EF6 Onwards Only - The features, APIs, etc. discussed in this page were introduced in Entity Framework 6. If you are using an earlier version, some or all of the information does not apply.
Async Query & Save (EF6 onwards)
EF6 introduced support for asynchronous query and save using the async and await keywords that were introduced in .NET 4.5. While not all applications may benefit from asynchrony, it can be used to improve client responsiveness and server scalability when handling long-running, network or IO-bound tasks.
The following topics are covered on this page:
- When to really use async
- Create the model
- Create a synchronous program
- Making it asynchronous
- The takeaway
When to really use async
The purpose of this walkthrough is to introduce the async concepts in a way that makes it easy to observe the difference between asynchronous and synchronous program execution. This walkthrough is not intended to illustrate any of the key scenarios where async programming provides benefits.
Async programming is primarily focused on freeing up the current managed thread (thread running .NET code) to do other work while it waits for an operation that does not require any compute time from a managed thread. For example, whilst the database engine is processing a query there is nothing to be done by .NET code.
In client applications (WinForms, WPF, etc.) the current thread can be used to keep the UI responsive while the async operation is performed. In server applications (ASP.NET etc.) the thread can be used to process other incoming requests - this can reduce memory usage and/or increase throughput of the server.
In most applications using async will have no noticeable benefits and even could be detrimental. Use tests, profiling and common sense to measure the impact of async in your particular scenario before committing to it.
Here are some more resources to learn about async:
- Brandon Bray’s overview of async/await in .NET 4.5
- Asynchronous Programming pages in the MSDN Library
- How to Build ASP.NET Web Applications Using Async (includes a demo of increased server throughput)
Create the model
We’ll be using the Code First workflow to create our model and generate the database, however the asynchronous functionality will work with all EF models including those created with the EF Designer.
- Create a Console Application and call it AsyncDemo
- Add the EntityFramework NuGet package
- In Solution Explorer, right-click on the AsyncDemo project
- Select Manage NuGet Packages…
- In the Manage NuGet Packages dialog, Select the Online tab and choose the EntityFramework package
- Click Install
- Add a Model.cs class with the following implementation
using System.Collections.Generic;
using System.Data.Entity;
namespace AsyncDemo
{
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public virtual List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
}
Create a synchronous program
Now that we have an EF model, let's write some code that uses it to perform some data access.
- Replace the contents of Program.cs with the following code
using System;
using System.Linq;
namespace AsyncDemo
{
class Program
{
static void Main(string[] args)
{
PerformDatabaseOperations();
Console.WriteLine();
Console.WriteLine("Quote of the day");
Console.WriteLine(" Don't worry about the world coming to an end today... ");
Console.WriteLine(" It's already tomorrow in Australia.");
Console.WriteLine();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
public static void PerformDatabaseOperations()
{
using (var db = new BloggingContext())
{
// Create a new blog and save it
db.Blogs.Add(new Blog
{
Name = "Test Blog #" + (db.Blogs.Count() + 1)
});
db.SaveChanges();
// Query for all blogs ordered by name
var blogs = (from b in db.Blogs
orderby b.Name
select b).ToList();
// Write all blogs out to Console
Console.WriteLine();
Console.WriteLine("All blogs:");
foreach (var blog in blogs)
{
Console.WriteLine(" " + blog.Name);
}
}
}
}
}
This code calls the PerformDatabaseOperations method which saves a new Blog to the database and then retrieves all Blogs from the database and prints them to the Console. After this, the program writes a quote of the day to the Console.
Since the code is syncronous, we can observe the following execution flow when we run the program:
- SaveChanges begins to push the new Blog to the database
- SaveChanges completes
- Query for all Blogs is sent to the database
- Query returns and results are written to Console
- Quote of the day is written to Console
Making it asynchronous
Now that we have our program up and running, we can begin making use of the new async and await keywords. We've made the following changes to Program.cs
- Line 2: The using statement for the System.Data.Entity namespace gives us access to the EF async extension methods.
- Line 4: The using statement for the System.Threading.Tasks namespace allows us to use the Task type.
- Line 12 & 18: We are capturing as task that monitors the progress of PerformSomeDatabaseOperations (line 12) and then blocking program execution for this task to complete once all the work for the program is done (line 18).
- Line 25: We've update PerformSomeDatabaseOperations to be marked as async and return a Task.
- Line 35: We're now calling the Async version of SaveChanges and awaiting it's completion.
- Line 42: We're now calling hte Async version of ToList and awaiting on the result.
For a comprehensive list of available extension methods in the System.Data.Entity namespace, refer to the QueryableExtensions class. You’ll also need to add “using System.Data.Entity” to your using statements.
using System;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
namespace AsyncDemo
{
class Program
{
static void Main(string[] args)
{
var task = PerformDatabaseOperations();
Console.WriteLine("Quote of the day");
Console.WriteLine(" Don't worry about the world coming to an end today... ");
Console.WriteLine(" It's already tomorrow in Australia.");
task.Wait();
Console.WriteLine();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
public static async Task PerformDatabaseOperations()
{
using (var db = new BloggingContext())
{
// Create a new blog and save it
db.Blogs.Add(new Blog
{
Name = "Test Blog #" + (db.Blogs.Count() + 1)
});
Console.WriteLine("Calling SaveChanges.");
await db.SaveChangesAsync();
Console.WriteLine("SaveChanges completed.");
// Query for all blogs ordered by name
Console.WriteLine("Executing query.");
var blogs = await (from b in db.Blogs
orderby b.Name
select b).ToListAsync();
// Write all blogs out to Console
Console.WriteLine("Query completed with following results:");
foreach (var blog in blogs)
{
Console.WriteLine(" - " + blog.Name);
}
}
}
}
}
Now that the code is asyncronous, we can observe a different execution flow when we run the program:
- SaveChanges begins to push the new Blog to the database
Once the command is sent to the database no more compute time is needed on the current managed thread. The PerformDatabaseOperations method returns (even though it hasn't finished executing) and program flow in the Main method continues. - Quote of the day is written to Console
Since there is no more work to do in the Main method, the managed thread is blocked on the Wait call until the database operation completes. Once it completes, the remainder of our PerformDatabaseOperations will be executed. - SaveChanges completes
- Query for all Blogs is sent to the database
Again, the managed thread is free to do other work while the query is processed in the database. Since all other execution has completed, the thread will just halt on the Wait call though. - Query returns and results are written to Console
The takeaway
We now saw how easy it is to make use of EF’s asynchronous methods. Although the advantages of async may not be very apparent with a simple console app, these same strategies can be applied in situations where long-running or network-bound activities might otherwise block the application, or cause a large number of threads to increase the memory footprint.