Object Relational Mappers (ORM) cho phép chúng ta định nghĩa mối quan hệ giữa các thực thể trong cơ sở dữ liệu quan hệ của chúng ta bằng cách sử dụng các đối tượng C#.
Tùy thuộc vào ORM mà chúng ta sử dụng, chúng ta cũng có thể lấy thư viện để tạo lược đồ của các mối quan hệ. Entity Framework Core là một trong những công cụ có mô hình di chuyển SQL tích hợp sẵn.
Bài viết này sẽ khám phá hầu hết các mối quan hệ được tìm thấy trong cơ sở dữ liệu quan hệ và cách chúng ta sẽ mô hình hóa các mối quan hệ đó bằng cách tiếp cận Code First.
Chúng ta sẽ sử dụng SQLite, một nhà cung cấp cơ sở dữ liệu dựa trên tệp cục bộ, nhưng nhiều kỹ thuật mô hình hóa này sẽ hoạt động trên hầu hết các cơ sở dữ liệu quan hệ và có khả năng mang lại một lược đồ tương tự.
Chúng ta sẽ giữ các định nghĩa thực thể của mình ở mức tối thiểu để chúng ta có thể thấy những gì cần thiết khi mô hình hóa loại mối quan hệ. Những định nghĩa này là điểm khởi đầu, vì vậy các nhà phát triển nên thoải mái thử nghiệm và biến chúng thành của riêng mình.
Kiểu quan hệ đầu tiên là kiểu độc lập. Trong trường hợp này, định nghĩa thực thể không có kết nối với bất kỳ thực thể nào khác trong ngữ cảnh cơ sở dữ liệu của chúng ta. Chúng dễ thiết kế và thường yêu cầu một cột định danh duy nhất.
public class NotRelated
{
public int Id { get; set; }
}
Trong DbContext
của chúng ta, chúng ta cần định nghĩa thuộc tính DbSet
.
public DbSet<NotRelated> NotRelateds { get; set; }
Khi định nghĩa mối quan hệ một-một, chúng ta vẫn cần chỉ định thực thể nào trong kết nối trước. Đặc tả cho phép EF Core chèn một dòng và sau đó thực hiện các truy vấn bổ sung để liên kết hai thực thể với nhau. Trước tiên, hãy định nghĩa mô hình một-một của chúng ta.
public class OneToOneLeft
{
public int Id { get; set; }
public int OneToOneRightId { get; set; }
public OneToOneRight Right { get; set; }
}
public class OneToOneRight
{
public int Id { get; set; }
public int OneToOneLeftId { get; set; }
public OneToOneLeft Left { get; set; }
}
Chúng ta sẽ nhận thấy rằng cả hai thực thể đều có tham chiếu đến thực thể quan hệ một-một của chúng. Trong trường hợp này, chúng ta thấy OneToOneLeft
có một tham chiếu đến OneToOneRight
và ngược lại. Để định nghĩa thứ tự, chúng ta sẽ cần thêm một số mã bổ sung vào phương thức OnModelCreating
của chúng ta trong DbContext
.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 1 to 1 (bidirectional)
modelBuilder.Entity<OneToOneLeft>()
.HasOne<OneToOneRight>()
.WithOne(r => r.Left)
.HasForeignKey<OneToOneRight>(r => r.Id);
base.OnModelCreating(modelBuilder);
}
Cuối cùng, chúng ta có thể thêm cả hai thực thể DbSet
vào DbContext
của chúng ta.
// 1 to 1 (bidirectional)
public DbSet<OneToOneLeft> OneToOneLefts { get; set; }
public DbSet<OneToOneRight> OneToOneRights { get; set; }
EF Core có một khái niệm về các thực thể được sở hữu. Thực thể được sở hữu là một thực thể mà chúng ta chỉ có thể truy cập thông qua mối quan hệ mẹ của nó. Ví dụ, lấy mối quan hệ giữa OneToOneOwner
và OneToOneOwned
.
public class OneToOneOwner
{
public int Id { get; set; }
public OneToOneOwned Owned { get; set; }
}
[Owned]
public class OneToOneOwned
{
public string Value { get; set; }
}
Như chúng ta có thể nhận thấy, cột OneToOneOwned
không có cột định danh. Theo mặc định, EF Core sẽ lưu trữ các giá trị sở hữu một-một trong cùng một bảng với thực thể sở hữu. Việc tối ưu hóa nâng cao hiệu suất truy vấn trong khi vẫn duy trì ý định lập mô hình C#. Hãy xem lược đồ SQL được tạo cho bảng OneToOneOwners
của chúng ta.
create table OneToOneOwners
(
Id INTEGER not null
constraint PK_OneToOneOwners
primary key autoincrement,
Owned_Value TEXT
);
EF Core có khả năng tách bảng nếu chúng ta vẫn muốn lưu trữ dữ liệu thuộc sở hữu của thực thể trong một bảng riêng biệt, nhưng chúng ta sẽ không đi sâu vào điều đó cho bài viết này. Để thêm mối quan hệ sở hữu một-một vào DbContext
, chúng ta cần thêm DbSet
của thực thể mẹ, chính là OneToOneOwner
.
// 1 to 1 (owned)
public DbSet<OneToOneOwner> OneToOneOwners { get; set; }
Mối quan hệ một-nhiều là một trong những mối quan hệ linh hoạt hơn khi xử lý mô hình cơ sở dữ liệu. Với EF Core, chúng ta có thể sử dụng hầu hết các kiểu tập hợp được tìm thấy trong C#, nhưng tôi thường khuyên bạn nên sử dụng List<T>
vì nó có một trong những cách triển khai interface hữu ích hơn với các phương thức như Add
và AddRange
.
Hãy bắt đầu bằng cách xác định thực thể mẹ của chúng ta là OneToMany
và các thực thể con của nó là OneToManyItem
.
public class OneToMany
{
public int Id { get; set; }
public List<OneToManyItem> Items { get; set; }
}
public class OneToManyItem
{
public int Id { get; set; }
public int OneToManyId { get; set; }
public OneToMany OneToMany { get; set; }
}
Như bạn có thể thấy, thuộc tính Items
trong OneToMany
là List<OneToManyItem>
. Chúng ta cũng có thể thấy rằng OneToManyItem
có thuộc tính điều hướng quay trở lại thực thể mẹ. Thuộc tính điều hướng tới thực thể mẹ không phải lúc nào cũng cần thiết, nhưng nó có thể hữu ích khi viết các truy vấn LINQ.
Cuối cùng, chúng ta có thể thêm cả hai thực thể vào DbContext
của mình. Không phải lúc nào cũng cần thêm thực thể con làm thuộc tính DbSet
, nhưng một lần nữa, nó có thể giúp chúng ta khi xây dựng các truy vấn LINQ.
// 1 to Many
public DbSet<OneToMany> OneToManys { get; set; }
public DbSet<OneToManyItem> OneToManyItems { get; set; }
Không giống như mối quan hệ sở hữu một-một, quan hệ sở hữu một-nhiều sẽ tạo ra các bảng riêng biệt. Trong trường hợp của chúng ta, chúng ta có các thực thể OneToManyOwner
và OneToManyOwnedItem
.
public class OneToManyOwner
{
public int Id { get; set; }
public List<OneToManyOwnedItem> Items { get; set; }
}
[Owned]
public class OneToManyOwnedItem
{
public string Name { get; set; }
}
Mặc dù chúng ta không xác định thuộc tính Id
trên thực thể OneToManyOwnedItem
của mình, nhưng EF Core sẽ tạo thứ được gọi là thuộc tính bóng (shadow property) cho khóa chính Id
. Chúng ta có thể thấy điều này trong lược đồ SQL đã tạo.
create table OneToManyOwnedItem
(
OneToManyOwnerId INTEGER not null
constraint FK_OneToManyOwnedItem_OneToManyOwners_OneToManyOwnerId
references OneToManyOwners
on delete cascade,
Id INTEGER not null,
Name TEXT,
constraint PK_OneToManyOwnedItem
primary key (OneToManyOwnerId, Id)
);
Để thêm thực thể OneToManyOwner
vào DbContext
, chúng ta chỉ cần thêm một thuộc tính DbSet
.
// 1 to Many (owned)
public DbSet<OneToManyOwner> OneToManyOwners { get; set; }
Các nhà phát triển từ Entity Framework 6 Code-First sẽ quen thuộc với mối quan hệ nhiều-nhiều ẩn. EF Core quản lý bảng mối quan hệ kết nối hai thực thể, trừu tượng hóa nó khỏi nhà phát triển. Mối quan hệ nhiều-nhiều ẩn chỉ có sẵn kể từ EF Core 5. Chúng ta hãy xem xét hai thực thể ManyToManyLeft
và ManyToManyRight
.
public class ManyToManyLeft
{
public int Id { get; set; }
public List<ManyToManyRight> Rights { get; set; }
}
public class ManyToManyRight
{
public int Id { get; set; }
public List<ManyToManyLeft> Lefts { get; set; }
}
Như chúng ta có thể thấy, cả hai thực thể đều có một tập hợp các thực thể kia. Trong trường hợp này, EF Core quản lý một bảng ẩn ManyToManyLeftManyToManyRight
trong lược đồ cơ sở dữ liệu của chúng ta.
create table ManyToManyLeftManyToManyRight
(
LeftsId INTEGER not null
constraint FK_ManyToManyLeftManyToManyRight_ManyToManyLefts_LeftsId
references ManyToManyLefts
on delete cascade,
RightsId INTEGER not null
constraint FK_ManyToManyLeftManyToManyRight_ManyToManyRights_RightsId
references ManyToManyRights
on delete cascade,
constraint PK_ManyToManyLeftManyToManyRight
primary key (LeftsId, RightsId)
);
create index IX_ManyToManyLeftManyToManyRight_RightsId
on ManyToManyLeftManyToManyRight (RightsId);
Mối quan hệ nhiều-nhiều ẩn là lý tưởng cho các tình huống trong đó kết nối giữa hai thực thể là vấn đề của thực tế, có nghĩa là bản thân mối quan hệ không có thuộc tính phân biệt. Như chúng ta sẽ thấy trong phần tiếp theo, chúng ta cũng có thể lập mô hình nhiều-nhiều với mối quan hệ được thực hiện như một thực thể.
Để hoàn thành mô hình, chúng ta chỉ cần thêm cả hai thực thể kết nối vào DbContext
của chúng ta.
// Many To Many (Transparent)
public DbSet<ManyToManyLeft> ManyToManyLefts { get; set; }
public DbSet<ManyToManyRight> ManyToManyRights { get; set; }
Mô hình hóa mối quan hệ nhiều-nhiều với một thực thể kết nối rất hữu ích cho các tình huống mà bản thân mối quan hệ có các thuộc tính xác định. Một ví dụ có thể là giữa một cá nhân và một ngôi nhà, trong đó cá nhân có thể sở hữu, cho thuê để sở hữu hoặc cho thuê.
Hãy xem xét một ví dụ với các đối tượng ManyToManyWithModeledLeft
, ManyToManyWithModeledRight
và ManyToManyRelationship
sau đây.
public class ManyToManyWithModeledLeft
{
public int Id { get; set; }
public ManyToManyRelationship Relationship { get; set; }
}
public class ManyToManyWithModeledRight
{
public int Id { get; set; }
public ManyToManyRelationship Relationship { get; set; }
}
public class ManyToManyRelationship
{
public int Id { get; set; }
public List<ManyToManyWithModeledLeft> Lefts { get; set; }
public List<ManyToManyWithModeledRight> Rights { get; set; }
}
Như chúng ta có thể nhận thấy, mô hình nhiều-nhiều là sự kết hợp của các mối quan hệ một-một và một-nhiều. Các thực thể ManyToManyWithModeledLeft
và ManyToManyWithModeledRight
có mối quan hệ một-một với thực thể ManyToManyRelationship
. Thực thể ManyToManyRelationship
có hai tập hợp điều hướng cho cả hai thực thể ManyToManyWithModeledLeft
và ManyToManyWithModeledRight
.
Lợi thế của cách tiếp cận này là cách chúng ta có thể đính kèm dữ liệu bổ sung vào mối quan hệ, thay vì để EF Core tự thêm dữ liệu vào bảng ẩn trong kịch bản nhiều-nhiều ẩn.
Chúng ta sẽ cần thêm tất cả các mối quan hệ này vào DbContext
của chúng ta.
// Many To Many (Modeled Relationship)
public DbSet<ManyToManyWithModeledLeft> ManyToManyWithModeledLefts { get; set; }
public DbSet<ManyToManyWithModeledRight> ManyToManyWithModeledRights { get; set; }
public DbSet<ManyToManyRelationship> ManyToManyRelationships { get; set; }
Mối quan hệ phân cấp là khi các dòng trong cùng một bảng tham chiếu đến một dòng khác, thường là trong mối quan hệ cha/con. Hãy xem cách mô hình hóa loại mối quan hệ này.
public class Hierarchical
{
public int Id { get; set; }
public int ParentId { get; set; }
public Hierarchical Parent { get; set; }
public List<Hierarchical> Children { get; set; }
}
Chúng ta có thể thấy rằng chúng ta có thuộc tính điều hướng của thuộc tính Parent
và thuộc tính điều hướng tập hợp của Children
. Mặc dù mối quan hệ này dễ mô hình hóa, nhưng việc truy vấn bằng LINQ tương đối khó khăn. Sử dụng mẫu mô hình này một cách hết sức thận trọng, vì các truy vấn phân cấp có thể tốn kém để thực thi ngay cả khi sử dụng các cấu trúc SQL thô.
Chúng ta có thể thấy lược đồ SQL khi tạo di chuyển EF Core.
create table Hierarchicals
(
Id INTEGER not null
constraint PK_Hierarchicals
primary key autoincrement,
ParentId INTEGER not null
constraint FK_Hierarchicals_Hierarchicals_ParentId
references Hierarchicals
on delete cascade
);
create index IX_Hierarchicals_ParentId
on Hierarchicals (ParentId);
Để thêm mối quan hệ này vào DbContext
, chúng ta chỉ cần thêm thực thể Hierarchial
của mình dưới dạng một thuộc tính DbSet
duy nhất.
// Hierarchical
public DbSet<Hierarchical> Hierarchicals { get; set; }
Chúng ta đã khám phá các mối quan hệ độc lập, một-nhiều, nhiều-nhiều và phân cấp trong EF Core.
Mặc dù có nhiều hoán vị của từng loại mối quan hệ, nhưng bài viết này sẽ giúp người mới bắt đầu có một điểm khởi đầu để xác định mối quan hệ và phục vụ như một bài viết tham khảo cho ngay cả những nhà phát triển EF Core có kinh nghiệm nhất.
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.
Trong bài viết này, chúng ta sẽ khám phá cách lập mô hình dữ liệu đệ quy với Entity Framework Core và SQL Server