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.
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.Add
và DbContext.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 EntityState
là Deleted
. 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:
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:
- Đính kèm một thực thể vào
DbContext
với trạng tháiEntityState
thích hợp, ví dụ: Added, Modified, hoặc Deleted. - 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;
và 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 DbContext
và DbSet
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ủaDbContext
vàDbSet
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 DbContext
và DbSet
để 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.UpdateRange
hoặ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 Update
và UpdateRange
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ể Student
có StudentId = 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()
và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ể Student
và Grade
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.