Transaction trong Entity Framework

Ở bài viết này chúng ta sẽ tìm hiểu về các transaction (giao dịch) trong Entity Framework (EF).

Transaction trong Entity Framework

Trong Entity Framework (EF), nội bộ phương thức SaveChanges() tạo ra một transaction và bao bọc tất cả các thao tác INSERT, UPDATE và DELETE theo nó.

Gọi phương thức SaveChanges() nhiều lần sẽ tạo các transaction riêng biệt, thực hiện các thao tác CUD (Create, Update, Delete)  và sau đó commit từng transaction. Ví dụ sau đây chứng minh điều này.

using (var context = new SchoolContext())
{
    context.Database.Log = Console.Write;

    var standard = context.Standards.Add(new Standard() 
    { 
        StandardName = "1st Grade" 
    });

    context.Students.Add(new Student()
    {
        FirstName = "Rama",
        StandardId = standard.StandardId
    });

    context.SaveChanges();

    context.Courses.Add(new Course() 
    { 
        CourseName = "Computer Science" 
    });
    
    context.SaveChanges();
}

Đây là kết quả khi biên dịch và thực thi ví dụ trên:

Trong ví dụ trên, chúng tôi ghi nhật ký tất cả các lệnh cơ sở dữ liệu. Chúng tôi thêm một thực thể StandardStudent mới và lưu chúng vào cơ sở dữ liệu bằng phương thức SaveChanges().

Điều này sẽ tạo ra một transaction mới và thực hiện các lệnh INSERT cho các thực thể StandardStudent trong một transaction và commit chúng.

Sau đó, chúng tôi tiếp tục thêm một thực thể Course mới và gọi phương thức SaveChanges().

Điều này sẽ tạo ra một transaction mới hoàn toàn khác, thực hiện lệnh INSERT và sau đó commit transaction.

Do đó, mỗi khi phương thức SaveChanges() được gọi sẽ tạo ra một transaction mới và thực hiện các lệnh cơ sở dữ liệu bên trong nó.

Dùng chung transaction trong Entity Framework

Entity Framework cho phép chúng ta tạo hoặc sử dụng một transaction cho nhiều lần gọi phương thức SaveChanges() bằng các phương thức sau:

  1. DbContext.Database.BeginTransaction(): Tạo transaction mới cho cơ sở dữ liệu và cho phép chúng ta commit hoặc rollback các thay đổi được thực hiện cho cơ sở dữ liệu bằng nhiều lệnh gọi phương thức SaveChanges.
  2. DbContext.Database.UseTransaction(): Cho phép chúng ta truyền vào một đối tượng transaction hiện có được tạo ra ngoài phạm vi của một đối tượng Context. Điều này sẽ cho phép Entity Framework thực thi các lệnh trong một đối tượng transaction bên ngoài. Ngoài ra bạn có thể truyền null để xóa transaction đó.

DbContext.Database.BeginTransaction()

Ví dụ sau đây cho thấy việc tạo một đối tượng transaction mới bằng cách sử dụng phương thức BeginTransaction(), sau đó bạn có thể gọi phương thức SaveChanges() nhiều lần.

using (var context = new SchoolContext())
{
    context.Database.Log = Console.Write;

    using (DbContextTransaction transaction = context.Database.BeginTransaction())
    {
        try
        {
            var standard = context.Standards.Add(new Standard() 
            { 
                StandardName = "1st Grade" 
            });

            context.Students.Add(new Student()
            {
                FirstName = "Rama2",
                StandardId = standard.StandardId
            });
            context.SaveChanges();

            context.Courses.Add(new Course() 
            { 
                CourseName = "Computer Science" 
            });
            
            context.SaveChanges();

            transaction.Commit();
        }
        catch (Exception ex)
        {
            transaction.Rollback();
            Console.WriteLine("Error occurred.");
        }
    }
}

Trong ví dụ trên, chúng tôi tạo mới các thực thể Standard, StudentCourse rồi lưu chúng vào cơ sở dữ liệu bằng cách gọi phương thức SaveChanges() hai lần.

Tuy nhiên những thay đổi của các câu lệnh INSERT trong cơ sở dữ liệu chỉ được áp dụng khi bạn gọi phương thức Commit(). Hình ảnh sau đây cho thấy nhật ký đầu ra.

