Poskytovatel Azure Cosmos DB pro EF Core
Tento poskytovatel databáze umožňuje použít Entity Framework Core s Azure Cosmos DB. Tento poskytovatel je součástí projektu Entity Framework Core.
Před přečtením této části důrazně doporučujeme seznámit se s dokumentací ke službě Azure Cosmos DB.
Poznámka
Tento poskytovatel funguje jenom se službou Azure Cosmos DB for NoSQL.
Instalace
Nainstalujte si balíček Microsoft.EntityFrameworkCore.Cosmos NuGet.
dotnet add package Microsoft.EntityFrameworkCore.Cosmos
Začínáme
Tip
Ukázku pro tento článek najdete na GitHubu.
Stejně jako u jiných poskytovatelů je prvním krokem volání UseCosmos:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseCosmos(
"https://localhost:8081",
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
databaseName: "OrdersDB");
Upozorňující
Koncový bod a klíč jsou zde kvůli jednoduchosti pevně zakódovány, ale v produkční aplikaci by měly být bezpečně uloženy.
V tomto příkladu je Order
jednoduchá entita s odkazem na vlastněný typStreetAddress
.
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; }
}
Ukládání a dotazování dat se řídí běžným vzorem 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();
}
Důležité
Volání EnsureCreatedAsync je nezbytné k vytvoření požadovaných kontejnerů a k vložení počátečních dat, pokud se v modelu nacházejí. EnsureCreatedAsync
by se však mělo volat jen během nasazování a nikoli při běžném provozu, protože může způsobit problémy s výkonem.
Možnosti služby Azure Cosmos DB
Je také možné nakonfigurovat poskytovatele služby Azure Cosmos DB s jedním připojovací řetězec a zadat další možnosti pro přizpůsobení připojení:
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));
});
Tip
Podrobný popis účinku jednotlivých výše uvedených možností najdete v dokumentaci k možnostem Azure Cosmos DB.
Přizpůsobení modelu specifická pro Cosmos
Standardně jsou všechny typy entit namapovány na stejný kontejner pojmenovaný po odvozeném kontextu (v tomto případě "OrderContext"
). Pokud chcete změnit výchozí název kontejneru, použijte HasDefaultContainer:
modelBuilder.HasDefaultContainer("Store");
K namapování typu entity na jiný kontejner použijte ToContainer:
modelBuilder.Entity<Order>()
.ToContainer("Orders");
Kvůli identifikaci typu entity, kterou daná položka reprezentuje, přidává EF Core hodnotu diskriminátoru, i když neexistují žádné odvozené typy entit. Název a hodnotu tohoto diskriminátoru lze změnit.
Pokud ve stejném kontejneru nebude nikdy uložen žádný jiný typ entity, může být diskriminátor odebrán voláním HasNo Discriminatoror:
modelBuilder.Entity<Order>()
.HasNoDiscriminator();
Klíče oddílu
Ve výchozím nastavení EF Core vytvoří kontejnery s klíčem oddílu nastaveným na "__partitionKey"
bez zadání jakékoli hodnoty pro ni při vkládání položek. Pokud ale chcete plně využít možnosti výkonu služby Azure Cosmos DB, měli byste použít pečlivě vybraný klíč oddílu. Lze ho nakonfigurovat voláním HasPartitionKey:
modelBuilder.Entity<Order>()
.HasPartitionKey(o => o.PartitionKey);
Poznámka
Vlastnost klíče oddílu může být libovolného typu, pokud je převedena na řetězec.
Po nakonfigurování by vlastnost klíče oddílu měla mít vždy jinou hodnotu než null. Přidáním volání WithPartitionKey lze dotaz vytvořit s jedním oddílem.
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();
}
Obecně se doporučuje přidat klíč oddílu do primárního klíče, protože to nejlépe odráží sémantiku serveru a umožňuje některé optimalizace, například u FindAsync
.
Zřízená propustnost
Pokud k vytvoření databáze nebo kontejnerů Azure Cosmos DB použijete EF Core, můžete nakonfigurovat zřízenou propustnost pro databázi voláním CosmosModelBuilderExtensions.HasAutoscaleThroughput nebo CosmosModelBuilderExtensions.HasManualThroughput. Příklad:
modelBuilder.HasManualThroughput(2000);
modelBuilder.HasAutoscaleThroughput(4000);
Pokud chcete pro kontejner nakonfigurovat zřízenou propustnost, volejte CosmosEntityTypeBuilderExtensions.HasAutoscaleThroughput nebo CosmosEntityTypeBuilderExtensions.HasManualThroughput. Příklad:
modelBuilder.Entity<Family>(
entityTypeBuilder =>
{
entityTypeBuilder.HasManualThroughput(5000);
entityTypeBuilder.HasAutoscaleThroughput(3000);
});
Vložené entity
Poznámka
Související typy entit jsou ve výchozím nastavení nakonfigurované jako vlastněné. Voláním ModelBuilder.Entity tomu můžete u konkrétního typu entity zabránit.
Ve službě Azure Cosmos DB jsou vlastněné entity vložené do stejné položky jako vlastník. Ke změně názvu vlastnosti použijte 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");
});
Při této konfiguraci se objednávka z předchozího příkladu uloží takto:
{
"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
}
Kolekce vlastněných entit jsou také vložené. V dalším příkladu použijeme třídu Distributor
s kolekcí StreetAddress
:
public class Distributor
{
public int Id { get; set; }
public string ETag { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
Vlastněné entity nemusí poskytovat explicitní hodnoty klíče, které se mají uložit:
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();
}
Budou zachovány takto:
{
"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 musí mít interně pro všechny sledované entity vždy jedinečné klíčové hodnoty. Primární klíč standardně vytvořený pro kolekce vlastněných typů sestává z vlastností cizího klíče odkazujících na vlastníka a z vlastnosti int
odpovídající indexu v poli JSON. K načtení položky s těmito hodnotami lze použít rozhraní 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();
}
Tip
V případě potřeby lze výchozí primární klíč pro typy vlastněných entit změnit, ale hodnoty klíčů by měly být zadány explicitně.
Kolekce primitivních typů
Kolekce podporovaných primitivních typů, například string
a int
, jsou zjišťovány a mapovány automaticky. Podporované kolekce jsou všechny typy, které implementují IReadOnlyList<T> nebo IReadOnlyDictionary<TKey,TValue>. Představte si například tento typ entity:
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; }
}
Jak seznam, tak slovník lze naplnit a vložit do databáze běžným způsobem:
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();
Výsledkem je následující dokument 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
}
Tyto kolekce je pak možné aktualizovat, a to opět běžným způsobem:
book.Quotes.Add("Pressing the emergency button lowered the rods again.");
book.Notes["48"] = "Chiesa d'Oro";
context.SaveChanges();
Omezení:
- Podporují se jen slovníky s řetězcovými klíči.
- Dotazování na obsah primitivních kolekcí se v současné době nepodporuje. Pokud jsou pro vás tyto funkce důležité, hlasujte pro #16926, #25700 a #25701.
Práce s odpojenými entitami
Každá položka musí mít hodnotu id
, která je pro daný klíč oddílu jedinečná. EF Core standardně generuje tuto hodnotu tak, že zřetězí diskriminátor a hodnoty primárního klíče, přičemž jako oddělovač použije znak „|“. Hodnoty klíče se generují jen v případě, kdy entita přejde do stavu Added
. To může představovat problém při připojování entit, pokud u typu .NET nemají vlastnost id
k uložení hodnoty.
Toto omezení obejdete tak, že hodnotu id
vytvoříte a nastavíte ručně, nebo entitu nejprve označíte jako přidanou a pak ji změníte do požadovaného stavu:
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}");
}
Zde je výsledný 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
}
Optimistické uzamykání pomocí eTags
Pokud chcete nějaký typ entity nakonfigurovat tak, aby se používalo optimistické uzamykání, volejte UseETagConcurrency. Toto volání vytvoří vlastnost _etag
ve stínovém stavu a nastaví ji jako token uzamykání.
modelBuilder.Entity<Order>()
.UseETagConcurrency();
Pokud chcete usnadnit řešení chyb uzamykání, můžete pomocí IsETagConcurrency namapovat eTag na některou vlastnost CLR.
modelBuilder.Entity<Distributor>()
.Property(d => d.ETag)
.IsETagConcurrency();
Váš názor
https://aka.ms/ContentUserFeedback.
Připravujeme: V průběhu roku 2024 budeme postupně vyřazovat problémy z GitHub coby mechanismus zpětné vazby pro obsah a nahrazovat ho novým systémem zpětné vazby. Další informace naleznete v tématu:Odeslat a zobrazit názory pro