DbContext
là cách đơn giản để các nhà phát triển làm việc với dữ liệu dựa trên Entity Framework Core.
Nó cho phép bạn tạo kết nối cơ sở dữ liệu bên trong một mô hình ứng dụng và cho phép nhà phát triển liên kết các thuộc tính của mô hình với bảng cơ sở dữ liệu bằng cách sử dụng một chuỗi kết nối.
Nó là lớp cơ sở để quản lý tất cả các loại hoạt động của cơ sở dữ liệu, chẳng hạn như thiết lập kết nối với cơ sở dữ liệu, truy vấn cơ sở dữ liệu và kết thúc kết nối.
Trong phiên bản cũ hơn của Entity Framework, có lớp ObjectContext
, nhưng bây giờ trong phiên bản mới nhất, chúng ta có lớp DbContext
.
DbContext
trong Entity Framework Core bao gồm các tính năng và vai trò sau đây:
Tất cả các tính năng và vai trò này là để thực hiện một tác vụ chuyên dụng trên Entity Framework Core.
DbContext
trong Entity Framework Core cho phép người dùng quản lý cơ sở dữ liệu hoàn chỉnh. Nó cho phép người dùng tạo, xóa hoặc kiểm tra các kết nối cơ sở dữ liệu hiện có bên trong dự án nhất định.
DbContext
cũng cho phép người dùng kiểm tra, thiết lập và đóng các kết nối giữa các cơ sở dữ liệu theo yêu cầu của dự án.
DbContext
đại diện cho tất cả các thực thể trong một dự án, nó cũng quản lý các thực thể này trong suốt thời gian tồn tại của chúng. Nó cũng áp dụng các hoạt động CRUD trên tất cả các thực thể, chẳng hạn như Add
, Attach
hoặc Remove
.
DbContext
cũng chuyển đổi các truy vấn LINQ thành các truy vấn SQL bằng cách sử dụng phương thức truy vấn.
DbContext
trong Entity Framework Core bao gồm Change Tracker API theo dõi tất cả các thay đổi khi các thực thể được thêm, cập nhật hoặc xóa.
Trạng thái của thực thể có thể được thay đổi thủ công hoặc tự động tùy thuộc vào người dùng.
Khi DbContext
thực hiện tất cả các hoạt động liên quan đến CRUD, nó lưu trữ tất cả các thay đổi được thực hiện đối với các thực thể vào cơ sở dữ liệu.
DbContext
lưu giữ tất cả các thay đổi được thực hiện đối với các thực thể trong suốt thời gian tồn tại của chúng dưới dạng tệp bộ nhớ cache cấp một.
DbContext
cũng thực hiện các hoạt động liên kết mô hình trong đó nó tự động đọc các lớp và cấu hình dựa trên mã để xây dựng mô hình trong bộ nhớ, siêu dữ liệu và cơ sở dữ liệu.
Trong quá trình thực hiện, DbContext
chuyển đổi các truy vấn từ bảng cơ sở dữ liệu thành các thực thể.
DbContext
cấu hình hành vi của context của cơ sở dữ liệu.
DbContext
kiểm tra tính hợp lệ của dữ liệu và thực hiện xác thực tự động của dữ liệu.
Truy vấn DbContext
Sử dụng DbContext
trong Entity Framework Core, có ba loại hoạt động truy vấn có thể được thực hiện, đó là:
Các phương thức chính để thêm các thực thể thông qua DbContext
là
Add<TEntity>(TEntity entity)
Add(object entity)
AddRange(IEnumerable<object> entities)
AddRange(params object[] entities)
Các phương thức này mới được thêm vào DbContext
của Entity Framework Core và không có trong phiên bản cũ của Entity Framework nếu có sẵn DbContext
(tức là Entity Framework 4.1 trở đi).
Thông thường, bạn sẽ sử dụng phiên bản generic của phương thức Add
nhưng bỏ qua tham số kiểu vì trình biên dịch sẽ suy ra kiểu từ đối số được truyền vào phương thức. Hai ví dụ sau đây giống hệt nhau:
// with type parameter
var author = new Author
{
FirstName = "William",
LastName = "Shakespeare"
};
context.Add<Author>(author);
context.SaveChanges();
// without type parameter
var author = new Author
{
FirstName = "William",
LastName = "Shakespeare"
};
context.Add(author);
context.SaveChanges();
Visual Studio 2015 đưa ra lời khuyên hữu ích để bỏ qua tham số kiểu trong ví dụ đầu tiên. Ví dụ thứ hai không nên nhầm lẫn với phiên bản sử dụng tham số kiểu object
của Add
:
object author = new Author
{
FirstName = "William",
LastName = "Shakespeare"
};
context.Add(author);
context.SaveChanges();
Khi bạn sử dụng một trong hai phiên bản của phương thức Add
, context bắt đầu theo dõi thực thể đã được chuyển vào phương thức và thiết lập giá trị cho trạng thái EntityState
là Added
.
Context cũng thiết lập giá trị cho trạng thái EntityState
là Added
cho tất cả các đối tượng khác trong biểu đồ 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.Add(author);
context.SaveChanges();
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 thực thể tác giả. Trong ví dụ tiếp theo, 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.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à 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.
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.AddRange(books);
context.SaveChanges();
Phiên bản của phương thức AddRange
này có tham số IEnumerable<object>
. Entity Framework Core đủ thông minh để xác định kiểu đối tượng được thêm vào context và sẽ tạo thành câu lệnh SQL thích hợp. Tác giả có liên quan đến tất cả các cuốn sách, vì vậy nó tạo thành một phần của biểu đồ và cũng được thêm vào.
Phiên bản khác của phương thức AddRange
này lấy một mảng tham số và cung cấp cơ sở để thêm một số đối tượng không liên quan vào cơ sở dữ liệu trong một lần:
var context = new SampleContext();
var author = new Author
{
FirstName = "William",
LastName = "Shakespeare"
};
var book = new Book
{
Title = "Adventures of Huckleberry Finn"
};
context.AddRange(author, book);
context.SaveChanges();
Khi phương thức SaveChanges
được gọi 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.
Cách tiếp cận mà bạn áp dụng để sửa đổi đối tượng phụ thuộc vào việc liệu context hiện đang theo dõi đối tượng có đượ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 EntityState
của thực thể 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 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.First(a => a.AuthorId == 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'
Trong một tình huống 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 nằm trong 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 một số cách: thiết lập EntityState
rõ ràng cho thực thể; sử dụng phương thức DbContext.Update
(mới có trong Entity Framework Core); sử dụng phương thức DbContext.Attach
.
Bạn có thể thiết lập EntityState
của thực thể thông qua thuộc tính EntityEntry.State
, được tạo sẵn bởi phương thức DbContext.Entry
.
public void Save(Author author)
{
context.Entry(author).State = EntityState.Modified;
context.SaveChanges();
}
Cách tiếp cận này sẽ dẫn đến chỉ thực thể author
được chỉ định trạng thái Modified
. Mọi đối tượng liên quan sẽ không được theo dõi. Vì ChangeTracker không biết thuộc tính nào đã được sửa đổi, context sẽ đưa ra một câu lệnh SQL cập nhật tất cả các giá trị thuộc tính (ngoài giá trị khóa chính).
Lớp DbContext
cung cấp phương thức Update
và UpdateRange
để làm việc với một hoặc nhiều thực thể.
public void Save(Author author)
{
context.Update(author);
context.SaveChanges();
}
Giống như việc thiết lập trạng thái State
của thực thể, phương thức này dẫn đến trạng thái của đối tượng được theo dõi bởi context là Modified
.
Một lần nữa, 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 ra câu lệnh SQL để cập nhật tất cả các thuộc tính.
Phương thức này khác với việc thiết lập thuộc tính State
một cách rõ ràng, thực tế là context sẽ bắt đầu theo dõi 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) trong trạng thái Modified
, dẫn đến các câu lệnh UPDATE
được tạo cho từng thực thể trong số 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 trạng thái là Added
và một câu lệnh INSERT
sẽ được tạo.
Khi bạn sử dụng phương thức Attach
trên một thực thể, trạng thái của nó sẽ được thiết lập thành Unchanged
, điều này sẽ dẫn đến không có lệnh cơ sở dữ liệu nào được tạo ra.
Tất cả các thực thể có thể truy cập với các giá trị khóa chính được xác định cũng sẽ được đặt thành Unchanged
. Những thực thể không có giá trị khóa chính sẽ được đánh dấu là Added
.
Tuy nhiên, bây giờ thực thể đang được theo dõi bởi context, bạn có thể thông báo cho context những thuộc tính nào đã được sửa đổi để tạo ra SQL chính xác để cập nhật chỉ những giá trị đó:
var context = new TestContext();
var author = new Author
{
AuthorId = 1,
FirstName = "William",
LastName = "Shakespeare"
};
author.Books.Add(new Book
{
BookId = 1,
Title = "Othello"
});
context.Attach(author);
context.Entry(author).Property("FirstName").IsModified = true;
context.SaveChanges();
Đoạn mã trên sẽ dẫn đến việc thực thể author được đánh dấu là Modified
và câu lệnh SQL được tạo để chỉ cập nhật thuộc tính FirstName
:
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'William'
TrackGraph
API cung cấp quyền truy cập vào các thực thể trong một đồ thị đối tượng, và cho phép bạn thực thi mã tùy chỉnh cho từng thực thể. Điều này hữu ích trong các tình huống mà bạn đang xử lý các biểu đồ đối tượng phức tạp bao gồm các thực thể liên quan khác nhau ở các trạng thái khác nhau.
Ví dụ sau đây sao chép một kịch bản trong đó một biểu đồ đối tượng được xây dựng bên ngoài context. Sau đó, phương thức TrackGraph được sử dụng để "duyệt qua biểu đồ":
var author = new Author
{
AuthorId = 1,
FirstName = "William",
LastName = "Shakespeare"
};
author.Books.Add(new Book
{
AuthorId = 1,
BookId = 1,
Title = "Hamlet",
Isbn = "1234"
});
author.Books.Add(new Book
{
AuthorId = 1,
BookId = 2,
Title = "Othello",
Isbn = "4321"
});
author.Books.Add(new Book
{
AuthorId = 1,
BookId = 3,
Title = "MacBeth",
Isbn = "5678"
});
var context = new TestContext();
context.ChangeTracker.TrackGraph(author, e =>
{
if((e.Entry.Entity as Author) != null)
{
e.Entry.State = EntityState.Unchanged;
}
else
{
e.Entry.State = EntityState.Modified;
}
});
context.SaveChanges();
Trong trường hợp này, giả định rằng thực thể author không bị thay đổi, nhưng sách có thể đã được chỉnh sửa. Phương thức TrackGraph
lấy thực thể gốc như một đối số và một biểu thức lambda xác định hành động để thực hiện.
Trong trường hợp này, thực thể gốc author đã được thiết lập EntityState
thành UnChanged
. Thiết lập EntityState
là bắt buộc đối với context để bắt đầu theo dõi thực thể.
Chỉ khi đó các thực thể liên quan mới có thể được phát hiện. Thiết lập trạng thái EntityState
của sách thành Modified
, như trong các ví dụ trước, sẽ dẫn đến câu lệnh SQL cập nhật mọi thuộc tính trên thực thể:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Books] SET [AuthorId] = @p0, [Isbn] = @p1, [Title] = @p2
WHERE [BookId] = @p3;
SELECT @@ROWCOUNT;
UPDATE [Books] SET [AuthorId] = @p4, [Isbn] = @p5, [Title] = @p6
WHERE [BookId] = @p7;
SELECT @@ROWCOUNT;
UPDATE [Books] SET [AuthorId] = @p8, [Isbn] = @p9, [Title] = @p10
WHERE [BookId] = @p11;
SELECT @@ROWCOUNT;
',N'@p3 int,@p0 int,@p1 nvarchar(4000),@p2 nvarchar(150),@p7 int,@p4 int,@p5 nvarchar(4000),
@p6 nvarchar(150),@p11 int,@p8 int,@p9 nvarchar(4000),@p10 nvarchar(150)',
@p3=1,@p0=1,@p1=N'1234',@p2=N'Hamlet',
@p7=2,@p4=1,@p5=N'4321',@p6=N'Othello',
@p113,@p8=1,@p9=N'5678',@p10=N'MacBeth'
Vì câu lệnh SQL cập nhật tất cả các thuộc tính, tất cả chúng cần phải có mặt và được gán giá trị hợp lệ, nếu không chúng sẽ được cập nhật thành giá trị mặc định của chúng.
Trong ví dụ tiếp theo, biểu đồ đối tượng một lần nữa được xây dựng bên ngoài context, nhưng chỉ thuộc tính Isbn
của sách được sửa đổi. Do đó, các thuộc tính khác (ngoài khóa chính của thực thể) bị bỏ qua:
var author = new Author
{
AuthorId = 1,
FirstName = "William",
LastName = "Shakespeare"
};
author.Books.Add(new Book
{
BookId = 1,
Isbn = "1234"
});
author.Books.Add(new Book
{
BookId = 2,
Isbn = "4321"
});
author.Books.Add(new Book
{
BookId = 3,
Isbn = "5678"
});
var context = new TestContext();
context.ChangeTracker.TrackGraph(author, e =>
{
e.Entry.State = EntityState.Unchanged; //starts tracking
if((e.Entry.Entity as Book) != null)
{
context.Entry(e.Entry.Entity as Book).Property("Isbn").IsModified = true;
}
});
Lần này, phần thân phương thức của biểu thức lambda đảm bảo rằng tất cả các thực thể được theo dõi ở trạng thái UnChanged
, và sau đó chỉ ra rằng thuộc tính Isbn
đã được sửa đổi. Điều này dẫn đến câu lệnh SQL được tạo chỉ cập nhật giá trị thuộc tính Isbn
:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Books] SET [Isbn] = @p0
WHERE [BookId] = @p1;
SELECT @@ROWCOUNT;
UPDATE [Books] SET [Isbn] = @p2
WHERE [BookId] = @p3;
SELECT @@ROWCOUNT;
UPDATE [Books] SET [Isbn] = @p4
WHERE [BookId] = @p5;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 nvarchar(4000),@p3 int,@p2 nvarchar(4000),@p5 int,@p4 nvarchar(4000)',
@p1=1,@p0=N'1234',
@p3=2,@p2=N'4321',
@p5=3,@p4=N'5678'
Phương pháp mà bạn áp dụng để xóa thực thể thông qua DbContext
tùy thuộc vào việc context hiện đang theo dõi đối tượng có 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. Phương thức DbContext.Remove
sẽ thiết lập trạng thái EntityState
của thực thể là Deleted
.
context.Remove(context.Authors.Single(a => a.AuthorId == 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.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.
Bạn có thể thiết lập rõ ràng trạng thái EntityState
của một thực thể thành Deleted
thông qua thuộc tính EntityEntry.State
, được cung cấp bởi phương thức DbContext.Entry
.
var context = new SampleContext();
var author = new Author { AuthorId = 1 };
context.Entry(author).State = EntityState.Deleted;
context.SaveChanges();
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 xác định đầy đủ sẽ có một ràng buộc tham chiếu xếp tầng được thiết lập thành Delete
hoặc SetNull
, cũng tương tự như 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 thực thể chính và để cơ sở dữ liệu quản lý các thực thể phụ thuộc.
Khi hành động ràng buộc tham chiếu được thiết lập 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 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 author, 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.FirstOrDefault(a => a.AuthorId == 1);
var books = context.Books.Where(b => EF.Property<int>(b, "AuthorId") == 1);
foreach (var book in books)
{
author.Books.Remove(book);
}
context.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 danh sách 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à một 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 để thiết lập các khóa ngoại thành null hoặc xóa các thành phần phụ thuộc.
Bạn có thể vui lòng tắt trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi duy trì hoạt động của trang web.
Bài viết này sẽ giúp bạn tìm hiểu về bộ chuyển đổi giá trị - một trong những cải tiến mạnh mẽ của Entity Framework Core 5.
Trong bài viết này, chúng ta sẽ khám phá cách làm việc với các tính năng của Entity Framework Core 5 (EF Core) để thực thi truy vấn SQL thô.
Với EF Core 5, chúng ta có thể thêm view vào trong DbContext và tạo view trong database bằng cách sử dụng cơ chế chuyển đổi cơ sở dữ liệu tích hợp sẵn.
Bài viết này sẽ khám phá các mối quan hệ trong cơ sở dữ liệu quan hệ và cách mô hình hóa các mối quan hệ đó bằng cách tiếp cận Code First trong EF Core.