Asynchronous Programming with Entity Framework Core

Asynchronous Programming with Entity Framework Core

In this article, I will discuss Asynchronous Programming with Entity Framework Core (EF Core) with Examples. Please read our previous article discussing Bulk Operations using Entity Framework Core Extension with Examples. At the end of this article, you will understand the following pointers.

  1. Asynchronous Programming with Entity Framework Core
  2. Example to Understand Asynchronous Programming with EF Core
  3. Asynchronous Query in Entity Framework Core
  4. Asynchronous Save using Entity Framework Core
  5. Asynchronous Delete in Entity Framework Core
  6. Asynchronous Operation with Cancellation Token in EF Core
  7. Advantages of Asynchronous Programming with Entity Framework Core
  8. Disadvantages of Asynchronous Programming with Entity Framework Core
Asynchronous Programming with Entity Framework Core

Asynchronous programming with Entity Framework Core (EF Core) allows us to perform database operations efficiently without blocking the main (UI) thread or the calling thread in a non-UI application. This is especially important for applications that need to remain responsive and scalable. 

Asynchronous programming in Entity Framework Core allows for non-blocking database operations, enhancing the scalability and responsiveness of applications, particularly those with I/O-bound operations, such as web applications. Asynchronous operations in EF Core are achieved using the async and await keywords in C#, along with asynchronous versions of EF Core methods.

Use Async/Await Keywords:

To perform asynchronous database operations, we should use the async and await keywords in our C# code. These keywords enable non-blocking execution and allow our application to continue processing other tasks while waiting for the database operation to complete.

Async Methods in EF Core:

Entity Framework Core (EF Core) provides asynchronous methods for most synchronous methods. For example, you can use ToListAsync, FirstOrDefaultAsync, SaveChangesAsync, etc.

// Synchronous query
var students = context.Students.ToList();

// Asynchronous query
var studentsAsync = await context.Students.ToListAsync();

The asynchronous methods typically have “Async” suffixes, making it easy to identify their asynchronous nature.

DbContext Lifetime:

Ensure the DbContext instance you use for asynchronous operations has a suitable lifetime. In web applications, using a scoped or per-request DbContext is common to ensure that it’s disposed of at the appropriate time.

Exception Handling:

Handle exceptions that may occur during asynchronous operations. You can use try/catch blocks to catch and handle exceptions thrown by EF Core or the underlying database.

try
{
    // Perform asynchronous database operation
}
catch (Exception ex)
{
    // Handle the exception
}
Cancellation:

You can pass a CancellationToken to asynchronous methods to allow for task cancellation. This is useful for scenarios where you want to cancel a database operation that is taking too long.

var cancellationToken = new CancellationToken();
var studentsAsync = await context.Students
    .ToListAsync(cancellationToken);
Async Database Updates:

You can use SaveChangesAsync for asynchronous database updates. This is especially important when making multiple changes to the database within a single transaction.

// Asynchronous database update
await context.SaveChangesAsync();
Avoid Mixing Synchronous and Asynchronous Code:

Avoid mixing synchronous and asynchronous code within a single method or operation, as it can lead to deadlocks or inefficient code. Stick to asynchronous code when performing database operations asynchronously.

Example to Understand Asynchronous Programming with EF Core:

To understand Asynchronous Programming with Entity Framework Core (EF Core), we will use the following Student Entity. So, create a class file named Student.cs and copy and paste the following code.

namespace EFCoreCodeFirstDemo.Entities
{
    public class Student
    {
        public int StudentId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Branch { get; set; }
    }
}

Next, modify the Context class as follows:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
{
    public class EFCoreDbContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            //Configuring the Connection String
            optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertificate=True;");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        }

        public DbSet<Student> Students { get; set; }
    }
}

With the above changes, open the Package Manager Console and Execute the add-migration and update-database commands as follows. You can give any name to your migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be given earlier.

Asynchronous Programming with Entity Framework Core with Examples

With this, our Database with Students table is created, as shown in the below image:

Asynchronous Programming with EF Core with Examples

Please execute the following SQL Script to insert demo data into the Students database table.

INSERT INTO Students Values ('Pranaya', 'Rout', 'CSE');
INSERT INTO Students Values ('Hina', 'Sharma', 'CSE');
INSERT INTO Students Values ('Priyanka', 'Dewangan', 'CSE');
INSERT INTO Students Values ('Anurag', 'Mohanty', 'ETC');
Example to Understand Asynchronous Query in Entity Framework Core:

For a better understanding, please modify the Program class as follows:

