Overview
在本指南中,您可以学习;了解如何使用MongoDB实体框架核心提供程序对MongoDB 数据库中文档之间的关系进行建模。 EF Core 提供程序支持以下关系类型:
嵌入式关系:直接嵌入父文档中的子文档。对于MongoDB中的一对一和一对多关系,推荐使用这种方法。
手动引用:通过将另一个文档的ID存储在字段中来引用另一个文档,而不是将该文档作为子文档嵌入的文档。此方法非常适合对多对多关系进行建模或需要独立访问权限文档时。
提示
为查询模式设计模式
当您为MongoDB进行数据建模时,请首先确定应用程序最常运行的操作。然后,选择有效支持这些操作的文档结构。 MongoDB中最高效的模式可能与您用于关系数据库的模式不匹配。相反,请考虑哪些数据通常一起检索、哪些值经常更改以及哪些数据关系随时间变化。
要学习;了解有关数据建模和从关系数据库迁移到MongoDB Server 的更多信息,请参阅MongoDB Server文档中的 SQL到MongoDB映射图表和MongoDB中的数据建模。
嵌入式关系
要将子文档嵌入到父文档中,请将嵌入式文档的模型指定为自有实体。嵌入式关系是MongoDB对关系进行建模的原生方法,可为经常一起访问权限的文档提供更好的性能。
何时嵌入文档
当相关数据通常在其父级上下文中查看并且嵌入式数据的大小保持合理限制时,请使用嵌入式文档。这包括一小组子值,例如地址、设置或最近项目的有限列表。嵌入文档时, MongoDB Server可以通过在单个数据库操作中检索相关数据以及在单个文档中自动更新相关数据来提高性能。
请考虑在以下情况下嵌入数据:
您的应用程序通常会一起读取父数据和子数据。在一个查询中获取所有内容比单独调用更有效率。
如果没有明确的上限,相关数据不会增长。有界数据不会导致文档无限增长或达到 MongoDB 16MB文档大小限制。
相关数据不会经常更改,且独立于父项。这样可以减少重写父文档所产生的开销。
注意
嵌套层次结构
当您更新嵌套拥有的实体的任何属性时,EF Core 提供程序会重写整个根文档。对于具有三层或更多嵌套级别的实体层次结构,此写入费用增加,因为树深处的微小更改会触发完整的父文档重写。如果您的模型使用深度嵌套,并且应用程序执行频繁写入,请考虑展平层次结构或使用手动引用替换内部拥有的实体。
如何嵌入文档
您可以通过以下方式指定自有实体:
[Owned] 属性:将
[Owned]数据注释属性应用于子文档的模型类。这种方法是声明性的,简洁,但灵活性较低,因为它全局适用于自有类型的所有用法。Fluent API:调用
OwnsOne()OwnsMany()配置中的 或DbContext方法。这种方法提供了更多的控制和灵活性,允许您为特定实体以不同的方式配置关系,而无需修改模型类。
以下部分举例说明了对单个和多个自有实体进行建模的方法。
一个拥有的实体
要将单个文档嵌入到父文档中,请调用 DbContext 配置中的 OwnsOne() 方法或应用[Owned] 属性应用于嵌入式类。
以下示例显示了一个包含嵌入式 Address文档的Customer 实体。选择 [Owned] Attribute 或 Fluent API标签页,查看相应的语法:
[] public class Address { public string Street { get; set; } = null!; public string City { get; set; } = null!; public string Country { get; set; } = null!; } public class Customer { public ObjectId Id { get; set; } public string Name { get; set; } = null!; public Address Address { get; set; } = null!; }
public class CustomerDbContext : DbContext { public DbSet<Customer> Customers { get; set; } = null!; public CustomerDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Customer>(c => { c.OwnsOne(c => c.Address); c.ToCollection("customers"); }); } }
保存 Customer 实体时, MongoDB会将 Address 存储为 Customer文档中的嵌入式文档。以下JSON显示了此关系在MongoDB中的显示方式:
{ "_id": ObjectId("..."), "Name": "John Doe", "Address": { "Street": "123 Main St", "City": "New York", "Country": "USA" } }
多个拥有实体
要将多个文档嵌入到父文档中,请调用 OwnsMany() 方法或应用[Owned] 属性应用于嵌入式类并定义一个集合属性。
以下示例显示了一个包含多个嵌入式 Order 文档的 Customer 实体。选择 [Owned] Attribute 或 Fluent API标签页,查看相应的语法:
[] public class Order { public string Product { get; set; } = null!; public int Quantity { get; set; } } public class CustomerWithOrders { public ObjectId Id { get; set; } public string Name { get; set; } = null!; public List<Order> Orders { get; set; } = new(); }
public class OrderDbContext : DbContext { public DbSet<CustomerWithOrders> Customers { get; set; } = null!; public OrderDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<CustomerWithOrders>(c => { c.OwnsMany(c => c.Orders); c.ToCollection("customers"); }); } }
保存 CustomerWithOrders 实体时, MongoDB会将 Orders 存储为嵌入式文档大量,如以下JSON所示:
{ "_id": ObjectId("..."), "Name": "Jane Smith", "Orders": [ { "Product": "Laptop", "Quantity": 1 }, { "Product": "Mouse", "Quantity": 2 } ] }
手动引用
要对非嵌入式实体之间的关系进行建模,您可以通过将一个文档的ID保存在另一个文档的字段中来手动存储引用。这种方法要求您在应用程序代码中管理这种关系。
何时使用引用
当您独立于父项定期查询相关数据时,当子设立可能变得非常大时,或者当相关数据频繁更改时,请使用引用。
请考虑在以下情况下使用引用:
相关数据的增长没有明确的上限。无界数组可能会导致性能问题,并可能达到 MongoDB 16MB 的文档大小限制。
您需要自行查询相关实体。您可以直接对集合查询和索引,而无需扫描父文档。
相关数据经常发生变化,且独立于父数据。频繁更新嵌入式数据需要每次重写整个父文档,效率低下。
应用程序通常不会同时读取父数据和子数据。通过使用引用,可以避免获取不需要的子数据。
该关系是多对多或其他复杂关系。
复制数据会产生过多的更新开销。
如何使用引用
以下示例展示了如何存储对相关实体的引用:
public class Author { public ObjectId Id { get; set; } public string Name { get; set; } = null!; } public class Book { public ObjectId Id { get; set; } public string Title { get; set; } = null!; // Store reference to Author by storing the Author's Id public ObjectId AuthorId { get; set; } }
要检索相关实体,必须单独查询引用的集合,如以下示例所示:
// Query a book and its author var book = db.Books.FirstOrDefault(b => b.Title == "My Book"); var author = db.Authors.FirstOrDefault(a => a.Id == book!.AuthorId);
对大型一对多集使用子集模式
如果父文档包含大型大量,但应用程序通常仅使用最近或最常访问的项目,请考虑使用子集模式。
在子集模式中,将最常访问的项目保留在主文档中,并将其余项目移动到单独的集合中。这种方法可以减小文档大小,并将更多工作集保留在RAM中,从而提高查询性能。
如果您希望获得嵌入的性能优势,但完整的嵌入历史记录会导致文档变得太大或加载成本太高,则此模式非常有用。
更多信息
要学习;了解有关MongoDB中模式设计最佳实践的更多信息,请参阅MongoDB Server文档中的MongoDB中数据建模最佳实践和设计您的模式。
要学习;了解有关在MongoDB中进行数据建模的更多信息,请参阅MongoDB Server文档中的数据建模。
要学习;了解有关 Entity Framework Core 中自有实体类型的更多信息,请参阅Microsoft Entity Framework Core 文档中的自有实体类型。
API 文档
要学习;了解有关本指南中讨论的方法和类型的更多信息,请参阅以下Microsoft API文档: