semper fi
semper fi

Reputation: 737

How to make async with linq

i'm trying to make my first web application with asp.net mvc including remote database with linq. So i'm using default template from mvc, i've remade code in AccountController that implement register users with using linq, and now i'm interesting do it with async. So is it possible to handle linq with async? And if it yes, show me please example how to do that, it will be very helpful for me. Here's my example of registration via linq:

[AllowAnonymous]
public ActionResult Register()
{
    return View();
}

//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        using (DataBaseClassDataContext dc = new DataBaseClassDataContext())
        {
            Users tbUsers = new Users();
            tbUsers.Login = model.Login;
            tbUsers.Name = model.Name;
            tbUsers.Surname = model.Surname;
            tbUsers.Password = model.Password;
            tbUsers.E_Mail = model.Email;
            tbUsers.Knowledge = model.Knowledge;
            dc.Users.InsertOnSubmit(tbUsers);
            dc.SubmitChanges();

            ModelState.Clear();
            ViewBag.Message = "Successfully Registration Done";
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Upvotes: 3

Views: 6546

Answers (1)

Jon Hanna
Jon Hanna

Reputation: 113242

Let's look at your first action first:

[AllowAnonymous]
public ActionResult Register()
{
  return View();
}

Well, there's nothing here that does anything that would delay the current thread, so there's no benefit in doing anything asynchronously, so the best thing to do is to just leave this one alone.

Now, let's look at your second action. It contains:

dc.SubmitChanges();

That does indeed block the current thread while the changes are sent to the database. It probably won't be a big hit, but it is somewhere where we might benefit from using asynchronous code, especially because it's just so darn easy to do these days.

First change the signature:

public async Task<ActionResult> Register(RegisterViewModel model)

If you compile now it will work, but you'll get a warning:

 This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

But we've already done two things:

  1. Changed from returning ActionResult to returning Task<ActionResult>.
  2. Used async to mean that we can just do return View(model); and it gets turned into code that actually returns a Task<ActionResult> that when run will then run code like that in your original method.

But we've not actually gained anything. However instead of dc.SubmitChanges() we'll use dc.SubmitChangesAsync().

This is assuming it exists, which will depend on your data provider. More on that in a bit.

If it does exist then instead of returning void or some value T it would return Task or Task<T>.

We could write code to handle that task ourselves, but the easiest thing to do is:

await dc.SubmitChangesAsync();

Now the method we have is one that returns a Task<Action> that when initially Run (MVC will handle that bit for us) will execute the code as far as that await. Then it will release the thread running the code to do other things. After the SubmitChangesAsync has done its job, the execution of the method resumes at that point.

Similarly we could turn a method like:

var someList = someQuery.ToList();

Into:

var someList = await someQuery.ToListAsync();

Now, as I said, this all depends on the methods which result in database access actually having an async version. If there is no SubmitChangesAsync() then either you have to give up on this approach (boo!) or you have to write your own implementation (not trivial).

The current version of EntityFramework provides the following:

AllAsync
AnyAsync
AverageAsync
ContainsAsync
CountAsync
FirstAsync
FirstOrDefaultAsync
ForEachAsync
LoadAsync
LongCountAsync
MaxAsync
MinAsync
SingleAsync
SingleOrDefaultAsync
SumAsync
ToArrayAsync
ToDictionaryAsync
ToListAsync
SaveChangesAsync

That is, pretty much all of the methods that result in an actual database access have an Async version.

If you are not using a provider which offers this, then moving to asynchronous code will not be easy; it'll involve a more than can be put into a quick answer to do well.

One final caveat. Say you have an async method that always does a single await and that await is a tail-call:

public async Task<List<int>> GetList(int typeID)
{
  if(typeID < 1)
    throw new ArgumentOutOfRangeException();
  return await (from stuff in place where stuff.TypeID == typeID select stuff).ToListAsync();
}

Here you should not use async and await at all, because you are creating a task which will always result in a single task being awaited on, and so the extra await just complicates things behind the scenes for no good reason. Here you should just return the task:

public Task<List<int>> GetList(int typeID)
{
  if(typeID < 1)
    throw new ArgumentOutOfRangeException();
  return (from stuff in place where stuff.TypeID == typeID select stuff).ToListAsync();
}

But note that something that looks like a tail call inside a using block is not really a tail call, because it also implicitly does the Dispose() at the end of the using, so you can't simplify such calls in that way.

You can though simplify cases where there are different possible tail calls, as long as every path either does such a call, or throws an exception:

public async Task<List<int>> GetList(int? typeID)
{
  if(!typeID.HasValue)
    throw new ArgumentNullException();
  if(typeID.Value < 0)
    return await (from stuff in place).ToListAsync();
  return await (from stuff in place where stuff.TypeID == typeID select stuff).ToListAsync();
}

Can become:

public Task<List<int>> GetList(int? typeID)
{
  if(!typeID.HasValue)
    throw new ArgumentNullException();
  if(typeID.Value < 0)
    return (from stuff in place).ToListAsync();
  return (from stuff in place where stuff.TypeID == typeID select stuff).ToListAsync();
}

Upvotes: 14

Related Questions