using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreCodeFirstDemo
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                //Environment.CurrentManagedThreadId will return the currently executing unique thread id
                Console.WriteLine($"Main Method Started by ThreadId: {Environment.CurrentManagedThreadId}");
                var query = GetStudent(2);

                Console.WriteLine($"Main Method Doing Some Other Task by ThreadId: {Environment.CurrentManagedThreadId}");
                //Here, we are blocking the Main thread to get the result from the GetStudent Method
                var student = query.Result;

                Console.WriteLine($"Name: {student?.FirstName} {student?.LastName}");
                Console.WriteLine($"Main Method Completed by ThreadId: {Environment.CurrentManagedThreadId}");
                Console.Read();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}"); ;
            }
        }

        private static async Task<Student?> GetStudent(int StudentId)
        {
            Console.WriteLine($"GetStudent Method Started by ThreadId: {Environment.CurrentManagedThreadId}");
            Student? student = null;
            using (var context = new EFCoreDbContext())
            {
                Console.WriteLine($"FirstOrDefaultAsync Method Started by ThreadId: {Environment.CurrentManagedThreadId}");
                student = await (context.Students.FirstOrDefaultAsync(s => s.StudentId == StudentId));
                Console.WriteLine($"FirstOrDefaultAsync Method Completed by ThreadId: {Environment.CurrentManagedThreadId}");
            }
            Console.WriteLine($"GetStudent Method Completed by ThreadId: {Environment.CurrentManagedThreadId}");
            return student;
        }
    }
}
Output:

Example to Understand Asynchronous Query in Entity Framework Core

As you can see in the above output, the Main method is started and completed by a thread whose ID is 1. But please focus on the GetStudent method. It is started by the Thread, whose ID is 1. But once it starts executing the FirstOrDefaultAsync asynchronous method, the Thread whose Id is 1 is released, and that thread starts executing the other code in the Main method. Once the query execution is completed, another thread whose ID is 5 will come from the thread pool and start executing the rest of the code inside the GetStudent method.

Asynchronous Save using Entity Framework Core

The Entity Framework Core provides an asynchronous version of the SaveChanges() method called the SaveChangesAsync() to save entities into the database asynchronously. For a better understanding, please look at the example below, which shows saving a Student Entity to the database asynchronously. In this case, when we call the SaveChangesAsync() method, the calling thread will be released and do another task. Once the SaveChangesAsync() method execution is completed, another thread pool thread will come and execute the rest of the code of the SaveStudent async method.

using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
    public class Program
    {
        static async Task Main(string[] args)
        {
            try
            {
                //Environment.CurrentManagedThreadId will return the currently executing unique thread id
                Console.WriteLine($"Main Method Started by ThreadId: {Environment.CurrentManagedThreadId}");
                var student = new Student()
                {
                    FirstName = "James",
                    LastName = "Smith",
                    Branch = "ETC"
                };

                //Calling the Async SaveStudent Method
                var result = SaveStudent(student);
                Console.WriteLine($"Main Method Doing Some Other Task by ThreadId: {Environment.CurrentManagedThreadId}");

                //Let us wait for the SaveStudent method to complete
                //await result;

                Console.WriteLine($"Main Method Completed by ThreadId: {Environment.CurrentManagedThreadId}");
                Console.Read();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}"); ;
            }
        }

        private static async Task SaveStudent(Student newStudent)
        {
            Console.WriteLine($"SaveStudent Method Started by ThreadId: {Environment.CurrentManagedThreadId}");
            using (var context = new EFCoreDbContext())
            {
                context.Students.Add(newStudent);
                Console.WriteLine($"SaveChangesAsync Method Started by ThreadId: {Environment.CurrentManagedThreadId}");
                //Calling the SaveChangesAsync Method, it will release the calling thread
                await (context.SaveChangesAsync());
                Console.WriteLine($"SaveChangesAsync Method Completed by ThreadId: {Environment.CurrentManagedThreadId}");
            }
            Console.WriteLine($"SaveStudent Method Completed by ThreadId: {Environment.CurrentManagedThreadId}");
        }
    }
}
Output:

Asynchronous Save using Entity Framework Core

As you can see in the above output, the Main method is executed by one thread, and two threads execute the SaveStudent method. This is possible because when the SaveChangesAsync method is called, it releases the calling thread, and when the SaveChangesAsync method completes its execution, another thread from the thread pool will come and execute the rest of the code.

Asynchronous Delete in Entity Framework Core

In this case, first, you need to Remove the entity from the context object, which will mark the entity state as Deleted. When we call the SaveChangesAsync() method, it will remove the entity from the database asynchronously. For a better understanding, please have a look at the following example. The following example code is self-explained, so please go through the comment lines.

