From 2f1a80b007e33896b4f83566f4a06fbec96ccd13 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Mon, 4 Nov 2019 17:34:39 -0800 Subject: [PATCH] Update Cosmos docs Fixes #1729 Fixes #1771 --- .../core/modeling/data-seeding.md | 2 +- .../core/providers/cosmos/index.md | 55 +++++++----- .../core/providers/cosmos/limitations.md | 8 +- .../providers/cosmos/unstructured-data.md | 13 ++- .../core/what-is-new/ef-core-2.2.md | 2 +- samples/core/Cosmos/Cosmos.csproj | 2 +- samples/core/Cosmos/ModelBuilding/Order.cs | 2 +- .../core/Cosmos/ModelBuilding/OrderContext.cs | 9 +- samples/core/Cosmos/ModelBuilding/Sample.cs | 85 ++++++++++--------- .../core/Cosmos/UnstructuredData/Sample.cs | 9 +- 10 files changed, 107 insertions(+), 80 deletions(-) diff --git a/entity-framework/core/modeling/data-seeding.md b/entity-framework/core/modeling/data-seeding.md index ef61ca0dcb..c8f0df50e7 100644 --- a/entity-framework/core/modeling/data-seeding.md +++ b/entity-framework/core/modeling/data-seeding.md @@ -88,4 +88,4 @@ Depending on the constraints of your deployment the initialization code can be e * Running the initialization app locally * Deploying the initialization app with the main app, invoking the initialization routine and disabling or removing the initialization app. -This can usually be automated by using [publish profiles](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/visual-studio-publish-profiles). +This can usually be automated by using [publish profiles](https://docs.microsoft.com/aspnet/core/host-and-deploy/visual-studio-publish-profiles). diff --git a/entity-framework/core/providers/cosmos/index.md b/entity-framework/core/providers/cosmos/index.md index f8211811f7..2ccfc5bc26 100644 --- a/entity-framework/core/providers/cosmos/index.md +++ b/entity-framework/core/providers/cosmos/index.md @@ -13,7 +13,10 @@ uid: core/providers/cosmos/index This database provider allows Entity Framework Core to be used with Azure Cosmos DB. The provider is maintained as part of the [Entity Framework Core Project](https://github.com/aspnet/EntityFrameworkCore). -It is strongly recommended to familiarize yourself with the [Azure Cosmos DB documentation](https://docs.microsoft.com/en-us/azure/cosmos-db/introduction) before reading this section. +It is strongly recommended to familiarize yourself with the [Azure Cosmos DB documentation](https://docs.microsoft.com/azure/cosmos-db/introduction) before reading this section. + +>[!NOTE] +> This provider only works with the SQL API of Azure Cosmos DB. ## Install @@ -56,7 +59,7 @@ Saving and quering data follows the normal EF pattern: [!code-csharp[HelloCosmos](../../../../samples/core/Cosmos/ModelBuilding/Sample.cs?name=HelloCosmos)] > [!IMPORTANT] -> Calling `EnsureCreated` is necessary to create the required collections and insert the [seed data](../../modeling/data-seeding.md) if present in the model. However `EnsureCreated` should only be called during deployment, not normal operation, as it may cause performance issues. +> Calling `EnsureCreated` is necessary to create the required containers and insert the [seed data](../../modeling/data-seeding.md) if present in the model. However `EnsureCreated` should only be called during deployment, not normal operation, as it may cause performance issues. ## Cosmos-specific Model Customization @@ -70,6 +73,23 @@ To map an entity type to a different container use `ToContainer`: To identify the entity type that a given item represent EF Core adds a discriminator value even if there are no derived entity types. The name and value of the discriminator [can be changed](../../modeling/inheritance.md). +If no other entity type will ever be stored in the same container the discriminator can be removed by calling `HasNoDiscriminator()`: + +[!code-csharp[NoDiscriminator](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=NoDiscriminator)] + +### Partition Keys + +By default EF Core will create containers with the partition key set to `"__partitionKey"` without supplying any value for it when inserting items. But to fully leverage the performance capabilities of Azure Cosmos a [carefully selected partition key](https://docs.microsoft.com/azure/cosmos-db/partition-data) should be used. It can be configured by calling `.HasPartitionKey()`: + +[!code-csharp[PartitionKey](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=PartitionKey)] + +>[!NOTE] +>The partition key property can be of any type as long as it is [converted to string](xref:core/modeling/value-conversions). + +Once configured the partition key property should always have a non-null value. When issuing a query a condition can be added to make it single-partition. + +[!code-csharp[PartitionKey](../../../../samples/core/Cosmos/ModelBuilding/Sample.cs?name=PartitionKey)] + ## Embedded Entities For Cosmos owned entities are embedded in the same item as the owner. To change a property name use `ToJsonProperty`: @@ -81,12 +101,11 @@ With this configuration the order from the example above is stored like this: ``` json { "Id": 1, - "Discriminator": "Order", + "PartitionKey": "1", "TrackingNumber": null, - "id": "Order|1", + "id": "1", "Address": { "ShipsToCity": "London", - "Discriminator": "StreetAddress", "ShipsToStreet": "221 B Baker St" }, "_rid": "6QEKAM+BOOABAAAAAAAAAA==", @@ -115,12 +134,10 @@ They will be persisted in this way: "ShippingCenters": [ { "City": "Phoenix", - "Discriminator": "StreetAddress", "Street": "500 S 48th Street" }, { "City": "Anaheim", - "Discriminator": "StreetAddress", "Street": "5650 Dolly Ave" } ], @@ -152,18 +169,18 @@ This is the resulting JSON: ``` json { "Id": 1, - "Discriminator": "Order", - "TrackingNumber": null, - "id": "Order|1", - "Address": { - "ShipsToCity": "London", - "Discriminator": "StreetAddress", - "ShipsToStreet": "3 Abbey Road" - }, - "_rid": "6QEKAM+BOOABAAAAAAAAAA==", - "_self": "dbs/6QEKAA==/colls/6QEKAM+BOOA=/docs/6QEKAM+BOOABAAAAAAAAAA==/", - "_etag": "\"00000000-0000-0000-683c-8f7ac48f01d5\"", + "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": 1568163739 + "_ts": 1572917100 } ``` diff --git a/entity-framework/core/providers/cosmos/limitations.md b/entity-framework/core/providers/cosmos/limitations.md index d257fb34d1..75a889c1b1 100644 --- a/entity-framework/core/providers/cosmos/limitations.md +++ b/entity-framework/core/providers/cosmos/limitations.md @@ -10,23 +10,23 @@ uid: core/providers/cosmos/limitations The Cosmos provider has a number of limitations. Many of these limitations are a result of limitations in the underlying Cosmos database engine and are not specific to EF. But most simply [haven't been implemented yet](https://github.com/aspnet/EntityFrameworkCore/issues?page=1&q=is%3Aissue+is%3Aopen+Cosmos+in%3Atitle+label%3Atype-enhancement+sort%3Areactions-%2B1-desc). -## Temporary limitations +## Temporary Limitations - Even if there is only one entity type without inheritance mapped to a container it still has a discriminator property. - Entity types with partition keys don't work correctly in some scenarios - `Include` calls are not supported - `Join` calls are not supported -## Azure Cosmos DB SDK limitations +## Azure Cosmos DB SDK Limitations - Only async methods are provided > [!WARNING] > Since there are no sync versions of the low level methods EF Core relies on, the corresponding functionality is currently implemented by calling `.Wait()` on the returned `Task`. This means that using methods like `SaveChanges`, or `ToList` instead of their async counterparts could lead to a deadlock in your application -## Azure Cosmos DB limitations +## Azure Cosmos DB Limitations -You can see the full overview of [Azure Cosmos DB supported features](https://docs.microsoft.com/en-us/azure/cosmos-db/modeling-data), these are the most notable differences compared to a relational database: +You can see the full overview of [Azure Cosmos DB supported features](https://docs.microsoft.com/azure/cosmos-db/modeling-data), these are the most notable differences compared to a relational database: - Client-initiated transactions are not supported - Some cross-partition queries are either not supported or much slower depending on the operators involved diff --git a/entity-framework/core/providers/cosmos/unstructured-data.md b/entity-framework/core/providers/cosmos/unstructured-data.md index 5208955ebb..f4dc10e882 100644 --- a/entity-framework/core/providers/cosmos/unstructured-data.md +++ b/entity-framework/core/providers/cosmos/unstructured-data.md @@ -10,21 +10,20 @@ uid: core/providers/cosmos/unstructured-data EF Core was designed to make it easy to work with data that follows a schema defined in the model. However one of the strengths of Azure Cosmos DB is the flexibility in the shape of the data stored. -## Accessing the raw JSON +## Accessing the Raw JSON It is possible to access the properties that are not tracked by EF Core through a special property in [shadow-state](../../modeling/shadow-properties.md) named `"__jObject"` that contains a `JObject` representing the data recieved from the store and data that will be stored: -[!code-csharp[Unmapped](../../../../samples/core/Cosmos/UnstructuredData/Sample.cs?highlight=21-23&name=Unmapped)] +[!code-csharp[Unmapped](../../../../samples/core/Cosmos/UnstructuredData/Sample.cs?highlight=23,24&name=Unmapped)] ``` json { "Id": 1, - "Discriminator": "Order", + "PartitionKey": "1", "TrackingNumber": null, - "id": "Order|1", + "id": "1", "Address": { "ShipsToCity": "London", - "Discriminator": "StreetAddress", "ShipsToStreet": "221 B Baker St" }, "_rid": "eLMaAK8TzkIBAAAAAAAAAA==", @@ -44,11 +43,11 @@ It is possible to access the properties that are not tracked by EF Core through ## Using CosmosClient -To decouple completely from EF Core get the `CosmosClient` object that is [part of the Azure Cosmos DB SDK](https://docs.microsoft.com/en-us/azure/cosmos-db/sql-api-get-started) from `DbContext`: +To decouple completely from EF Core get the `CosmosClient` object that is [part of the Azure Cosmos DB SDK](https://docs.microsoft.com/azure/cosmos-db/sql-api-get-started) from `DbContext`: [!code-csharp[CosmosClient](../../../../samples/core/Cosmos/UnstructuredData/Sample.cs?highlight=3&name=CosmosClient)] -## Missing property values +## Missing Property Values In the previous example we removed the `"TrackingNumber"` property from the order. Because of how indexing works in Cosmos DB, queries that reference the missing property somewhere else than in the projection could return unexpected results. For example: diff --git a/entity-framework/core/what-is-new/ef-core-2.2.md b/entity-framework/core/what-is-new/ef-core-2.2.md index ae6cbbb3ea..fe7e4dd7aa 100644 --- a/entity-framework/core/what-is-new/ef-core-2.2.md +++ b/entity-framework/core/what-is-new/ef-core-2.2.md @@ -18,7 +18,7 @@ EF Core 2.2 now supports working with spatial data from various databases using Spatial data support is implemented as a series of provider-specific extension packages. Each of these packages contributes mappings for NTS types and methods, and the corresponding spatial types and functions in the database. Such provider extensions are now available for [SQL Server](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite/), [SQLite](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite/), and [PostgreSQL](https://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite/) (from the [Npgsql project](https://www.npgsql.org/)). -Spatial types can be used directly with the [EF Core in-memory provider](https://docs.microsoft.com/en-us/ef/core/providers/in-memory/) without additional extensions. +Spatial types can be used directly with the [EF Core in-memory provider](xref:core/providers/in-memory/index) without additional extensions. Once the provider extension is installed, you can add properties of supported types to your entities. For example: diff --git a/samples/core/Cosmos/Cosmos.csproj b/samples/core/Cosmos/Cosmos.csproj index b813ab7336..631aabcf36 100644 --- a/samples/core/Cosmos/Cosmos.csproj +++ b/samples/core/Cosmos/Cosmos.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/core/Cosmos/ModelBuilding/Order.cs b/samples/core/Cosmos/ModelBuilding/Order.cs index 7b1ae78e87..2ce6b144cf 100644 --- a/samples/core/Cosmos/ModelBuilding/Order.cs +++ b/samples/core/Cosmos/ModelBuilding/Order.cs @@ -5,7 +5,7 @@ public class Order { public int Id { get; set; } public int? TrackingNumber { get; set; } - //public string PartitionKey { get; set; } + public string PartitionKey { get; set; } public StreetAddress ShippingAddress { get; set; } } #endregion diff --git a/samples/core/Cosmos/ModelBuilding/OrderContext.cs b/samples/core/Cosmos/ModelBuilding/OrderContext.cs index b1900fb194..ff82807f39 100644 --- a/samples/core/Cosmos/ModelBuilding/OrderContext.cs +++ b/samples/core/Cosmos/ModelBuilding/OrderContext.cs @@ -26,9 +26,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .ToContainer("Orders"); #endregion + #region NoDiscriminator + modelBuilder.Entity() + .HasNoDiscriminator(); + #endregion + #region PartitionKey - //modelBuilder.Entity() - // .HasPartitionKey(o => o.PartitionKey); + modelBuilder.Entity() + .HasPartitionKey(o => o.PartitionKey); #endregion #region PropertyNames diff --git a/samples/core/Cosmos/ModelBuilding/Sample.cs b/samples/core/Cosmos/ModelBuilding/Sample.cs index ef21988881..275951cd14 100644 --- a/samples/core/Cosmos/ModelBuilding/Sample.cs +++ b/samples/core/Cosmos/ModelBuilding/Sample.cs @@ -15,18 +15,17 @@ public static async Task Run() Console.WriteLine(); #region HelloCosmos - var londonOrder = new Order - { - Id = 1, - ShippingAddress = new StreetAddress { City = "London", Street = "221 B Baker St" } - }; - using (var context = new OrderContext()) { - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); - context.Add(londonOrder); + context.Add(new Order + { + Id = 1, + ShippingAddress = new StreetAddress { City = "London", Street = "221 B Baker St" }, + PartitionKey = "1" + }); await context.SaveChangesAsync(); } @@ -35,40 +34,45 @@ public static async Task Run() { var order = await context.Orders.FirstAsync(); Console.WriteLine($"First order will ship to: {order.ShippingAddress.Street}, {order.ShippingAddress.City}"); + Console.WriteLine(); } #endregion #region PartitionKey - //using (var context = new OrderContext()) - //{ - // context.Add(new Order - // { - // Id = 2, - // ShippingAddress = new StreetAddress { City = "New York", Street = "11 Wall Street" }, - // PartitionKey = "1" - // }); - - // context.SaveChangesAsync(); - //} - - //using (var context = new OrderContext()) - //{ - // var order = context.Orders.LastAsync(); - // Console.WriteLine($"Last order will ship to: {order.ShippingAddress.Street}, {order.ShippingAddress.City}"); - //} + 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(); + } #endregion #region OwnedCollection - using (var context = new OrderContext()) + var distributor = new Distributor { - context.Add(new Distributor - { - Id = 1, - ShippingCenters = new HashSet { + Id = 1, + ShippingCenters = new HashSet { 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(); } @@ -84,28 +88,29 @@ public static async Task Run() 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(); } #endregion #region Attach using (var context = new OrderContext()) { - var orderEntry = context.Add(londonOrder); - orderEntry.State = EntityState.Unchanged; + var distributorEntry = context.Add(distributor); + distributorEntry.State = EntityState.Unchanged; - londonOrder.ShippingAddress.Street = "3 Abbey Road"; + distributor.ShippingCenters.Remove(distributor.ShippingCenters.Last()); await context.SaveChangesAsync(); } using (var context = new OrderContext()) { - var order = await context.Orders.FirstAsync(); - Console.WriteLine($"First order will now ship to: {order.ShippingAddress.Street}, {order.ShippingAddress.City}"); + var firstDistributor = await context.Distributors.FirstAsync(); + Console.WriteLine($"Number of shipping centers is now: {firstDistributor.ShippingCenters.Count}"); - var orderEntry = context.Entry(order); - var idProperty = orderEntry.Property("id"); - Console.WriteLine($"The order 'id' is: {idProperty.CurrentValue}"); + var distributorEntry = context.Entry(firstDistributor); + var idProperty = distributorEntry.Property("id"); + Console.WriteLine($"The distributor 'id' is: {idProperty.CurrentValue}"); } #endregion } diff --git a/samples/core/Cosmos/UnstructuredData/Sample.cs b/samples/core/Cosmos/UnstructuredData/Sample.cs index e17848cbd6..a2575d082e 100644 --- a/samples/core/Cosmos/UnstructuredData/Sample.cs +++ b/samples/core/Cosmos/UnstructuredData/Sample.cs @@ -19,13 +19,14 @@ public static async Task Run() #region Unmapped using (var context = new OrderContext()) { - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); var order = new Order { Id = 1, - ShippingAddress = new StreetAddress { City = "London", Street = "221 B Baker St" } + ShippingAddress = new StreetAddress { City = "London", Street = "221 B Baker St" }, + PartitionKey = "1" }; context.Add(order); @@ -37,8 +38,8 @@ public static async Task Run() { var order = await context.Orders.FirstAsync(); var orderEntry = context.Entry(order); - var jsonProperty = orderEntry.Property("__jObject"); + var jsonProperty = orderEntry.Property("__jObject"); jsonProperty.CurrentValue["BillingAddress"] = "Clarence House"; orderEntry.State = EntityState.Modified;