The asynchronous API

So far, all of our database operations with Entity Framework have been synchronous. In other words, our .NET program waited for any given database operation, such as a query or an update, to complete before moving forward. In many cases, there is nothing wrong with this approach. There are use cases, however, where an ability to perform such operations asynchronously is important. In these cases, we let .NET use its execution thread while the software waits for the database operation to complete. For example, if you are creating a web application utilizing the asynchronous approach, we can be more efficient with server resources, releasing web worker threads back to the thread pool while we are waiting for the database to finish processing a request, whether it is a save or retrieve operation. Even in a desktop application, the asynchronous API is useful because the user can potentially perform other tasks in the application, instead of waiting on a possibly time-consuming query or save operation. In other words, the .NET thread does not need to wait for a database thread to complete its work. In a number of applications, the asynchronous API does not provide benefits and could even be harmful from the performance perspective due to the thread context switching. Before using the asynchronous API, developers need to make sure it will benefit them.

Entity Framework exposes a number of asynchronous operations. By convention, all such methods end with the Async suffix. For save operations, we can use the SaveChangesAsync method on DbContext. There are many methods for query operations. For example, many aggregate functions have asynchronous counterparts, such as SumAsync or AverageAsync. We can also asynchronously read a result set into a list or an array using ToListAsync or ToArrayAsync, respectively. Also, we can enumerate through the results of a query using ForEachAsync. Let's look at a few examples.

This is how we can get the list of objects from a database asynchronously:

private static async Task<IEnumerable<Company>> GetCompaniesAsync()
{
    using (var context = new Context())
    {
        return await context.Companies
            .OrderBy(c => c.CompanyName)
            .ToListAsync();
    }
}

It is important to notice that we follow typical async/await usage patterns. Our function is flagged as async and returns a task object, specifically a task of a list of companies. We create a DbContext object inside our function. Then, we create creating a query that returns all companies, ordered by their names. Then, we return the results of this query wrapped inside an asynchronous list generation. We have to await this return value, as we need to follow async/await patterns. Here is how this code looks in VB.NET:

Private Async Function GetCompaniesAsync() As Task(Of IEnumerable(Of Company))
    Using context = New Context()
        Return Await context.Companies.OrderBy( _
            Function(c) c.CompanyName).ToListAsync()
    End Using
End Function

Note

Any Entity Framework query can be converted to its asynchronous version using ToListAsync or ToArrayAsync.

Next, let's create a new record asynchronously:

private static async Task<Company> AddCompanyAsync(Company company)
{
    using (var context = new Context())
    {
        context.Companies.Add(company);
        await context.SaveChangesAsync();
        return company;
    }
}

Again, we are wrapping the operation inside the async function. We are accepting a parameter, company in our case. We are adding this company to the context. Finally, we save asynchronously and return the saved company. Here is how the code looks in VB.NET:

Private Async Function AddCompanyAsync(company As Company) As Task(Of Company)
    Using context = New Context()
        context.Companies.Add(company)
        Await context.SaveChangesAsync()
        Return company
    End Using
End Function

Next, we can locate a record asynchronously. We can use any number of methods here, such as Single or First. Both of them have asynchronous versions. We will use the Find method in our example, as shown in the following code snippet:

private static async Task<Company> FindCompanyAsync(int companyId)
{
    using (var context = new Context())
    {
        return await context.Companies
            .FindAsync(companyId);
    }
}

We see the familiar asynchronous pattern in this code snippet as well. We just use FindAsync, which takes the exact same parameters as the synchronous version. In general, all asynchronous methods in Entity Framework have the same signature, as far as parameters are concerned, as their synchronous counterparts.

Here is how the same method looks in VB.NET:

Private Async Function FindCompanyAsync(companyId As Integer) As Task(Of Company)
    Using context = New Context()
        Return Await context.Companies.FindAsync(companyId)
    End Using
End Function

As we mentioned before, aggregate functions have the async versions as well. For example, here is how we compute count asynchronously:

private static async Task<int> ComputeCountAsync()
{
    using (var context = new Context())
    {
        return await context.Companies
            .CountAsync(c => c.IsActive);
    }
}

We use the CountAsync method and pass in a condition, which is exactly what we would have done if we were to call the synchronous version, the Count function. Here is how the code looks in VB.NET:

Private Async Function ComputeCountAsync() As Task(Of Integer)
    Using context = New Context()
        Return Await context.Companies.CountAsync( _
            Function(c) c.IsActive)
    End Using
End Function

If you would like to loop asynchronously through query results, you can also use ForEachAsync, which you can attach to any query as well. For example, here is how we can loop through Companies:

private static async Task LoopAsync()
{
    using (var context = new Context())
    {
        await context.Companies.ForEachAsync(c =>
        {
            c.IsActive = true;
        });
        await context.SaveChangesAsync();
    }
}

In the preceding code, we run through all type Companies, but we could have just as easily run through a result of any query, after applying an order or a filter. Here is what this method looks like in VB.NET:

Private Async Function LoopAsync() As Task
    Using context = New Context()
        Await context.Companies.ForEachAsync( _
            Sub(c)
                c.IsActive = True
            End Sub)
        Await context.SaveChangesAsync()
    End Using
End Function

We followed the accepted naming conventions, adding the Async suffix to our asynchronous functions. Usually, these functions would be called from a method that is also flagged as async and we would have awaited a result of our asynchronous functions. If this is not possible, we can always use the Task API and wait for a task to complete. For example, we can access the result of a task, causing the current thread to pause and let the task finish executing, as shown in the following code snippet:

Console.WriteLine(
                FindCompanyAsync(companyId).Result.CompanyName);

In this example, we call the previously defined function and then access the Result property of the task to cause the asynchronous function to finish executing. This is how the code would look in VB.NET:

Console.WriteLine(FindCompanyAsync(companyId).Result.CompanyName)

When deciding whether or not to use the asynchronous API, we need to research and make sure that there is a reason to do so. We should also ensure that the entire calling chain of methods is asynchronous to gain maximum coding benefits. Finally, resort to the Task API when you need to.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.217.253