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 包。
dotnet add package Microsoft.EntityFrameworkCore.Cosmos
Install-Package Microsoft.EntityFrameworkCore.Cosmos
入门Get started
提示
可在 GitHub 示例中查看此文章的示例。
与其他提供程序一样,第一步是调用 UseCosmos:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseCosmos(
"https://localhost:8081",
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
databaseName: "OrdersDB");
警告
为了简单起见,此处对终结点和密钥进行了硬编码,但在生产应用中,应安全地存储这些终结点和密钥。
在本例中,Order
是一个简单实体,其中包含对从属类型 StreetAddress
的引用。
public class Order
{
public int Id { get; set; }
public int? TrackingNumber { get; set; }
public string PartitionKey { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
public class StreetAddress
{
public string Street { get; set; }
public string City { get; set; }
}
保存和查询数据遵循常规 EF 模式:
using (var context = new OrderContext())
{
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
context.Add(new Order
{
Id = 1,
ShippingAddress = new StreetAddress { City = "London", Street = "221 B Baker St" },
PartitionKey = "1"
});
await context.SaveChangesAsync();
}
using (var context = new OrderContext())
{
var order = await context.Orders.FirstAsync();
Console.WriteLine($"First order will ship to: {order.ShippingAddress.Street}, {order.ShippingAddress.City}");
Console.WriteLine();
}
重要
要创建所需的容器并插入种子数据(如果存在于模型中),则需要调用 EnsureCreatedAsync。 但是只应在部署期间调用 EnsureCreatedAsync
,而不应在正常操作中调用,否则可能会导致性能问题。
特定于 Cosmos 的模型自定义Cosmos-specific model customization
默认情况下,所有实体类型都映射到同一个容器,该容器以派生的上下文命名(在本例中为 "OrderContext"
)。 要更改默认容器名称,请使用 HasDefaultContainer:
modelBuilder.HasDefaultContainer("Store");
要将实体类型映射到其他容器,请使用 ToContainer:
modelBuilder.Entity<Order>()
.ToContainer("Orders");
为了标识给定项表示的实体类型,EF Core 添加鉴别器值(即使没有派生实体类型)。 可以更改鉴别器的名称和值。
如果其他实体类型永远不会存储在同一个容器中,则可以通过调用 HasNoDiscriminator 删除鉴别器:
modelBuilder.Entity<Order>()
.HasNoDiscriminator();
分区键Partition keys
默认情况下,EF Core 将创建分区键设置为 "__partitionKey"
的容器,而不会在插入项时为其提供任何值。 但若要充分利用 Azure Cosmos 的性能功能,应仔细选择应使用的分区键。 可以通过调用 HasPartitionKey来配置它:
modelBuilder.Entity<Order>()
.HasPartitionKey(o => o.PartitionKey);
备注
只要分区键属性转换为字符串,则它可以为任意类型。
配置分区键属性后,应始终具有非 null 值。 发出查询时,可以添加条件将其设置为单分区。
using (var context = new OrderContext())
{
context.Add(new Order
{
Id = 2,
ShippingAddress = new StreetAddress { City = "New York", Street = "11 Wall Street" },
PartitionKey = "2"
});
await context.SaveChangesAsync();
}
using (var context = new OrderContext())
{
var order = await context.Orders.Where(p => p.PartitionKey == "2").LastAsync();
Console.Write("Last order will ship to: ");
Console.WriteLine($"{order.ShippingAddress.Street}, {order.ShippingAddress.City}");
Console.WriteLine();
}
嵌入的实体Embedded entities
对于 Cosmos,从属实体嵌入到所有者所在的项中。 要更改属性名称,请使用 ToJsonProperty:
modelBuilder.Entity<Order>().OwnsOne(
o => o.ShippingAddress,
sa =>
{
sa.ToJsonProperty("Address");
sa.Property(p => p.Street).ToJsonProperty("ShipsToStreet");
sa.Property(p => p.City).ToJsonProperty("ShipsToCity");
});
对于此配置,以上示例中的顺序存储如下:
{
"Id": 1,
"PartitionKey": "1",
"TrackingNumber": null,
"id": "1",
"Address": {
"ShipsToCity": "London",
"ShipsToStreet": "221 B Baker St"
},
"_rid": "6QEKAM+BOOABAAAAAAAAAA==",
"_self": "dbs/6QEKAA==/colls/6QEKAM+BOOA=/docs/6QEKAM+BOOABAAAAAAAAAA==/",
"_etag": "\"00000000-0000-0000-683c-692e763901d5\"",
"_attachments": "attachments/",
"_ts": 1568163674
}
还嵌入了从属实体的集合。 对于下一个示例,我们将使用具有 StreetAddress
集合的 Distributor
类:
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
从属实体不需要提供要存储的显式键值:
var distributor = new Distributor
{
Id = 1,
ShippingCenters = new HashSet<StreetAddress> {
new StreetAddress { City = "Phoenix", Street = "500 S 48th Street" },
new StreetAddress { City = "Anaheim", Street = "5650 Dolly Ave" }
}
};
using (var context = new OrderContext())
{
context.Add(distributor);
await context.SaveChangesAsync();
}
它们将以这种方式持久保存:
{
"Id": 1,
"Discriminator": "Distributor",
"id": "Distributor|1",
"ShippingCenters": [
{
"City": "Phoenix",
"Street": "500 S 48th Street"
},
{
"City": "Anaheim",
"Street": "5650 Dolly Ave"
}
],
"_rid": "6QEKANzISj0BAAAAAAAAAA==",
"_self": "dbs/6QEKAA==/colls/6QEKANzISj0=/docs/6QEKANzISj0BAAAAAAAAAA==/",
"_etag": "\"00000000-0000-0000-683c-7b2b439701d5\"",
"_attachments": "attachments/",
"_ts": 1568163705
}
在内部而言,EF Core 始终需要对所有被跟踪实体提供唯一键值。 默认情况下,为从属类型集合创建的主键包含指向所有者的外键属性和与 JSON 数组中的索引对应的 int
属性。 要检索这些值,可使用以下条目 API:
using (var context = new OrderContext())
{
var firstDistributor = await context.Distributors.FirstAsync();
Console.WriteLine($"Number of shipping centers: {firstDistributor.ShippingCenters.Count}");
var addressEntry = context.Entry(firstDistributor.ShippingCenters.First());
var addressPKProperties = addressEntry.Metadata.FindPrimaryKey().Properties;
Console.WriteLine($"First shipping center PK: ({addressEntry.Property(addressPKProperties[0].Name).CurrentValue}, {addressEntry.Property(addressPKProperties[1].Name).CurrentValue})");
Console.WriteLine();
}
提示
必要时,可更改从属实体类型的默认主键,但应显式提供键值。
使用断开连接的实体Working with disconnected entities
每个项都需要具有一个对于给定分区键唯一的 id
值。 默认情况下 EF Core 通过使用 ‘|’ 作为分隔符串联鉴别器和主键值来生成值。 仅当实体进入 Added
状态时才生成键值。 如果附加实体在 .NET 类型上没有用于存储值的 id
属性,则这可能会导致问题。
要解决此限制,可以手动创建并设置 id
值,或者先将实体标记为已添加,然后将其更改为所需状态:
using (var context = new OrderContext())
{
var distributorEntry = context.Add(distributor);
distributorEntry.State = EntityState.Unchanged;
distributor.ShippingCenters.Remove(distributor.ShippingCenters.Last());
await context.SaveChangesAsync();
}
using (var context = new OrderContext())
{
var firstDistributor = await context.Distributors.FirstAsync();
Console.WriteLine($"Number of shipping centers is now: {firstDistributor.ShippingCenters.Count}");
var distributorEntry = context.Entry(firstDistributor);
var idProperty = distributorEntry.Property<string>("id");
Console.WriteLine($"The distributor 'id' is: {idProperty.CurrentValue}");
}
生成的 JSON 如下:
{
"Id": 1,
"Discriminator": "Distributor",
"id": "Distributor|1",
"ShippingCenters": [
{
"City": "Phoenix",
"Street": "500 S 48th Street"
}
],
"_rid": "JBwtAN8oNYEBAAAAAAAAAA==",
"_self": "dbs/JBwtAA==/colls/JBwtAN8oNYE=/docs/JBwtAN8oNYEBAAAAAAAAAA==/",
"_etag": "\"00000000-0000-0000-9377-d7a1ae7c01d5\"",
"_attachments": "attachments/",
"_ts": 1572917100
}