2 kịch bản lưu dữ liệu trong Entity Framework Core

Entity Framework Core cung cấp các cách khác nhau để thêm, cập nhật hoặc xóa dữ liệu trong cơ sở dữ liệu. Một thực thể chứa dữ liệu trong thuộc tính của nó sẽ được thêm hoặc cập nhật hoặc xóa dựa trên trạng thái EntityState của nó.

Có hai kịch bản để lưu dữ liệu thực thể: được kết nối và ngắt kết nối. Trong kịch bản được kết nối, cùng một thể hiện DbContext được sử dụng trong việc truy xuất và lưu các thực thể, trong khi điều này khác với kịch bản ngắt kết nối.

Lưu dữ liệu trong kịch bản được kết nối trong EF Core

Hình dưới đây minh họa các thao tác CUD (Create, Update, Delete) trong kịch bản được kết nối.

Lưu dữ liệu trong kịch bản được kết nối

Theo hình trên, Entity Framework xây dựng và thực thi các câu lệnh INSERT, UPDATE hoặc DELETE cho các thực thể có trạng thái EntityState là Added, Modified hoặc Deleted khi phương thức DbContext.SaveChanges() được gọi.

Trong kịch bản được kết nối, một thể hiện DbContext theo dõi tất cả các thực thể và do đó, nó sẽ tự động thiết lập một trạng thái EntityState phù hợp cho mỗi thực thể bất cứ khi nào một thực thể được tạo, sửa đổi hoặc xóa.

Thêm dữ liệu

Các phương thức DbSet.AddDbContext.Add thêm một thực thể mới vào Context (ví dụ của DbContext) sẽ tạo một bản ghi mới vào cơ sở dữ liệu khi bạn gọi phương thức SaveChanges().


using (var context = new SchoolContext())
{
    var std = new Student()
    {
        FirstName = "Bill",
        LastName = "Gates"
    };
    context.Students.Add(std);

    // or
    // context.Add<Student>(std);

    context.SaveChanges();
}

Trong ví dụ trên, context.Students.Add(std) thêm một thể hiện mới được tạo của thực thể Student vào Context với trạng thái EntityState là Added.

EF Core đã giới thiệu phương thức DbContext.Add mới, hoạt động tương tự như phương thức DbSet.Add. Sau đó, phương thức SaveChanges() xây dựng và thực thi câu lệnh INSERT sau vào cơ sở dữ liệu.


exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Students] ( [FirstName], [LastName])
VALUES (@p0, @p1);
SELECT [StudentId]
FROM [Students]
WHERE @@ROWCOUNT = 1 AND [StudentId] = scope_identity();',N
'@p0 nvarchar(4000), @p1 nvarchar(4000) ',@p0=N'Bill',@p1=N'Gates'
go

Cập nhật dữ liệu

Trong kịch bản được kết nối, EF Core API theo dõi tất cả các thực thể được truy xuất bằng Context.

Vì vậy, khi bạn chỉnh sửa dữ liệu của thực thể, EF sẽ tự động đánh dấu trạng thái EntityState của thực thể là Modified, mà kết quả của nó là câu lệnh cập nhật trong cơ sở dữ liệu khi bạn gọi phương thức SaveChanges().


using (var context = new SchoolContext())
{
    var std = context.Students.First<Student>(); 
    std.FirstName = "Steve";
    context.SaveChanges();
}

Trong ví dụ trên, chúng tôi lấy sinh viên đầu tiên từ cơ sở dữ liệu bằng cách sử dụng context.Students.First<student>().

Ngay sau khi chúng tôi sửa đổi thuộc tính FirstName, Context thiết lập trạng thái  EntityState của thực thể là Modified do sửa đổi được thực hiện trong phạm vi của thể hiện DbContext.

Vì vậy, khi chúng ta gọi phương thức SaveChanges(), nó sẽ xây dựng và thực thi câu lệnh Update sau trong cơ sở dữ liệu.


exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Students] SET [FirstName] = @p0
WHERE [StudentId] = @p1;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 nvarchar(4000)',@p1=1,@p0=N'Steve'
Go

Trong câu lệnh cập nhật, EF Core API chỉ cập nhật các thuộc tính có giá trị được sửa đổi, các thuộc tính còn lại bị bỏ qua.

Trong ví dụ trên, chỉ có thuộc tính FirstName được chỉnh sửa, vì vậy một câu lệnh cập nhật chỉ có cột FirstName.

Xóa dữ liệu

Sử dụng phương thức DbSet.Remove() hoặc DbContext.Remove để xóa một bản ghi trong bảng cơ sở dữ liệu.


using (var context = new SchoolContext())
{
    var std = context.Students.First<Student>();
    context.Students.Remove(std);

    // or
    // context.Remove<Student>(std);

    context.SaveChanges();
}

Trong ví dụ trên, context.Students.Remove(std) hoặc context.Remove<Students>(std) đánh dấu đối tượng thực thể std có trạng thái EntityStateDeleted. Do đó, EF Core sẽ xây dựng và thực thi câu lệnh DELETE sau trong cơ sở dữ liệu.


exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [Students]
WHERE [StudentId] = @p0;
SELECT @@ROWCOUNT;
',N'@p0 int',@p0=1
Go

Do đó, rất dễ dàng để thêm, cập nhật hoặc xóa dữ liệu trong Entity Framework Core trong kịch bản được kết nối.

Lưu dữ liệu trong kịch bản ngắt kết nối trong EF Core

Lưu dữ liệu trong kịch bản ngắt kết nối hơi khác một chút so với kịch bản được kết nối.

Trong kịch bản bị ngắt kết nối, DbContext không biết các thực thể bị ngắt kết nối vì các thực thể đã được thêm hoặc sửa đổi ngoài phạm vi của thể hiện DbContext hiện tại.

Vì vậy, bạn cần đính kèm (attach) các thực thể bị ngắt kết nối vào một Context với trạng thái EntityState phù hợp để thực hiện các thao tác CUD (Create, Update, Delete) vào cơ sở dữ liệu.

Hình dưới đây minh họa các thao tác CUD trong kịch bản ngắt kết nối:

Lưu dữ liệu trong kịch bản ngắt kết nối trong EF Core

Theo hình trên, các thực thể bị ngắt kết nối (các thực thể không được theo dõi bởi DbContext) cần phải được đính kèm vào DbContext với trạng thái EntityState phù hợp.

Ví dụ: Trạng thái Added cho các thực thể mới, trạng thái Modified cho các thực thể đã chỉnh sửa và trạng thái Deleted cho các thực thể bị xóa, điều này sẽ thực thi các lệnh INSERT, UPDATE hoặc DELETE trong cơ sở dữ liệu khi phương thức SaveChanges() được gọi.

Thêm dữ liệu

Các bước sau đây phải được thực hiện để thêm, cập nhật hoặc xóa các bản ghi vào bảng DB bằng Entity Framework Core trong kịch bản ngắt kết nối:

  1. Đính kèm một thực thể vào DbContext với trạng thái EntityState thích hợp, ví dụ: Added, Modified, hoặc Deleted.
  2. Gọi phương thức SaveChanges().

Ví dụ sau đây cho thấy việc thêm một bản ghi mới vào cơ sở dữ liệu bằng các bước trên:


//Disconnected entity
var std = new Student() { Name = "Bill" };

using (var context = new SchoolContext())
{
    //1. Attach an entity to context with Added EntityState
    context.Add<Student>(std);
    
    //or the followings are also valid
    // context.Students.Add(std);
    // context.Entry<Student>(std).State = EntityState.Added;
    // context.Attach<Student>(std);
                  
    //2. Calling SaveChanges to insert a new record into Students table
    context.SaveChanges();
}