using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
    public class Program
    {
        static void Main(string[] args)
        {
            try
            {
                //Environment.CurrentManagedThreadId will return the currently executing unique thread id
                Console.WriteLine($"Main Method Start by ThreadId: {Environment.CurrentManagedThreadId}");
                var deleteAsyncQuery = DeleteStudentAsync(1);
                
                Console.WriteLine($"Main Method Doing Some other task by ThreadId: {Environment.CurrentManagedThreadId}");
                //Wait till the DeleteStudentAsync Method completed its execution

                var deleteAsyncResult = deleteAsyncQuery.Result;
                Console.WriteLine($"DeleteStudentAsync Method Result: {deleteAsyncResult}");
                Console.WriteLine($"Main Method Completed by ThreadId: {Environment.CurrentManagedThreadId}");
                Console.Read();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}"); ;
            }
        }

        public static async Task<string> DeleteStudentAsync(int StudentId)
        {
            Console.WriteLine($"DeleteStudentAsync Method Started by ThreadId: {Environment.CurrentManagedThreadId}");
            using (var context = new EFCoreDbContext())
            {
                Console.WriteLine($"FindAsync Method Started by ThreadId: {Environment.CurrentManagedThreadId}");
                //Calling the FindAsync Method, it will release the thread
                Student? DeleteStudent = await context.Students.FindAsync(StudentId);

                if(DeleteStudent != null )
                {
                    //Then remove the entity by calling the Remove Method which will mark the Entity State as Deleted
                    context.Students.Remove(DeleteStudent);
                    Console.WriteLine($"SaveChangesAsync Method Started by ThreadId: {Environment.CurrentManagedThreadId}");
                    //Calling the SaveChangesAsync Method, it will release the thread
                    await (context.SaveChangesAsync());
                }
            }
            Console.WriteLine($"DeleteStudentAsync Method Completed by ThreadId: {Environment.CurrentManagedThreadId}");
            return "Student Deleted Successfully";
        }
    }
}
Output:

Asynchronous Delete in Entity Framework Core

Note: If the student you are trying to delete has any foreign key relationship data, it will not allow you to delete the student. In such cases, first, you need to delete foreign key table data and then delete the student data. In most real-time applications, we never delete any data from the database. In most cases, we use some kind of flag, such as IsActive, which indicates whether the record is active or inactive.

Asynchronous Operation with Cancellation Token in EF Core:

Cancellation is an important feature of asynchronous programming, especially when working with Entity Framework Core (EF Core), to ensure that long-running database operations can be canceled when needed. Let us see an example to understand how to use cancellation with EF Core asynchronous queries:

using EFCoreCodeFirstDemo.Entities;
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo
{
    public class Program
    {
        static async Task Main(string[] args)
        {
            // Create a cancellation token source with a timeout of 500 Milliseconds
            var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
            CancellationToken cancellationToken = cancellationTokenSource.Token;

            try
            {
                //Creating the Context Object
                using var context = new EFCoreDbContext();

                // Asynchronously retrieve a list of students with a cancellation token
                List<Student> students = await context.Students
                    .Where(s => s.Branch.StartsWith("C")) // Example query
                    .ToListAsync(cancellationToken);

                foreach (var student in students)
                {
                    Console.WriteLine($"Student ID: {student.StudentId}, Name: {student.FirstName} {student.LastName}, Branch: {student.Branch}");
                }
                Console.Read();
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("Query was canceled due to a timeout.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}"); ;
            }
        }

    }
}
In this code:
  • We create a CancellationTokenSource with a timeout of 500 Milli Seconds (TimeSpan.FromMilliseconds(5)) to specify the maximum duration for the query.
  • We obtain a CancellationToken from the CancellationTokenSource. This token will cancel the query if it exceeds the specified timeout.
  • We create an asynchronous query using EF Core’s ToListAsync method, passing the cancellationToken to the method. This allows us to cancel the query if it runs longer than the specified timeout.
  • If the query completes within the timeout, the list of students is displayed. If the query is canceled due to the timeout, an OperationCanceledException is caught, and a corresponding message is displayed.
Advantages and Disadvantages of Asynchronous Programming with EF Core

Asynchronous programming with Entity Framework Core (EF Core) offers several advantages and disadvantages, depending on how it’s used and the specific requirements of your application. Here are the advantages and disadvantages of asynchronous programming with EF Core:

Advantages of Asynchronous Programming with Entity Framework Core:
  • Improved Scalability: Asynchronous operations free up the thread to handle other requests while waiting for I/O operations to complete, thus better-using server resources and handling more requests concurrently.
  • Enhanced Responsiveness: In UI applications, asynchronous database calls prevent the UI from freezing or becoming unresponsive while the database operation is processed. This is important for maintaining a smooth user experience, especially in client applications like web and desktop applications.
  • Efficient Resource Utilization: Asynchronous programming leads to more efficient use of server resources, as threads are not idly waiting for I/O operations to complete.
Disadvantages of Asynchronous Programming with Entity Framework Core:
  • Complexity: Asynchronous code can be more complex to write and maintain than synchronous code, especially when handling error cases, cancellation, and coordination between asynchronous tasks.
  • Debugging: Debugging asynchronous code can be challenging, as breakpoints and debugging tools may not work seamlessly with asynchronous operations, making it harder to trace the execution flow.
  • Deadlocks: Incorrectly written asynchronous code can lead to deadlocks, especially when mixing synchronous and asynchronous code or not properly handling synchronization context.

In the next article, I will discuss Disconnected Entities in Entity Framework Core with Examples. In this article, I try to explain Asynchronous Programming with Entity Framework Core with Examples. I hope you enjoyed this Asynchronous Programming with Entity Framework Core article.

Leave a Reply

Your email address will not be published. Required fields are marked *