EF Core Azure Cosmos DB ProviderEF Core Azure Cosmos DB Provider

备注

此提供程序是 EF Core 3.0 新增内容。

此数据库提供程序允许将 Entity Framework Core 与 Azure Cosmos DB 一起使用。 该提供程序作为 Entity Framework Core 项目的组成部分进行维护。

在阅读本部分之前,强烈建议先熟悉 Azure Cosmos DB 文档

备注

此提供程序仅适用于 Azure Cosmos DB 的 SQL API。

安装Install

安装 Microsoft.EntityFrameworkCore.Cosmos NuGet 包

  1. dotnet add package Microsoft.EntityFrameworkCore.Cosmos
  1. Install-Package Microsoft.EntityFrameworkCore.Cosmos

入门Get started

提示

可在 GitHub 示例中查看此文章的示例。

与其他提供程序一样,第一步是调用 UseCosmos

  1. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  2. => optionsBuilder.UseCosmos(
  3. "https://localhost:8081",
  4. "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
  5. databaseName: "OrdersDB");

警告

为了简单起见,此处对终结点和密钥进行了硬编码,但在生产应用中,应安全地存储这些终结点和密钥。

在本例中,Order 是一个简单实体,其中包含对从属类型 StreetAddress 的引用。

  1. public class Order
  2. {
  3. public int Id { get; set; }
  4. public int? TrackingNumber { get; set; }
  5. public string PartitionKey { get; set; }
  6. public StreetAddress ShippingAddress { get; set; }
  7. }
  1. public class StreetAddress
  2. {
  3. public string Street { get; set; }
  4. public string City { get; set; }
  5. }

保存和查询数据遵循常规 EF 模式:

  1. using (var context = new OrderContext())
  2. {
  3. await context.Database.EnsureDeletedAsync();
  4. await context.Database.EnsureCreatedAsync();
  5. context.Add(new Order
  6. {
  7. Id = 1,
  8. ShippingAddress = new StreetAddress { City = "London", Street = "221 B Baker St" },
  9. PartitionKey = "1"
  10. });
  11. await context.SaveChangesAsync();
  12. }
  13. using (var context = new OrderContext())
  14. {
  15. var order = await context.Orders.FirstAsync();
  16. Console.WriteLine($"First order will ship to: {order.ShippingAddress.Street}, {order.ShippingAddress.City}");
  17. Console.WriteLine();
  18. }

重要

要创建所需的容器并插入种子数据(如果存在于模型中),则需要调用 EnsureCreatedAsync。 但是只应在部署期间调用 EnsureCreatedAsync,而不应在正常操作中调用,否则可能会导致性能问题。

特定于 Cosmos 的模型自定义Cosmos-specific model customization

默认情况下,所有实体类型都映射到同一个容器,该容器以派生的上下文命名(在本例中为 "OrderContext")。 要更改默认容器名称,请使用 HasDefaultContainer

  1. modelBuilder.HasDefaultContainer("Store");

要将实体类型映射到其他容器,请使用 ToContainer

  1. modelBuilder.Entity<Order>()
  2. .ToContainer("Orders");

为了标识给定项表示的实体类型,EF Core 添加鉴别器值(即使没有派生实体类型)。 可以更改鉴别器的名称和值。

如果其他实体类型永远不会存储在同一个容器中,则可以通过调用 HasNoDiscriminator 删除鉴别器:

  1. modelBuilder.Entity<Order>()
  2. .HasNoDiscriminator();

分区键Partition keys

默认情况下,EF Core 将创建分区键设置为 "__partitionKey" 的容器,而不会在插入项时为其提供任何值。 但若要充分利用 Azure Cosmos 的性能功能,应仔细选择应使用的分区键。 可以通过调用 HasPartitionKey来配置它:

  1. modelBuilder.Entity<Order>()
  2. .HasPartitionKey(o => o.PartitionKey);

备注

只要分区键属性转换为字符串,则它可以为任意类型。

配置分区键属性后,应始终具有非 null 值。 发出查询时,可以添加条件将其设置为单分区。

  1. using (var context = new OrderContext())
  2. {
  3. context.Add(new Order
  4. {
  5. Id = 2,
  6. ShippingAddress = new StreetAddress { City = "New York", Street = "11 Wall Street" },
  7. PartitionKey = "2"
  8. });
  9. await context.SaveChangesAsync();
  10. }
  11. using (var context = new OrderContext())
  12. {
  13. var order = await context.Orders.Where(p => p.PartitionKey == "2").LastAsync();
  14. Console.Write("Last order will ship to: ");
  15. Console.WriteLine($"{order.ShippingAddress.Street}, {order.ShippingAddress.City}");
  16. Console.WriteLine();
  17. }

嵌入的实体Embedded entities

对于 Cosmos,从属实体嵌入到所有者所在的项中。 要更改属性名称,请使用 ToJsonProperty

  1. modelBuilder.Entity<Order>().OwnsOne(
  2. o => o.ShippingAddress,
  3. sa =>
  4. {
  5. sa.ToJsonProperty("Address");
  6. sa.Property(p => p.Street).ToJsonProperty("ShipsToStreet");
  7. sa.Property(p => p.City).ToJsonProperty("ShipsToCity");
  8. });