Trong ví dụ trên, std là một thể hiện bị ngắt kết nối của thực thể Student. Phương thức context.Add<Student>() gắn một thực thể Student vào Context với tình trạng Added. Phương thức SaveChanges() xây dựng và thực thi câu lệnh INSERT sau:


exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Students] ([Name])
VALUES (@p0);
SELECT [StudentId]
FROM [Students]
WHERE @@ROWCOUNT = 1 AND [StudentId] = scope_identity();',N'@p0 nvarchar(4000), 
@p1 nvarchar(4000) ',@p0=N'Bill'
go

EF Core cung cấp nhiều cách để thêm các thực thể với trạng thái Added. Trong ví dụ trên câu lệnh context.Students.Add(std);, context.Entry<Student>(std).State = EntityState.Added;context.Attach<Student>(std); sẽ dẫn đến cùng một câu lệnh INSERT như trên.

Entity Framework Core cung cấp các phương thức DbContextDbSet sau đây để gắn các thực thể bị ngắt kết nối với trạng thái EntityState là Added, chúng sẽ lần lượt thực thi các câu lệnh INSERT trong cơ sở dữ liệu.

Phương thức DbContext Phương thức DbSet Mô tả
DbContext.Attach DbSet.Attach Đính kèm một thực thể vào DbContext. Đặt trạng thái Unchanged cho thực thể có thuộc tính khóa có giá trị và trạng thái Added cho thực thể có thuộc tính khóa trống hoặc giá trị mặc định của kiểu dữ liệu.
DbContext.Add DbSet.Add Đính kèm một thực thể vào DbContext với trạng thái Added.
DbContext.AddRange DbSet.AddRange Đính kèm một tập các thực thể vào DbContext với trạng thái Added.
DbContext.Entry - Trả về một EntityEntry cho thực thể được chỉ định, nó cung cấp truy cập để thay đổi thông tin và hoạt động theo dõi.
DbContext.AddAsync DbSet.AddAsync Phương thức không đồng bộ của phương thức Add.
DbContext.AddRangeAsync DbSet.AddRangeAsync Phương thức không đồng bộ của phương thức AddRange.
Lưu ý: Các phương thức của DbContext ở trên được giới thiệu trong EF Core (chúng không có sẵn trong EF 6 hoặc trước đó). Cả hai phương thức của DbContextDbSet thực hiện cùng một hoạt động. Bạn sử dụng cái nào phụ thuộc vào mẫu code và sở thích của bạn.

Thêm dữ liệu liên quan

Trong chương trước, chúng ta đã học cách tạo mối quan hệ một-một, một-nhiều và nhiều-nhiều giữa hai thực thể. EF Core API sẽ thêm tất cả dữ liệu quan hệ có trong các thực thể liên quan vào cơ sở dữ liệu.

Sử dụng phương thức DbContext.Add hoặc DbSet.Add để thêm các thực thể liên quan vào cơ sở dữ liệu.

Phương thức Add đính kèm các thực thể vào Context và thiết lập trạng thái Added cho tất cả các đối tượng trong một biểu đồ thực thể mà thuộc tính khóa Id có giá trị là trống, null hoặc giá trị mặc định của kiểu dữ liệu.

Hãy xem ví dụ sau.


var stdAddress = new StudentAddress()
{
    City = "SFO",
    State = "CA",
    Country = "USA"
};

var std = new Student()
{
    Name = "Steve",
    Address = stdAddress
};
using (var context = new SchoolContext())
{
    // Attach an entity to DbContext with Added state
    context.Add<Student>(std);

    // Calling SaveChanges to insert a new record into Students table
    context.SaveChanges();
}

Trong ví dụ trên, context.Add<Student>(std) thêm một thể hiện của thực thể Student. EF Core API phát hiện thể hiện của StudentAddress thông qua thuộc tính điều hướng tham chiếu trong thực thể Student và thiết lập trạng thái EntityState của cả hai thực thể là Added. Nó sẽ xây dựng và thực thi hai lệnh INSERT sau khi gọi phương thức SaveChanges().


exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Students] ([Name])
VALUES (@p0);
SELECT [StudentId]
FROM [Students]
WHERE @@ROWCOUNT = 1 AND [StudentId] = scope_identity();',N'@p0 nvarchar(4000), 
@p1 nvarchar(4000) ',@p0=N'Steve'
go

exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [StudentAddresses] ([Address], [City], [Country], [State], [StudentId])
VALUES (@p5, @p6, @p7, @p8, @p9);
SELECT [StudentAddressId]
FROM [StudentAddresses]
WHERE @@ROWCOUNT = 1 AND [StudentAddressId] = scope_identity();
',N'@p5 nvarchar(4000),@p6 nvarchar(4000),@p7 nvarchar(4000),@p8 nvarchar(4000),
@p9 int',@p5=NULL,@p6=N'SFO',@p7=N'USA',@p8=N'CA',@p9=1
Go

Thêm nhiều bản ghi

Sử dụng phương thức DbContext.AddRange hoặc DbSet.AddRange để thêm nhiều thực thể trong một lần. Bạn không cần phải gọi phương thức DbContext.Add nhiều lần.

Phương thức AddRange Mô tả
AddRange(IEnumerable<Object> entities) Thêm danh sách các thực thể vào DbContext với trạng thái Added.
AddRange(param object[] entities) Thêm một mảng các thực thể vào DbContext với trạng thái Added.
AddRangeAsync(IEnumerable<Object>, CancellationToken) Phương thức không đồng bộ của phương thức AddRange(IEnumerable<Object> entities).

Ví dụ sau đây cho thấy việc thêm một danh sách các đối tượng thực thể Student bằng phương thức AddRange.


var studentList = new List<Student>() 
{
    new Student(){ Name = "Bill" },
    new Student(){ Name = "Steve" }
};

using (var context = new SchoolContext())
{
    context.AddRange(studentList);
    context.SaveChanges();
}

Ví dụ trên sẽ thêm hai bản ghi mới vào bảng Students.

Bạn cũng có thể thêm một danh sách các kiểu thực thể khác nhau, như được trình bày bên dưới.


var std1 = new Student() { Name = "Bill" };

var std2 = new Student() { Name = "Steve" };

var computer = new Course() { CourseName = "Computer Science" };

var entityList = new List<Object>() 
{
    std1,
    std2,
    computer
};

using (var context = new SchoolContext())
{                
    context.AddRange(entityList);

    // or 
    // context.AddRange(std1, std2, computer);

    context.SaveChanges();
}

Trong ví dụ trên, entityList là một danh sách kiểu List<Object>. Vì vậy, nó có thể chứa bất kỳ kiểu thực thể nào. Phương thức AddRange() gắn tất cả các đối tượng được chỉ định vào Context và phương thức SaveChanges() sẽ xây dựng và thực hiện câu lệnh INSERT cho tất cả thực thể trong một lần gọi cơ sở dữ liệu.

EF Core cải thiện hiệu suất bằng cách thực hiện các câu lệnh INSERT cho tất cả các thực thể trên trong một lần gọi cơ sở dữ liệu. Ví dụ trên sẽ thực thi các câu lệnh sau trong cơ sở dữ liệu.


exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Courses] ([CourseName], [Description])
VALUES (@p0, @p1);
SELECT [CourseId]
FROM [Courses]
WHERE @@ROWCOUNT = 1 AND [CourseId] = scope_identity();

DECLARE @inserted1 TABLE ([StudentId] int, [_Position] [int]);
MERGE [Students] USING (
VALUES (@p2, 0),
(@p3, 1)) AS i ([Name], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name])
VALUES (i.[Name])
OUTPUT INSERTED.[StudentId], i._Position
INTO @inserted1;