Lưu ý: Bạn có thể chỉ định các mức cô lập (isolation levels) khác nhau trong phương thức DbContext.Database.BeginTransaction(). Truy cập MSDN để tìm hiểu thêm về các mức cô lập.

Nếu một ngoại lệ xảy ra, thì toàn bộ thay đổi được thực hiện cho cơ sở dữ liệu sẽ được khôi phục do chúng ta đã xử lý mã trong khối try ... catch và gọi phương thức Rollback() trong khối catch khi có ngoại lệ xảy ra.

using (var context = new SchoolContext())
{
    context.Database.Log = Console.Write;

    using (DbContextTransaction transaction = context.Database.BeginTransaction())
    {
        try
        {
            var standard = context.Standards.Add(new Standard() 
            { 
                StandardName = "1st Grade" 
            });

            context.Students.Add(new Student()
            {
                FirstName = "Rama",
                StandardId = standard.StandardId
            });
            context.SaveChanges();
            
            // throw exectiopn to test roll back transaction
            throw new Exception();

            context.Courses.Add(new Course() 
            { 
                CourseName = "Computer Science" 
            });
            context.SaveChanges();

            transaction.Commit();
        }
        catch (Exception ex)
        {
            transaction.Rollback();
            Console.WriteLine("Error occurred.");
        }
    }
}

Trong ví dụ trên, chúng tôi đưa ra một ngoại lệ sau khi gọi phương thức SaveChanges() lần đầu tiên.

Điều này sẽ thực thi một khối catch trong đó chúng ta gọi phương thức RollBack() để khôi phục mọi thay đổi đã được thực hiện cho cơ sở dữ liệu. Hình dưới đây cho thấy đầu ra.

DbContext.Database.UseTransaction()

Phương thức DbContext.Database.UseTransaction() cho phép chúng ta sử dụng một transaction được tạo ngoài phạm vi của đối tượng Context.

Nếu chúng ta sử dụng phương thức UseTransaction(), thì Context sẽ không tạo ra một đối tượng transaction nội bộ và sẽ sử dụng transaction được cung cấp.

Ví dụ sau đây cho thấy phương thức UseTransaction() với cách tiếp cận Code First của Entity Framework 6.

private static void Main(string[] args)
{
    string providerName = "System.Data.SqlClient";
    string serverName = ".";
    string databaseName = "SchoolDB";

    // Initialize the connection string builder for the SQL Server provider.
    SqlConnectionStringBuilder sqlBuilder =
        new SqlConnectionStringBuilder();

    // Set the properties for the data source.
    sqlBuilder.DataSource = serverName;
    sqlBuilder.InitialCatalog = databaseName;
    sqlBuilder.IntegratedSecurity = true;

    using (SqlConnection con = new SqlConnection(sqlBuilder.ToString()))
    {
        con.Open();
        using (SqlTransaction transaction = con.BeginTransaction())
        {
            try
            {
                using (SchoolContext context = new SchoolContext(con, false))
                {
                    context.Database.UseTransaction(transaction);

                    context.Students.Add(new Student() { Name = "Ravi" });
                    context.SaveChanges();
                }

                using (SchoolContext context = new SchoolContext(con, false))
                {
                    context.Database.UseTransaction(transaction);

                    context.Grades.Add(new Standard() 
                    { 
                        GradeName = "Grade 1", 
                        Section = "A" 
                    });
                    context.SaveChanges();
                }
                transaction.Commit();
            }
            catch (Exception ex)
            {
                transaction.Rollback();

                Console.WriteLine(ex.InnerException);
            }
        }
    }
}

Sau đây là lớp SchoolContext được sử dụng trong ví dụ trên.

public class SchoolContext : DbContext
{
    public SchoolContext(DbConnection con, bool contextOwnsConnection) :base(con, contextOwnsConnection)
    {

    }
    public SchoolContext(): base("SchoolDB")
    {
        Database.SetInitializer<SchoolContext>(new CreateDatabaseIfNotExists<SchoolContext>());
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Grades { get; set; }
    public DbSet<Course> Courses { get; set; }
}


Bài viết liên quan:

2 kịch bản lưu dữ liệu trong Entity Framework Core là kịch bản được kết nối và kịch bản ngắt kết nối.

Tạo ứng dụng .NET Core Console đầu tiên và cấu hình sử dụng Entity Framework Core.

Truy vấn trong Entity Framework Core có gì mới? Truy vấn trong EF Core khác EF ở những điểm nào.