对于此配置,以上示例中的顺序存储如下:

  1. {
  2. "Id": 1,
  3. "PartitionKey": "1",
  4. "TrackingNumber": null,
  5. "id": "1",
  6. "Address": {
  7. "ShipsToCity": "London",
  8. "ShipsToStreet": "221 B Baker St"
  9. },
  10. "_rid": "6QEKAM+BOOABAAAAAAAAAA==",
  11. "_self": "dbs/6QEKAA==/colls/6QEKAM+BOOA=/docs/6QEKAM+BOOABAAAAAAAAAA==/",
  12. "_etag": "\"00000000-0000-0000-683c-692e763901d5\"",
  13. "_attachments": "attachments/",
  14. "_ts": 1568163674
  15. }

还嵌入了从属实体的集合。 对于下一个示例,我们将使用具有 StreetAddress 集合的 Distributor 类:

  1. public class Distributor
  2. {
  3. public int Id { get; set; }
  4. public ICollection<StreetAddress> ShippingCenters { get; set; }
  5. }

从属实体不需要提供要存储的显式键值:

  1. var distributor = new Distributor
  2. {
  3. Id = 1,
  4. ShippingCenters = new HashSet<StreetAddress> {
  5. new StreetAddress { City = "Phoenix", Street = "500 S 48th Street" },
  6. new StreetAddress { City = "Anaheim", Street = "5650 Dolly Ave" }
  7. }
  8. };
  9. using (var context = new OrderContext())
  10. {
  11. context.Add(distributor);
  12. await context.SaveChangesAsync();
  13. }

它们将以这种方式持久保存:

  1. {
  2. "Id": 1,
  3. "Discriminator": "Distributor",
  4. "id": "Distributor|1",
  5. "ShippingCenters": [
  6. {
  7. "City": "Phoenix",
  8. "Street": "500 S 48th Street"
  9. },
  10. {
  11. "City": "Anaheim",
  12. "Street": "5650 Dolly Ave"
  13. }
  14. ],
  15. "_rid": "6QEKANzISj0BAAAAAAAAAA==",
  16. "_self": "dbs/6QEKAA==/colls/6QEKANzISj0=/docs/6QEKANzISj0BAAAAAAAAAA==/",
  17. "_etag": "\"00000000-0000-0000-683c-7b2b439701d5\"",
  18. "_attachments": "attachments/",
  19. "_ts": 1568163705
  20. }

在内部而言,EF Core 始终需要对所有被跟踪实体提供唯一键值。 默认情况下,为从属类型集合创建的主键包含指向所有者的外键属性和与 JSON 数组中的索引对应的 int 属性。 要检索这些值,可使用以下条目 API:

  1. using (var context = new OrderContext())
  2. {
  3. var firstDistributor = await context.Distributors.FirstAsync();
  4. Console.WriteLine($"Number of shipping centers: {firstDistributor.ShippingCenters.Count}");
  5. var addressEntry = context.Entry(firstDistributor.ShippingCenters.First());
  6. var addressPKProperties = addressEntry.Metadata.FindPrimaryKey().Properties;
  7. Console.WriteLine($"First shipping center PK: ({addressEntry.Property(addressPKProperties[0].Name).CurrentValue}, {addressEntry.Property(addressPKProperties[1].Name).CurrentValue})");
  8. Console.WriteLine();
  9. }

提示

必要时,可更改从属实体类型的默认主键,但应显式提供键值。

使用断开连接的实体Working with disconnected entities

每个项都需要具有一个对于给定分区键唯一的 id 值。 默认情况下 EF Core 通过使用 ‘|’ 作为分隔符串联鉴别器和主键值来生成值。 仅当实体进入 Added 状态时才生成键值。 如果附加实体在 .NET 类型上没有用于存储值的 id 属性,则这可能会导致问题。

要解决此限制,可以手动创建并设置 id 值,或者先将实体标记为已添加,然后将其更改为所需状态:

  1. using (var context = new OrderContext())
  2. {
  3. var distributorEntry = context.Add(distributor);
  4. distributorEntry.State = EntityState.Unchanged;
  5. distributor.ShippingCenters.Remove(distributor.ShippingCenters.Last());
  6. await context.SaveChangesAsync();
  7. }
  8. using (var context = new OrderContext())
  9. {
  10. var firstDistributor = await context.Distributors.FirstAsync();
  11. Console.WriteLine($"Number of shipping centers is now: {firstDistributor.ShippingCenters.Count}");
  12. var distributorEntry = context.Entry(firstDistributor);
  13. var idProperty = distributorEntry.Property<string>("id");
  14. Console.WriteLine($"The distributor 'id' is: {idProperty.CurrentValue}");
  15. }

生成的 JSON 如下:

  1. {
  2. "Id": 1,
  3. "Discriminator": "Distributor",
  4. "id": "Distributor|1",
  5. "ShippingCenters": [
  6. {
  7. "City": "Phoenix",
  8. "Street": "500 S 48th Street"
  9. }
  10. ],
  11. "_rid": "JBwtAN8oNYEBAAAAAAAAAA==",
  12. "_self": "dbs/JBwtAA==/colls/JBwtAN8oNYE=/docs/JBwtAN8oNYEBAAAAAAAAAA==/",
  13. "_etag": "\"00000000-0000-0000-9377-d7a1ae7c01d5\"",
  14. "_attachments": "attachments/",
  15. "_ts": 1572917100
  16. }