SELECT [t].[StudentId] FROM [Students] t
INNER JOIN @inserted1 i ON ([t].[StudentId] = [i].[StudentId])
ORDER BY [i].[_Position];
',N'@p0 nvarchar(4000),@p1 nvarchar(4000),@p2 nvarchar(4000),@p3 nvarchar(4000)',
@p0=N'Computer Science',@p1=NULL,@p2=N'Steve',@p3=N'Bill'
go

Thêm dữ liệu bằng DbSet

Như đã đề cập trước đó, bạn có thể sử dụng DbSet để lưu một thể hiện của một thực thể, nó sẽ được dịch thành lệnh INSERT / UPDATE / DELETE trong cơ sở dữ liệu, giống như EF 6.x.

Sử dụng phương thức DbSet<TEntity>.Add() để đính kèm một thực thể với trạng thái Added hoặc phương thức DbSet<TEntity>.AddRange() để đính kèm một tập các thực thể với trạng thái Added, như được trình bày bên dưới.


var std = new Student()
{
    Name = "Bill"
};

using (var context = new SchoolContext())
{
    context.Students.Add(std);

    // or
    // context.Students.Attach(std);

    context.SaveChanges();
}

Trong ví dụ trên, kiểu dữ liệu của context.Students chính là kiểu DbSet<Student>. Vì vậy, chúng ta chỉ có thể thêm thực thể Student. context.Students.Add(std) gắn thực thể Student vào Context với trạng thái Added, sẽ thực thi câu lệnh INSERT trong cơ sở dữ liệu khi phương thức SaveChanges() được gọi.

Cập nhật dữ liệu

EF Core API xây dựng và thực thi câu lệnh UPDATE trong cơ sở dữ liệu cho các thực thể có trạng thái EntityState là Modified. Trong kịch bản được kết nối, DbContext theo dõi tất cả các thực thể để nó biết cái nào được sửa đổi và do đó tự động đặt trạng thái EntityState thành Modified.

Trong kịch bản ngắt kết nối, chẳng hạn như trong một ứng dụng web, DbContext không biết về các thực thể vì các thực thể đã được sửa đổi ngoài phạm vi của thể hiện DbContext hiện tại.

Vì vậy, trước tiên chúng ta cần đính kèm các thực thể bị ngắt kết nối với một thể hiện của DbContext với trạng thái EntityState là Modified.

Bảng sau liệt kê các phương thức của DbContextDbSet để cập nhật các thực thể:

Phương thức DbContext Phương thức DbSet Mô tả
DbContext.Update DbSet.Update Đính kèm một thực thể vào DbContext với trạng thái Modified.
DbContext.UpdateRange DbSet.UpdateRange Đính kèm một tập các thực thể vào DbContext với trạng thái Modified.

Ví dụ sau đây cho thấy việc cập nhật một thực thể bị ngắt kết nối.


// Disconnected Student entity
var stud = new Student(){ StudentId = 1, Name = "Bill" };

stud.Name = "Steve"; 

using (var context = new SchoolContext())
{
    context.Update<Student>(stud);

    // or the followings are also valid
    // context.Students.Update(stud);
    // context.Attach<Student>(stud).State = EntityState.Modified;
    // context.Entry<Student>(stud).State = EntityState.Modified; 

    context.SaveChanges(); 
}

Trong ví dụ trên, stud là một đối tượng đã tồn tại của thực thể Student bởi vì nó có giá trị thuộc tính khóa hợp lệ (StudentId = 1).

Entity Framework Core đã giới thiệu phương thức DbContext.Update(), nó gắn thực thể được chỉ định vào một Context và đặt trạng thái EntityState của nó thành Modified.

Ngoài ra, bạn cũng có thể sử dụng phương thức DbSet.Update() (context.Students.Update(stud)) để làm điều tương tự.

Ví dụ trên thực thi câu lệnh UPDATE sau trong cơ sở dữ liệu.


exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Students] SET [Name] = @p0
WHERE [StudentId] = @p1;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 nvarchar(4000)',@p1=1,@p0=N'Steve'
go

Cập nhật nhiều thực thể

