Lớp DbSet<TEntity>
đại diện cho một tập hợp các thực thể trong mô hình và là nơi thực hiện các thao tác cơ sở dữ liệu cho từng thực thể. Lớp DbSet<TEntity>
được thêm dưới dạng thuộc tính vào DbContext
và được ánh xạ theo mặc định tới các bảng cơ sở dữ liệu lấy tên của thuộc tính DbSet<TEntity>
. DbSet
là một triển khai của mẫu Repository.
public class SampleContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
}
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Book
{ public int BookId { get; set; }
public string Title { get; set; }
public Author Author { get; set; }
public int AuthorId { get; set; }
}
Trong ví dụ trên, hai thuộc tính DbSet<TEntity>
đã được thêm vào lớp DbContext
. Thuộc tính DbSet
đầu tiên đại diện cho một tập hợp các đối tượng Book
, được ánh xạ theo quy ước vào một bảng cơ sở dữ liệu có tên “Books”. Thuộc tính DbSet
thứ hai đại diện cho một tập hợp các đối tượng Author
và được ánh xạ tới một bảng có tên “Authors” trong cơ sở dữ liệu.
Các thao tác cơ bản của DbSet
Các đối tượng DbSet
đại diện cho tập hợp các thực thể trong bộ nhớ. Bất kỳ thay đổi nào bạn thực hiện đối với nội dung của một DbSet
sẽ chỉ được lưu vào cơ sở dữ liệu nếu phương thức SaveChanges
của DbContext
được gọi.
Lớp DbSet
có một số phương thức cho phép bạn thực hiện các thao tác CRUD (Create, Read, Update, Delete) cơ bản trên các thực thể.
Thêm một thực thể mới
Để thêm một thực thể mới vào tập hợp được đại diện bởi DbSet
, bạn sử dụng phương thức DbSet.Add
:
var author = new Author
{
FirstName = "William",
LastName = "Shakespeare"
};
using (var context = new SampleContext())
{
context.Authors.Add(author); // adds the author to the DbSet in memory
context.SaveChanges(); // commits the changes to the database
}
Truy xuất một thực thể
Nếu bạn muốn truy xuất một thực thể duy nhất, bạn có thể sử dụng phương thức First
hoặc Single
tùy thuộc vào việc bạn có mong đợi có nhiều hơn một hàng phù hợp với điều kiện hay không.
Phương thức Single
sẽ dẫn đến một ngoại lệ được ném ra nếu có nhiều dữ liệu phù hợp với điều kiện. Nó chỉ nên được sử dụng khi truy vấn context cho các thực thể bằng khóa chính:
using (var context = new SampleContext())
{
var author = context.Authors.Single(a => a.AuthorId == 1);
}
Nếu bạn không chắc chắn về việc truy xuất bất kỳ dữ liệu nào dựa trên tiêu chí bạn truyền vào, bạn nên sử dụng các phương thức FirstOrDefault
hoặc SingleOrDefault
, nó sẽ trả về null
trong trường hợp không có dữ liệu nào phù hợp với tiêu chí tìm kiếm:
using (var context = new SampleContext())
{
var author = context.Authors.FirstOrDefault(a => a.LastName == "Shakespeare");
}
Có sẵn một phương thức tiện lợi được gọi là phương thức Find
được sử dụng để truy vấn context cho một thực thể theo khóa chính:
using (var context = new SampleContext())
{
var author = context.Authors.Find(1);
}
Phương thức này là một trình bao bọc xung quanh phương thức SingleOrDefault
và kiểm tra xem liệu một thực thể có khóa được chỉ định hiện đang được theo dõi bởi context hay không và nếu có, nó sẽ được trả về.
Nếu không, một truy vấn cơ sở dữ liệu sẽ được thực thi và thực thể sẽ được trả về nếu được tìm thấy. Nếu nó vẫn không được tìm thấy, phương thức Find
sẽ trả về null
.
Các phương thức Single
, First
, SingleOrDefault
và FirstOrDefault
cho kết quả thực hiện ngay lập tức bằng một truy vấn vào cơ sở dữ liệu.
Phiên bản không đồng bộ: SingleAsync
, FirstAsync
, SingleOrDefaultAsync
, FirstOrDefaultAsync
và FindAsync
.
Truy xuất nhiều thực thể
Phương thức phổ biến nhất được sử dụng để trả về nhiều thực thể là phương thức ToList
:
using (var context = new SampleContext())
{
var author = context.Authors.ToList();
}
Sửa đổi một thực thể
Để cập nhật một thực thể, hãy thực hiện các thay đổi bắt buộc đối với giá trị của thuộc tính hoặc thuộc tính và gọi phương thức SaveChanges
. Thực thể phải được theo dõi bởi context để được cập nhật:
using(var context = new SampleContext())
{
var author = context.Authors.Find(1); // retrieve the entity
author.LastName = "Jones"; // amend properties
context.SaveChanges(); // commit the changes
}
Phiên bản không đồng bộ: SaveChangesAsync
.
Xóa một thực thể
Lớp DbSet
có phương thức Remove
được sử dụng để xóa một thực thể. Thực thể phải được theo dõi theo context để xóa:
using(var context = new SampleContext())
{
var author = context.Authors.Find(1);
context.Authors.Remove(author);
context.SaveChanges();
}
Cách tiếp cận này có thể dẫn đến hai truy vấn SQL được thực thi: một truy vấn được tạo bởi phương thức Single
để lấy thực thể nếu nó chưa được context theo dõi và một truy vấn được tạo bởi phương thức Remove
để xóa thực thể.
Thay vào đó, bạn có thể sử dụng một đối tượng sơ khai để ngăn chặn nhu cầu truy vấn SQL để truy xuất thực thể. Đối tượng sơ khai là đại diện của thực thể sẽ được sử dụng để thực thi. Tất cả những gì cần thiết cho một đối tượng sơ khai hợp lệ là khóa chính của thực thể.
using (var context = new SampleContext())
{
var author = new Author { AuthorId = 1 };
context.Authors.Attach(author);
context.Authors.Remove(author);
context.Savechanges();
}
Truy vấn dữ liệu bằng DbSet
Truy vấn dữ liệu trong Entity Framework Core được thực hiện dựa trên các thuộc tính DbSet
của DbContext. DbSet đại diện cho một tập hợp các thực thể của một kiểu cụ thể – kiểu được chỉ định bởi tham số kiểu.
Truy vấn được chỉ định bằng cách sử dụng Truy vấn tích hợp ngôn ngữ (LINQ), một thành phần trong .NET Framework cung cấp khả năng truy vấn các tập hợp trong C# hoặc VB.NET.
Các truy vấn LINQ có thể được viết bằng cú pháp truy vấn hoặc cú pháp phương thức. Cú pháp truy vấn tương tự với cú pháp SQL. Nhà cung cấp Entity Framework Core mà bạn sử dụng chịu trách nhiệm dịch truy vấn LINQ thành truy vấn SQL để thực thi dựa trên cơ sở dữ liệu.
Ví dụ sau sử dụng cú pháp truy vấn để truy xuất tất cả các tác giả được sắp xếp theo họ của họ:
var data = from a in Authors select a orderby a.LastName
Cú pháp phương thức sử dụng một chuỗi các phương thức mở rộng. Nhiều tên phương thức cũng giống SQL. Ví dụ tiếp theo cho thấy truy vấn trước đó được biểu thị bằng cú pháp phương thức:
var data = context.Authors.OrderBy(a => a.LastName);
Hướng dẫn này sử dụng cú pháp phương thức trong các ví dụ truy vấn.
Truy xuất một đối tượng duy nhất
Truy vấn trả về thực thể duy nhất được thực hiện sử dụng các biến thể của phương thức First
, FirstOrDefault
, Single
, SingleOrDefault
và Find
.
Ngoài ra, có các phiên bản không đồng bộ của từng loại ở trên là: SingleAsync
, FirstAsync
, SingleOrDefaultAsync
, FirstOrDefaultAsync
và FindAsync
.
Phương thức First và FirstOrDefault của DbSet
Các phương thức First
và FirstOrDefault
được sử dụng để trả về một kết quả từ nhiều kết quả phù hợp. Nếu bạn mong đợi ít nhất một bản ghi phù hợp với tiêu chí, bạn có thể sử dụng phương thức First
.
Nếu có khả năng không có bản ghi nào phù hợp với tiêu chí, hãy sử dụng phương thức FirstOrDefault
, phương thức này sẽ trả về null
, mặc định, trong trường hợp không tìm thấy bản ghi nào.
Cả hai phương thức này đều dẫn đến việc thực thi truy vấn ngay lập tức, có nghĩa là câu lệnh SQL được tạo và thực thi dựa trên cơ sở dữ liệu ngay sau khi gọi phương thức.
Kết quả của phương thức First
là truy vấn SELECT TOP(1)
lấy tất cả các cột của từ bảng được ánh xạ đến DbSet
:
var author = context.Authors.First();
Câu lệnh SQL kết quả:
SELECT TOP(1) [a].[AuthorId], [a].[FirstName], [a].[LastName]
FROM [Authors] AS [a]
Phương thức Single và SingleOrDefault của DbSet
Các phương thức Single
và SingleOrDefault
được sử dụng để trả về một bản ghi trong đó chỉ có một bản ghi phù hợp với các tiêu chí được chỉ định.
Phương thức Single
sẽ tạo ra một truy vấn SELECT TOP(2)
. Nếu truy vấn trả về nhiều hơn một kết quả, thì một ngoại lệ InvalidOperationException
được tạo với thông báo:
Chuỗi chứa nhiều hơn một phần tử.
Vì lý do này, bạn rất khó sử dụng phương thức Single
mà không chỉ định một số tiêu chí, thường là khóa chính hoặc giá trị unique index. Bạn có thể chỉ định tiêu chí dưới dạng biểu thức lambda trong phương thức Where
hoặc truyền trực tiếp tiêu chí đó vào phương thức Single
:
var author = context.Authors.Where(a => a.AuthorId == 1).Single();
var author = context.Authors.Single(a => a.AuthorId == 1);
Cả hai cách tiếp cận đều dẫn đến việc tạo ra câu lệnh SQL giống hệt nhau:
SELECT TOP(2) [a].[AuthorId], [a].[FirstName], [a].[LastName]
FROM [Authors] AS [a]
WHERE [a].[AuthorId] = 1
Nếu truy vấn không trả về kết quả phù hợp, bạn nên sử dụng phương thức SingleOrDefault
sẽ trả về null
trong trường hợp này.
Phương thức Find của DbSet
Phương thức DbSet.Find
quen thuộc với những người sử dụng phiên bản trước của Entity Framework có hỗ trợ các API DbSet
.
Phương thức này yêu cầu tham số là khóa thực thể thay vì sử dụng biểu thức lambda. Nó cung cấp tùy chọn ít dài dòng hơn để truy xuất các thực thể đơn lẻ bằng khóa của chúng:
var author = context.Authors.Find(1);
Phương thức Find
là một trình bao bọc xung quanh phương thức SingleOrDefault
và kiểm tra xem liệu một thực thể có khóa được chỉ định hiện đang được theo dõi bởi context hay không và nếu có, nó sẽ được trả về.
Nếu không, một truy vấn cơ sở dữ liệu sẽ được thực thi và thực thể sẽ được trả về nếu được tìm thấy. Nếu nó vẫn không được tìm thấy, phương thức Find
sẽ trả về null
.
Thêm thực thể mới bằng DbSet
Các phương thức chính để thêm các thực thể bằng DbSet
là
Add<TEntity>(TEntity entity)
AddRange<TEntity>(IEnumerable<TEntity> entities)
AddRange<TEntity>(params TEntity[] entities)
Thông thường, bạn sẽ thấy các ví dụ về phiên bản generic của phương thức Add
nhưng với tham số kiểu dữ liệu bị bỏ qua vì trình biên dịch sẽ suy ra nó. Hai ví dụ sau đây giống hệt nhau:
// with type parameter
var author = new Author
{
FirstName = "William",
LastName = "Shakespeare"
};
context.Authors.Add<Author>(author);
// without type parameter
var author = new Author
{
FirstName = "William",
LastName = "Shakespeare"
};
context.Authors.Add(author);
Context bắt đầu theo dõi thực thể đã được truyền vào phương thức Add
và thiết lập giá trị cho thuộc tính EntityState
của nó là Added
.
Context cũng thiết lập giá trị Added
cho thuộc tính EntityState
cho tất cả các đối tượng khác trong biểu đồ đối tượng chưa được context theo dõi.
Trong ví dụ tiếp theo, trạng thái Added
cũng được áp dụng cho các cuốn sách:
var context = new SampleContext();
var author = new Author
{
FirstName = "William",
LastName = "Shakespeare",
Books = new List<Book>
{
new Book { Title = "Hamlet"},
new Book { Title = "Othello" },
new Book { Title = "MacBeth" }
}
};
context.Authors.Add(author);
Các cuốn sách được bổ sung bởi thực tế là chúng được tham chiếu thông qua thuộc tính Books
của tác giả. Trong ví dụ tiếp theo, các cuốn sách sẽ không được thêm vào:
var author = new Author
{
FirstName = "William",
LastName = "Shakespeare"
};
var hamlet = new Book
{
Title = "Hamlet",
Author = author
};
var othello = new Book
{
Title = "Othello",
Author = author
};
var macbeth = new Book
{
Title = "MacBeth",
Author = author
};
context.Authors.Add(author);
context.SaveChanges();
Mặc dù tác giả đã được gán cho thuộc tính Author
của mỗi cuốn sách đã được khởi tạo, thực thể author
không biết về các mối quan hệ này. Đó là vì thuộc tính Books
vẫn còn null
và những cuốn sách không được thêm vào context.
Thêm nhiều thực thể mới bằng DbSet
Phương thức AddRange
được sử dụng để thêm nhiều thực thể vào cơ sở dữ liệu trong một lời gọi phương thức. Mã trong ví dụ tiếp theo rất giống với ví dụ trước, nhưng phương thức AddRange
được sử dụng để lưu tất cả sách và tác giả vào cơ sở dữ liệu trong một lần:
var context = new SampleContext();
var author = new Author
{
FirstName = "Stephen",
LastName = "King"
};
var books = new List<Book>
{
new Book { Title = "It", Author = author },
new Book { Title = "Carrie", Author = author },
new Book { Title = "Misery", Author = author }
};
context.Books.AddRange(books);
context.SaveChanges();
Phương thức AddRange
có tham số kiểu IEnumerable<object>
. Tác giả là một phần của biểu đồ đối tượng cho ít nhất một trong những cuốn sách, vì vậy nó cũng được thêm vào.
Phiên bản khác của phương thức AddRange
có tham số là một mảng tham số của các thực thể. Nó cho phép bạn truyền một số lượng các biến để thêm các thực thể vào cơ sở dữ liệu mà không cần tạo một danh sách cho chúng:
var context = new SampleContext();
var author = new Author
{
FirstName = "William",
LastName = "Shakespeare"
};
var hamlet = new Book
{
Title = "Hamlet",
Author = author
};
var othello = new Book
{
Title = "Othello",
Author = author
};
var macbeth = new Book
{
Title = "MacBeth",
Author = author
};
context.Books.AddRange(hamlet, othello, macbeth);
context.SaveChanges();
Khi phương thức SaveChanges
được gọi là trên DbContext
, tất cả các thực thể với trạng thái EntityState
là Added
sẽ được chèn vào cơ sở dữ liệu.
Thứ tự của câu lệnh SQL là chèn các đối tượng được quản lý trước để đảm bảo rằng các giá trị khóa chính của chúng sẽ có sẵn để áp dụng cho khóa ngoại của các đối tượng phụ thuộc.
Sửa đổi thực thể bằng DbSet
Cách tiếp cận mà bạn áp dụng để sửa đổi các thực thể thông qua DbSet
tùy thuộc vào việc liệu context có đang theo dõi các thực thể được sửa đổi hay không.
Trong ví dụ sau, thực thể được lấy bởi context, vì vậy context bắt đầu theo dõi nó ngay lập tức. Khi bạn thay đổi các giá trị thuộc tính trên thực thể được theo dõi, context sẽ thay đổi giá trị của EntityState
thành Modified
và ChangeTracker ghi lại giá trị thuộc tính cũ và giá trị thuộc tính mới.
Khi phương thức SaveChanges
được gọi, một câu lệnh UPDATE
được tạo và thực thi bởi cơ sở dữ liệu.
var author = context.Authors.Find(1);
author.FirstName = "Bill";
context.SaveChanges();
Vì ChangeTracker theo dõi những thuộc tính nào đã được sửa đổi, context sẽ đưa ra một câu lệnh SQL chỉ cập nhật những thuộc tính đã được thay đổi:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Authors] SET [FirstName] = @p0
WHERE [AuthorId] = @p1;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 nvarchar(4000)',@p1=1,@p0=N'Bill'
Trường hợp ngắt kết nối
Trong trường hợp bị ngắt kết nối chẳng hạn như ứng dụng ASP.NET, các thay đổi đối với các giá trị thuộc tính của thực thể hiện tại có thể diễn ra trong controller hoặc service, không thuộc context.
Trong những trường hợp này, context cần được thông báo rằng thực thể đang ở trạng thái đã được sửa đổi. Điều này có thể đạt được bằng cách sử dụng phương thức DbSet<T>.Update
(tính năng mới trong Entity Framework Core).
Phương thức Update của DbSet
Lớp DbSet<T>
cung cấp phương thức Update
và UpdateRange
để làm việc với các thực thể.
public void Save(Author author)
{
context.Authors.Update(author);
context.SaveChanges();
}
Phương thức này dẫn đến thực thể được theo dõi bởi context và có trạng thái là Modified
. Context không có bất kỳ cách nào để xác định giá trị thuộc tính nào đã được thay đổi và sẽ tạo câu lệnh SQL để cập nhật tất cả các thuộc tính của thực thể.
Bất kỳ thực thể nào có liên quan (chẳng hạn như một danh sách sách trong ví dụ này) cũng sẽ được theo dõi ở trạng thái Modified
, dẫn đến các câu lệnh UPDATE
được tạo cho chúng. Nếu thực thể có liên quan không được gán giá trị khóa, nó sẽ được đánh dấu là Added
và một câu lệnh INSERT
sẽ được tạo thay thế.
Xóa thực thể bằng DbSet
Phương thức mà bạn áp dụng để xóa thực thể thông qua DbSet
tùy thuộc vào việc context hiện đang theo dõi thực thể bị xóa hay không.
Trong ví dụ sau, thực thể cần xóa được lấy bởi context, vì vậy context bắt đầu theo dõi nó ngay lập tức. Kết quả của phương thức DbSet<T>.Remove
là trạng thái EntityState
của thực thể được thiết lập thành Deleted
.
context.Authors.Remove(context.Authors.Find(1));
context.SaveChanges();
Khi phương thức SaveChanges
được gọi, một câu lệnh DELETE
được tạo và thực thi bởi cơ sở dữ liệu.
exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [Authors]
WHERE [AuthorId] = @p0;
SELECT @@ROWCOUNT;
',N'@p0 int',@p0=1
Cách tiếp cận này thực sự dẫn đến hai câu lệnh SQL được thực thi: một câu lệnh để truy xuất thực thể từ cơ sở dữ liệu và câu lệnh thứ hai để xóa nó. Bạn có thể sử dụng một đối tượng sơ khai để đại diện cho đối tượng sẽ bị xóa và do đó ngăn đối tượng được truy xuất từ cơ sở dữ liệu:
var context = new SampleContext();
var author = new Author { AuthorId = 1 };
context.Authors.Remove(author);
context.SaveChanges();
Thuộc tính duy nhất mà đối tượng sơ khai yêu cầu là giá trị khóa chính.
Dữ liệu liên quan
Nếu thực thể mà bạn muốn xóa có dữ liệu liên quan, thì cách tiếp cận mà bạn thực hiện sẽ phụ thuộc vào cách cấu hình mối quan hệ.
Một mối quan hệ được định nghĩa đầy đủ sẽ có một ràng buộc tham chiếu xếp tầng được đặt thành Delete
hoặc SetNull
, cũng tương tự một mối quan hệ được cấu hình thông qua Fluent API. Trong những trường hợp này, bạn có thể xóa hàng chính và để cơ sở dữ liệu quản lý các hàng phụ thuộc.
Khi hành động ràng buộc tham chiếu được đặt thành NoAction
, bạn cần quan tâm đến mọi dữ liệu liên quan một cách rõ ràng. Ví dụ tiếp theo minh họa một mối quan hệ được cấu hình trên một mô hình không bao gồm thuộc tính khóa ngoại:
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
}
Theo mặc định, mối quan hệ này được cấu hình là tùy chọn và tùy chọn hành động ràng buộc tham chiếu được cấu hình NoAction
.
Ngoài ra, Entity Framework Core giới thiệu một thuộc tính shadow để đại diện cho khóa ngoại. Nó được đặt tên là AuthorId
và được áp dụng cho thực thể Book
, và vì mối quan hệ là tùy chọn nên thuộc tính AuthorId
là nullable.
Để xóa tác giả, bạn cần xóa mối quan hệ giữa từng cuốn sách và tác giả. Mã để đạt được điều này như sau:
var context = new SampleContext();
var author = context.Authors.Find(1);
var books = context.Books.Where(b => EF.Property<int>(b, "AuthorId") == 1);
foreach (var book in books)
{
author.Books.Remove(book);
}
context.Authors.Remove(author);
context.SaveChanges();
Tác giả của nó được truy xuất từ cơ sở dữ liệu và sau đó sách của nó được lấy và xóa từng cuốn một khỏi bộ sưu tập Book
của tác giả.
Cuối cùng, tác giả được truyền vào phương thức Remove
của context. Kết quả là sách được cập nhật để giá trị AuthorId
của chúng được sửa đổi thành null
:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Books] SET [AuthorId] = @p0
WHERE [BookId] = @p1;
SELECT @@ROWCOUNT;
UPDATE [Books] SET [AuthorId] = @p2
WHERE [BookId] = @p3;
SELECT @@ROWCOUNT;
UPDATE [Books] SET [AuthorId] = @p4
WHERE [BookId] = @p5;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 int,@p3 int,@p2 int,@p5 int,@p4 int',@p1=1,@p0=NULL,@p3=2,@p2=NULL,@p5=3,@p4=NULL
và tác giả đã bị xóa:
exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [Authors]
WHERE [AuthorId] = @p6;
SELECT @@ROWCOUNT;
',N'@p6 int',@p6=1
Cách tiếp cận này dẫn đến bốn lệnh gọi đến cơ sở dữ liệu: một lệnh để lấy tác giả; một để lấy sách của tác giả, một để cập nhật sách và cuối cùng để xóa tác giả.
Do đó, bạn nên sử dụng các ràng buộc toàn vẹn tham chiếu để đặt các khóa ngoại thành null hoặc xóa các thành phần phụ thuộc.