EF Core Azure Cosmos DB 提供者
此資料庫提供者可讓 Entity Framework Core 與 Azure Cosmos DB 搭配使用。 Entity Framework Core 專案的維護包含此提供者。
強烈建議您在閱讀本節之前先熟悉 Azure Cosmos DB 文件。
注意
此提供者僅適用于適用于 NoSQL 的 Azure Cosmos DB。
安裝
安裝 Microsoft.EntityFrameworkCore.Cosmos NuGet 套件。
dotnet add package Microsoft.EntityFrameworkCore.Cosmos
開始使用
提示
您可以檢視本文中的 GitHut 範例。
至於其他提供者,第一步是呼叫 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
僅應在部署期間呼叫,在一般作業期間呼叫可能會導致效能問題。
Azure Cosmos DB 選項
您也可以使用單一連接字串設定 Azure Cosmos DB 提供者,並指定其他選項來自訂連線:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseCosmos(
"AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
databaseName: "OptionsDB",
options =>
{
options.ConnectionMode(ConnectionMode.Gateway);
options.WebProxy(new WebProxy());
options.LimitToEndpoint();
options.Region(Regions.AustraliaCentral);
options.GatewayModeMaxConnectionLimit(32);
options.MaxRequestsPerTcpConnection(8);
options.MaxTcpConnectionsPerEndpoint(16);
options.IdleTcpConnectionTimeout(TimeSpan.FromMinutes(1));
options.OpenTcpConnectionTimeout(TimeSpan.FromMinutes(1));
options.RequestTimeout(TimeSpan.FromMinutes(1));
});
提示
如需上述每個選項效果的詳細描述,請參閱 Azure Cosmos DB 選項文件。
Cosmos 專用模型自訂
根據預設,所有實體類型都會對應至相同容器,並以衍生的內容命名 (在此案例中為 "OrderContext"
)。 若要變更預設容器名稱,請使用 HasDefaultContainer:
modelBuilder.HasDefaultContainer("Store");
若要將實體類型對應至不同的容器,請使用 ToContainer:
modelBuilder.Entity<Order>()
.ToContainer("Orders");
為了識別其指定項目代表 EF Core 的實體類型,即使沒有衍生的實體類型,也會新增鑑別子值。 鑑別子的名稱和值可以變更。
如果不會在同一個容器中儲存其他實體類型,就可以呼叫 HasNoDiscriminator 將鑑別子移除:
modelBuilder.Entity<Order>()
.HasNoDiscriminator();
分割區索引鍵
根據預設,EF Core 會建立容器,並將分割區索引鍵設定為 , "__partitionKey"
而不會在插入專案時提供任何值。 但是,若要充分利用 Azure Cosmos DB 的效能功能, 應該使用謹慎選取的資料分割索引鍵 。 您可以透過呼叫 HasPartitionKey 來設定分割區索引鍵:
modelBuilder.Entity<Order>()
.HasPartitionKey(o => o.PartitionKey);
注意
只要分割區索引鍵屬性會轉換成字串,就不限於任何類型。
分割區索引鍵屬性設定完成後,應該都會具有 null 以外的值。 新增 WithPartitionKey 呼叫可使查詢成為單一分割區。
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.WithPartitionKey("2").LastAsync();
Console.WriteLine($"Last order will ship to: {order.ShippingAddress.Street}, {order.ShippingAddress.City}");
Console.WriteLine();
}
通常建議將分割區索引鍵新增至主索引鍵,因為這樣最能反映伺服器語意,並能最佳化某些項目,例如 FindAsync
。
佈建的輸送量
如果您使用 EF Core 來建立 Azure Cosmos DB 資料庫或容器,您可以 藉由呼叫 CosmosModelBuilderExtensions.HasAutoscaleThroughput 或 CosmosModelBuilderExtensions.HasManualThroughput 來設定資料庫的布建輸送量 。 例如:
modelBuilder.HasManualThroughput(2000);
modelBuilder.HasAutoscaleThroughput(4000);
呼叫 CosmosEntityTypeBuilderExtensions.HasAutoscaleThroughput 或 CosmosEntityTypeBuilderExtensions.HasManualThroughput 可設定容器的佈建輸送量。 例如:
modelBuilder.Entity<Family>(
entityTypeBuilder =>
{
entityTypeBuilder.HasManualThroughput(5000);
entityTypeBuilder.HasAutoscaleThroughput(3000);
});
內嵌實體
注意
相關實體類型預設會設定為擁有。 若要防止特定實體類型,請呼叫 ModelBuilder.Entity。
針對 Azure Cosmos DB,擁有的實體會內嵌在與擁有者相同的專案中。 若要變更屬性名稱,請使用 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
}
同時也會內嵌自有實體的集合。 在下一個範例中,我們將使用 Distributor
類別和 StreetAddress
的集合:
public class Distributor
{
public int Id { get; set; }
public string ETag { 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();
}
提示
必要時,您可以變更自有實體類型的預設主索引鍵,但應明確提供索引鍵值。
基本類型集合
系統會自動探索及對應支援的基本類型集合,例如 string
和 int
。 所有可實作 IReadOnlyList<T> 或 IReadOnlyDictionary<TKey,TValue> 的類型都是支援的集合。 例如,請考慮以下的實體類型:
public class Book
{
public Guid Id { get; set; }
public string Title { get; set; }
public IList<string> Quotes { get; set; }
public IDictionary<string, string> Notes { get; set; }
}
清單和字典都可使用一般方式填入並插入資料庫:
using var context = new BooksContext();
var book = new Book
{
Title = "How It Works: Incredible History",
Quotes = new List<string>
{
"Thomas (Tommy) Flowers was the British engineer behind the design of the Colossus computer.",
"Invented originally for Guinness, plastic widgets are nitrogen-filled spheres.",
"For 20 years after its introduction in 1979, the Walkman dominated the personal stereo market."
},
Notes = new Dictionary<string, string>
{
{ "121", "Fridges" },
{ "144", "Peter Higgs" },
{ "48", "Saint Mark's Basilica" },
{ "36", "The Terracotta Army" }
}
};
context.Add(book);
context.SaveChanges();
這會產生下列 JSON 文件:
{
"Id": "0b32283e-22a8-4103-bb4f-6052604868bd",
"Discriminator": "Book",
"Notes": {
"36": "The Terracotta Army",
"48": "Saint Mark's Basilica",
"121": "Fridges",
"144": "Peter Higgs"
},
"Quotes": [
"Thomas (Tommy) Flowers was the British engineer behind the design of the Colossus computer.",
"Invented originally for Guinness, plastic widgets are nitrogen-filled spheres.",
"For 20 years after its introduction in 1979, the Walkman dominated the personal stereo market."
],
"Title": "How It Works: Incredible History",
"id": "Book|0b32283e-22a8-4103-bb4f-6052604868bd",
"_rid": "t-E3AIxaencBAAAAAAAAAA==",
"_self": "dbs/t-E3AA==/colls/t-E3AIxaenc=/docs/t-E3AIxaencBAAAAAAAAAA==/",
"_etag": "\"00000000-0000-0000-9b50-fc769dc901d7\"",
"_attachments": "attachments/",
"_ts": 1630075016
}
然後,您可以再次使用一般方式更新這些集合:
book.Quotes.Add("Pressing the emergency button lowered the rods again.");
book.Notes["48"] = "Chiesa d'Oro";
context.SaveChanges();
限制:
使用已中斷連線的實體
每個項目都必須具有 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
}
使用 ETag 的開放式並行存取
請呼叫 UseETagConcurrency,以將實體類型設定為使用開放式並行存取。 此呼叫會建立陰影狀態的 _etag
屬性,並將其設定為並行權杖。
modelBuilder.Entity<Order>()
.UseETagConcurrency();
若要更輕鬆解決並行錯誤,您可以使用 IsETagConcurrency 將 ETag 對應至 CLR 屬性。
modelBuilder.Entity<Distributor>()
.Property(d => d.ETag)
.IsETagConcurrency();
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應