Sử dụng phương thức  DbContext.UpdateRangehoặc DbSet.UpdateRange để đính kèm một tập hợp hoặc mảng các thực thể vào DbContext và đặt trạng thái EntityState của chúng thành Modified.


var modifiedStudent1 = new Student()
{
    StudentId = 1,
    Name = "Bill"
};

var modifiedStudent2 = new Student()
{
    StudentId = 3,
    Name = "Steve"
};

var modifiedStudent3 = new Student()
{
    StudentId = 3,
    Name = "James"
};

IList<Student> modifiedStudents = new List<Student>()
{
    modifiedStudent1,
    modifiedStudent2,
    modifiedStudent3
};

using (var context = new SchoolContext())
{
    context.UpdateRange(modifiedStudents);
    
    // or the followings are also valid
    //context.UpdateRange(modifiedStudent1, modifiedStudent2, modifiedStudent3);
    //context.Students.UpdateRange(modifiedStudents);
    //context.Students.UpdateRange(modifiedStudent1, modifiedStudent2, modifiedStudent3);
                
    context.SaveChanges();
}

Như bạn có thể thấy, phương thức UpdateRange này có hai phương thức quá tải. Một phương thức quá tải yêu cầu một tập hợp các thực thể và phương thức quá tải thứ hai yêu cầu mảng object[] làm tham số.

Phương thức DbSet.UpdateRange làm việc theo cách tương tự như phương thức DbContext.UpdateRange.

EF Core cải thiện hiệu suất bằng cách xây dựng câu lệnh UPDATE cho tất cả các thực thể trong ví dụ trên và thực hiện nó trong một lần gọi cơ sở dữ liệu.


exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Students] SET [Name] = @p0
WHERE [StudentId] = @p1;
SELECT @@ROWCOUNT;

UPDATE [Students] SET [Name] = @p2
WHERE [StudentId] = @p3;
SELECT @@ROWCOUNT;

UPDATE [Students] SET [Name] = @p4
WHERE [StudentId] = @p5;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 nvarchar(4000),@p3 int,@p2 nvarchar(4000),@p5 int,@p4 nvarchar(4000)',
@p1=1,@p0=N'Bill',@p3=2,@p2=N'Steve',@p5=3,@p4=N'James'
go

Thay đổi EntityState

Phương thức Update thiết lập trạng thái EntityState của thực thể dựa trên giá trị của thuộc tính khóa.

Nếu thuộc tính khóa của thực thể gốc hoặc thực thể con có giá trị trống, null hoặc giá trị mặc định của kiểu dữ liệu đã chỉ định thì phương thức Update() sẽ coi đó là thực thể mới và đặt trạng thái EntityState của nó thành Added.


public static void Main()
{
    var newStudent = new Student()
    {
        Name = "Bill"
    };

    var modifiedStudent = new Student()
    {
        StudentId = 1,
        Name = "Steve"
    };

    using (var context = new SchoolContext())
    {
        context.Update<Student>(newStudent);
        context.Update<Student>(modifiedStudent);

        DisplayStates(context.ChangeTracker.Entries());
    }
}

private static void DisplayStates(IEnumerable<EntityEntry> entries)
{
    foreach (var entry in entries)
    {
        Console.WriteLine($"Entity: {entry.Entity.GetType().Name},
                 State: {entry.State.ToString()} ");
    }
}

Đây là kết quả khi biên dịch và thực thi chương trình trên:

Entity: Student, State: Added
Entity: Student, State: Modified

Trong ví dụ trên, newStudent không có giá trị thuộc tính khóa (StudentId). Vì vậy, phương thức Update() sẽ đánh dấu nó là Added, trong khi modifiedStudent có giá trị thuộc tính khóa (StudentId = 1), vì vậy nó sẽ được đánh dấu là Modified.

Ngoại lệ

Các phương thức UpdateUpdateRange ném ra một ngoại lệ InvalidOperationException trong trường hợp một thể hiện của DbContext đã theo dõi một thực thể có cùng giá trị thuộc tính khóa. Hãy xem ví dụ sau:


var student = new Student()
{
    StudentId = 1,
    Name = "Steve"
};

using (var context = new SchoolContext())
{
    // loads entity in a conext whose StudentId is 1
    context.Students.First<Student>(s => s.StudentId == 1); 

    // throws an exception as it already tracking entity with StudentId=1
    context.Update<Student>(student); 

    context.SaveChanges();
}

Trong ví dụ trên, một đối tượng context truy xuất một thực thể StudentStudentId = 1 từ cơ sở dữ liệu và bắt đầu theo dõi nó. Vì vậy, khi đính kèm một thực thể có cùng giá trị khóa sẽ đưa ra ngoại lệ sau:

The instance of entity type 'Student' cannot be tracked because another instance with the same key value for {'StudentId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

Xóa dữ liệu

EF Core API xây dựng và thực thi câu lệnh DELETE trong cơ sở dữ liệu cho các thực thể có trạng thái EntityState là Deleted.

Không có sự khác biệt trong việc xóa một thực thể trong kịch bản được kết nối và ngắt kết nối trong EF Core.

EF Core giúp dễ dàng xóa một thực thể khỏi Context, từ đó sẽ xóa một bản ghi trong cơ sở dữ liệu bằng các phương thức sau.

Phương thức DbContext Phương thức DbSet Mô tả
DbContext.Remove DbSet.Remove Đính kèm thực thể được chỉ định vào DbContext với trạng thái Deleted và bắt đầu theo dõi nó.
DbContext.RemoveRange DbSet.RemoveRange Đính kèm một tập hợp hoặc một mảng thực thể được chỉ định vào DbContext với trạng thái Deleted và bắt đầu theo dõi chúng.

Ví dụ sau đây cho thấy các cách khác nhau để xóa một thực thể trong kịch bản ngắt kết nối.


// entity to be deleted
var student = new Student() 
{
    StudentId = 1
};

using (var context = new SchoolContext()) 
{
    context.Remove<Student>(student);
   
    // or the followings are also valid
    // context.RemoveRange(student);
    //context.Students.Remove(student);
    //context.Students.RemoveRange(student);
    //context.Attach<Student>(student).State = EntityState.Deleted;
    //context.Entry<Student>(student).State = EntityState.Deleted;
    
    context.SaveChanges();
}

Trong ví dụ trên, một thực thể Student với thuộc tính StudentId có giá trị hợp lệ được xóa khỏi Context bằng cách sử dụng phương thức Remove() hoặc RemoveRange(). Dữ liệu sẽ bị xóa khỏi cơ sở dữ liệu khi gọi phương thức SaveChanges(). Ví dụ trên thực thi lệnh DELETE sau trong cơ sở dữ liệu:


exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [Students]
WHERE [StudentId] = @p0;
SELECT @@ROWCOUNT;
',N'@p0 int',@p0=1
go
Lưu ý: Các phương thức DbContext.Remove()DbContext.RemoveRange() mới được giới thiệu trong EF Core để giúp thao tác xóa dễ dàng.

Ngoại lệ

Nếu giá trị khóa trong thực thể được chỉ định trong phương thức Remove() hoặc RemoveRange() không tồn tại trong bảng cơ sở dữ liệu tương ứng, thì EF Core sẽ đưa ra một ngoại lệ: Ví dụ sau sẽ đưa ra một ngoại lệ.


var student = new Student() 
{
    StudentId = 50
};

using (var context = new SchoolContext()) 
{

    context.Remove<Student>(student);

    context.SaveChanges();
}

Trong ví dụ trên, một đối tượng của thực thể Student với thuộc tính khóa StudentId = 50 không tồn tại trong cơ sở dữ liệu. Vì vậy, EF Core sẽ ném ra ngoại lệ DbUpdateConcurrencyException như sau:

Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.

Vì vậy, bạn cần xử lý ngoại lệ trên một cách thích hợp hoặc đảm bảo rằng dữ liệu tương ứng với id tồn tại trong cơ sở dữ liệu trước khi xóa nó.


var student = new Student() 
{
    StudentId = 50
};

using (var context = new SchoolContext()) 
{
    try
    {
        context.Remove<Student>(deleteStudent);
        context.SaveChanges();
    }    
    catch (DbUpdateConcurrencyException ex)
    {
        throw new Exception("Record does not exist in the database");
    }
    catch (Exception ex)
    {
        throw;
    }
}

Xóa nhiều thực thể

Bạn có thể xóa nhiều thực thể trong một lần bằng cách sử dụng phương thức DbContext.RemoveRange() hoặc DbSet.RemoveRange().


IList<Student> students = new List<Student>() 
{
    new Student(){ StudentId = 1 },
    new Student(){ StudentId = 2 },
    new Student(){ StudentId = 3 },
    new Student(){ StudentId = 4 }
};

using (var context = new SchoolContext()) 
{
    context.RemoveRange(students);
    
    // or
    // context.Students.RemoveRange(students);
    
    context.SaveChanges();
}

Ví dụ trên sẽ xóa 4 bản ghi khỏi cơ sở dữ liệu trong một lần gọi cơ sở dữ liệu. Do đó EF Core đã giúp cải thiện hiệu suất.


exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [Students]
WHERE [StudentId] = @p0;
SELECT @@ROWCOUNT;


DELETE FROM [Students]
WHERE [StudentId] = @p1;
SELECT @@ROWCOUNT;

DELETE FROM [Students]
WHERE [StudentId] = @p2;
SELECT @@ROWCOUNT;


DELETE FROM [Students]
WHERE [StudentId] = @p3;
SELECT @@ROWCOUNT;

',N'@p0 int,@p1 int',@p0=1,@p1=2,@p2=3,@p3=4
go

Xóa thực thể liên quan

Nếu một thực thể có mối quan hệ với các thực thể khác, chẳng hạn như một-một hoặc một-nhiều thì việc xóa dữ liệu liên quan khi thực thể gốc bị xóa tùy thuộc vào cách cấu hình mối quan hệ.

Ví dụ, hãy xem xét rằng các thực thể StudentGrade có mối quan hệ một-nhiều.

EF sẽ đưa ra lỗi toàn vẹn dữ liệu nếu chúng ta cố gắng xóa một Grade có các bản ghi Student liên quan trong cơ sở dữ liệu.

Để giải quyết vấn đề này, bạn có thể định nghĩa các tùy chọn hành động ràng buộc tham chiếu bằng Fluent API. Ví dụ: bạn có thể cấu hình tùy chọn cascade delete cho mối quan hệ, như ví dụ bên dưới.


modelBuilder.Entity<Student>()
    .HasOne<Grade>(s => s.Grade)
    .WithMany(g => g.Students)
    .HasForeignKey(s => s.GradeId)
    .OnDelete(DeleteBehavior.Cascade);

Bây giờ, nếu bạn xóa thực thể Grade, thì tất cả các bản ghi Student liên quan cũng sẽ bị xóa trong cơ sở dữ liệu.

Có những lựa chọn hành động hạn chế tham chiếu khác có sẵn trong EF Core, chẳng hạn như SetNull, ClientSetNull, và Restrict.

Để tìm hiểu thêm về xóa thực thể liên quan trong EF Core, bạn có thể tham khảo thêm phần "cấu hình Cascade Delete bằng Fluent API" trong hướng dẫn Cấu hình trong Entity Framework Core.

Cấu hình trong Entity Framework Core | Comdy
Hướng dẫn Cấu hình trong Entity Framework Core sử dụng attribute chú thích dữ liệu và Fluent API.


Bài viết liên quan:

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.

Entity Framework Core toàn tập sẽ hướng dẫn bạn tất cả mọi thứ về Entity Framework Core.