diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index 83e84b0743..b81233e570 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -479,6 +479,11 @@ "source_path": "entity-framework/core/logging-events-diagnostics/event-counters.md", "redirect_url": "/ef/core/logging-events-diagnostics/metrics", "redirect_document_id": false + }, + { + "source_path": "entity-framework/core/providers/cosmos/functions.md", + "redirect_url": "/ef/core/providers/cosmos/querying", + "redirect_document_id": false } ] } diff --git a/entity-framework/core/providers/cosmos/functions.md b/entity-framework/core/providers/cosmos/functions.md deleted file mode 100644 index fb66960203..0000000000 --- a/entity-framework/core/providers/cosmos/functions.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Function Mappings - Azure Cosmos DB Provider - EF Core -description: Function Mappings of the Azure Cosmos DB EF Core Provider -author: bricelam -ms.date: 7/26/2023 -uid: core/providers/cosmos/functions ---- -# Function Mappings of the Azure Cosmos DB EF Core Provider - -This page shows which .NET members are translated into which SQL functions when using the Azure Cosmos DB provider. - -## Date and time functions - -.NET | SQL ---------------------- | --- -DateTime.UtcNow | GetCurrentDateTime() -DateTimeOffset.UtcNow | GetCurrentDateTime() - -## Numeric functions - -.NET | SQL | Added in --------------------------- | ----------------- | -------- -double.DegreesToRadians(x) | RADIANS(@x) | EF Core 8.0 -double.RadiansToDegrees(x) | DEGREES(@x) | EF Core 8.0 -EF.Functions.Random() | RAND() -Math.Abs(value) | ABS(@value) -Math.Acos(d) | ACOS(@d) -Math.Asin(d) | ASIN(@d) -Math.Atan(d) | ATAN(@d) -Math.Atan2(y, x) | ATN2(@y, @x) -Math.Ceiling(d) | CEILING(@d) -Math.Cos(d) | COS(@d) -Math.Exp(d) | EXP(@d) -Math.Floor(d) | FLOOR(@d) -Math.Log(a, newBase) | LOG(@a, @newBase) -Math.Log(d) | LOG(@d) -Math.Log10(d) | LOG10(@d) -Math.Pow(x, y) | POWER(@x, @y) -Math.Round(d) | ROUND(@d) -Math.Sign(value) | SIGN(@value) -Math.Sin(a) | SIN(@a) -Math.Sqrt(d) | SQRT(@d) -Math.Tan(a) | TAN(@a) -Math.Truncate(d) | TRUNC(@d) - -> [!TIP] -> In addition to the methods listed here, corresponding [generic math](/dotnet/standard/generics/math) implementations -> and [MathF](/dotnet/api/system.mathf) methods are also translated. For example, `Math.Sin`, `MathF.Sin`, `double.Sin`, -> and `float.Sin` all map to the `SIN` function in SQL. - -## String functions - -.NET | SQL | Added in -------------------------------------------------------------- | ---------------------------------------------------------- | -------- -Regex.IsMatch(input, pattern) | RegexMatch(@pattern, @input) | EF Core 7.0 -Regex.IsMatch(input, pattern, options) | RegexMatch(@input, @pattern, @options) | EF Core 7.0 -string.Concat(str0, str1) | @str0 + @str1 -string.Equals(a, b, StringComparison.Ordinal) | STRINGEQUALS(@a, @b) -string.Equals(a, b, StringComparison.OrdinalIgnoreCase) | STRINGEQUALS(@a, @b, true) -stringValue.Contains(value) | CONTAINS(@stringValue, @value) -stringValue.EndsWith(value) | ENDSWITH(@stringValue, @value) -stringValue.Equals(value, StringComparison.Ordinal) | STRINGEQUALS(@stringValue, @value) -stringValue.Equals(value, StringComparison.OrdinalIgnoreCase) | STRINGEQUALS(@stringValue, @value, true) -stringValue.FirstOrDefault() | LEFT(@stringValue, 1) -stringValue.IndexOf(value) | INDEX_OF(@stringValue, @value) -stringValue.IndexOf(value, startIndex) | INDEX_OF(@stringValue, @value, @startIndex) -stringValue.LastOrDefault() | RIGHT(@stringValue, 1) -stringValue.Length | LENGTH(@stringValue) -stringValue.Replace(oldValue, newValue) | REPLACE(@stringValue, @oldValue, @newValue) -stringValue.StartsWith(value) | STARTSWITH(@stringValue, @value) -stringValue.Substring(startIndex) | SUBSTRING(@stringValue, @startIndex, LENGTH(@stringValue)) -stringValue.Substring(startIndex, length) | SUBSTRING(@stringValue, @startIndex, @length) -stringValue.ToLower() | LOWER(@stringValue) -stringValue.ToUpper() | UPPER(@stringValue) -stringValue.Trim() | TRIM(@stringValue) -stringValue.TrimEnd() | RTRIM(@stringValue) -stringValue.TrimStart() | LTRIM(@stringValue) - -## Miscellaneous functions - -.NET | SQL ---------------------------|---- -collection.Contains(item) | @item IN @collection diff --git a/entity-framework/core/providers/cosmos/index.md b/entity-framework/core/providers/cosmos/index.md index 1e13188251..b3f7cbac98 100644 --- a/entity-framework/core/providers/cosmos/index.md +++ b/entity-framework/core/providers/cosmos/index.md @@ -7,6 +7,9 @@ uid: core/providers/cosmos/index --- # EF Core Azure Cosmos DB Provider +> [!WARNING] +> Extensive work has gone into the Cosmos DB provider in 9.0. In order to improve the provider, a number of high-impact breaking changes had to be made; if you are upgrading an existing application, please read the [breaking changes section](xref:core/what-is-new/ef-core-9.0/breaking-changes#cosmos-breaking-changes) carefully. + 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/dotnet/efcore). It is strongly recommended to familiarize yourself with the [Azure Cosmos DB documentation](/azure/cosmos-db/introduction) before reading this section. @@ -70,374 +73,10 @@ The Azure Cosmos DB provider for EF Core has multiple overloads of the [UseCosmo | Account endpoint and token | `UseCosmos(accountEndpoint, tokenCredential, databaseName)` | [Resource tokens](/azure/cosmos-db/secure-access-to-data#primary-keys) | | Connection string | `UseCosmos(connectionString, databaseName)` | [Work with account keys and connection strings](/azure/cosmos-db/scripts/cli/common/keys) | -## Queries - -### LINQ queries - -[EF Core LINQ queries](xref:core/querying/index) can be executed against Azure Cosmos DB in the same way as for other database providers. For example: - - -[!code-csharp[StringTranslations](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosQueriesSample.cs?name=StringTranslations)] - -> [!NOTE] -> The Azure Cosmos DB provider does not translate the same set of LINQ queries as other providers. See [_Limitations_](xref:core/providers/cosmos/limitations) for more information. - -### SQL queries - -Queries can also be written [directly in SQL](xref:core/querying/sql-queries). For example: - - -[!code-csharp[FromSql](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosQueriesSample.cs?name=FromSql)] - -This query results in the following query execution: - -```sql -SELECT c -FROM ( - SELECT * FROM root c WHERE c["Angle1"] <= @p0 OR c["Angle2"] <= @p0 -) c -``` - -Just like for relational `FromSql` queries, the hand written SQL can be further composed using LINQ operators. For example: - - -[!code-csharp[FromSqlComposed](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosQueriesSample.cs?name=FromSqlComposed)] - -This combination of SQL and LINQ is translated to: - -```sql -SELECT DISTINCT c["Angle1"] -FROM ( - SELECT * FROM root c WHERE c["Angle1"] <= @p0 OR c["Angle2"] <= @p0 -) c -WHERE (c["InsertedOn"] <= GetCurrentDateTime()) -``` - ## Azure Cosmos DB options It is also possible to configure the Azure Cosmos DB provider with a single connection string and to specify other options to customize the connection: [!code-csharp[Configuration](../../../../samples/core/Cosmos/ModelBuilding/OptionsContext.cs?name=Configuration)] -> [!TIP] -> The code above shows possible options. It is not intended that these will all be used at the same time! See the [Azure Cosmos DB Options documentation](/dotnet/api/microsoft.azure.cosmos.cosmosclientoptions) for a detailed description of the effect of each option mentioned above. - -## Cosmos-specific model customization - -By default all entity types are mapped to the same container, named after the derived context (`"OrderContext"` in this case). To change the default container name use [HasDefaultContainer](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosModelBuilderExtensions.HasDefaultContainer): - -[!code-csharp[DefaultContainer](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=DefaultContainer)] - -To map an entity type to a different container use [ToContainer](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosEntityTypeBuilderExtensions.ToContainer): - -[!code-csharp[Container](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=Container)] - -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](xref:core/modeling/inheritance). - -If no other entity type will ever be stored in the same container the discriminator can be removed by calling [HasNoDiscriminator](/dotnet/api/Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder.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 DB, a [carefully selected partition key](/azure/cosmos-db/partition-data) should be used. It can be configured by calling [HasPartitionKey](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosEntityTypeBuilderExtensions.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. A query can be made single-partition by adding a call. - -[!code-csharp[PartitionKey](../../../../samples/core/Cosmos/ModelBuilding/Sample.cs?name=PartitionKey&highlight=14)] - -It is generally recommended to add the partition key to the primary key as that best reflects the server semantics and allows some optimizations, for example in `FindAsync`. - -### Provisioned throughput - -If you use EF Core to create the Azure Cosmos DB database or containers you can configure [provisioned throughput](/azure/cosmos-db/set-throughput) for the database by calling or . For example: - - -[!code-csharp[ModelThroughput](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=ModelThroughput)] - -To configure provisioned throughput for a container call or . For example: - - -[!code-csharp[EntityTypeThroughput](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=EntityTypeThroughput)] - -### Time-to-live - -Entity types in the Azure Cosmos DB model can now be configured with a default time-to-live. For example: - -```csharp -modelBuilder.Entity().HasDefaultTimeToLive(3600); -``` - -Or, for the analytical store: - -```csharp -modelBuilder.Entity().HasAnalyticalStoreTimeToLive(3600); -``` - -Time-to-live for individual entities can be set using a property mapped to "ttl" in the JSON document. For example: - - -[!code-csharp[TimeToLiveProperty](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=TimeToLiveProperty)] - -> [!NOTE] -> A default time-to-live must configured on the entity type for the "ttl" to have any effect. See [_Time to Live (TTL) in Azure Cosmos DB_](/azure/cosmos-db/nosql/time-to-live) for more information. - -The time-to-live property is then set before the entity is saved. For example: - - -[!code-csharp[SetTtl](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=SetTtl)] - -The time-to-live property can be a [shadow property](xref:core/modeling/shadow-properties) to avoid polluting the domain entity with database concerns. For example: - - -[!code-csharp[TimeToLiveShadowProperty](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=TimeToLiveShadowProperty)] - -The shadow time-to-live property is then set by [accessing the tracked entity](xref:core/change-tracking/entity-entries). For example: - - -[!code-csharp[SetTtlShadow](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=SetTtlShadow)] - -## Embedded entities - -> [!NOTE] -> Related entity types are configured as owned by default. To prevent this for a specific entity type call . - -For Azure Cosmos DB, owned entities are embedded in the same item as the owner. To change a property name use [ToJsonProperty](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosEntityTypeBuilderExtensions.ToJsonProperty): - -[!code-csharp[PropertyNames](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=PropertyNames)] - -With this configuration the order from the example above is stored like this: - -```json -{ - "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 -} -``` - -Collections of owned entities are embedded as well. For the next example we'll use the `Distributor` class with a collection of `StreetAddress`: - -[!code-csharp[Distributor](../../../../samples/core/Cosmos/ModelBuilding/Distributor.cs?name=Distributor)] - -The owned entities don't need to provide explicit key values to be stored: - -[!code-csharp[OwnedCollection](../../../../samples/core/Cosmos/ModelBuilding/Sample.cs?name=OwnedCollection)] - -They will be persisted in this way: - -```json -{ - "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 -} -``` - -Internally EF Core always needs to have unique key values for all tracked entities. The primary key created by default for collections of owned types consists of the foreign key properties pointing to the owner and an `int` property corresponding to the index in the JSON array. To retrieve these values entry API could be used: - -[!code-csharp[ImpliedProperties](../../../../samples/core/Cosmos/ModelBuilding/Sample.cs?name=ImpliedProperties)] - -> [!TIP] -> When necessary the default primary key for the owned entity types can be changed, but then key values should be provided explicitly. - -### Collections of primitive types - -Collections of supported primitive types, such as `string` and `int`, are discovered and mapped automatically. Supported collections are all types that implement or . For example, consider this entity type: - - -[!code-csharp[BookEntity](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosPrimitiveTypesSample.cs?name=BookEntity)] - -Both the list and the dictionary can be populated and inserted into the database in the normal way: - - -[!code-csharp[Insert](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosPrimitiveTypesSample.cs?name=Insert)] - -This results in the following JSON document: - -```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 -} -``` - -These collections can then be updated, again in the normal way: - - -[!code-csharp[Updates](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosPrimitiveTypesSample.cs?name=Updates)] - -Limitations: - -* Only dictionaries with string keys are supported -* Querying into the contents of primitive collections is not currently supported. Vote for [#16926](https://github.com/dotnet/efcore/issues/16926), [#25700](https://github.com/dotnet/efcore/issues/25700), and [#25701](https://github.com/dotnet/efcore/issues/25701) if these features are important to you. - -## Working with disconnected entities - -Every item needs to have an `id` value that is unique for the given partition key. By default EF Core generates the value by concatenating the discriminator and the primary key values, using '|' as a delimiter. The key values are only generated when an entity enters the `Added` state. This might pose a problem when [attaching entities](xref:core/saving/disconnected-entities) if they don't have an `id` property on the .NET type to store the value. - -To work around this limitation one could create and set the `id` value manually or mark the entity as added first, then changing it to the desired state: - -[!code-csharp[Attach](../../../../samples/core/Cosmos/ModelBuilding/Sample.cs?highlight=4&name=Attach)] - -This is the resulting JSON: - -```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 -} -``` - -## Optimistic concurrency with eTags - -To configure an entity type to use [optimistic concurrency](xref:core/saving/concurrency) call . This call will create an `_etag` property in [shadow state](xref:core/modeling/shadow-properties) and set it as the concurrency token. - -[!code-csharp[Main](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=ETag)] - -To make it easier to resolve concurrency errors you can map the eTag to a CLR property using . - -[!code-csharp[Main](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=ETagProperty)] +The code above shows some possible options - these are not intended to be used at the same time. See the [Azure Cosmos DB Options documentation](/dotnet/api/microsoft.azure.cosmos.cosmosclientoptions) for a detailed description of the effect of each option mentioned above. diff --git a/entity-framework/core/providers/cosmos/limitations.md b/entity-framework/core/providers/cosmos/limitations.md index 446e0aee1e..bd1ad6187e 100644 --- a/entity-framework/core/providers/cosmos/limitations.md +++ b/entity-framework/core/providers/cosmos/limitations.md @@ -17,6 +17,8 @@ Common EF Core patterns that either do not apply, or are a pit-of-failure, when - Loading graphs of related entities from different documents is not supported. Document databases are not designed to perform joins across many documents; doing so would be very inefficient. Instead, it is more common to denormalize data so that everything needed is in one, or a small number, of documents. However, there are some forms of cross-document relationships that could be handled--see [Limited Include support for Cosmos](https://github.com/dotnet/efcore/issues/16920#issuecomment-989721078). > [!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 +> The Cosmos SDK, which the EF provider uses, does not support synchronous I/O. As a result, synchronous EF APIs such as `ToList` or `SaveChanges` throw in version 9.0 and above; always use asynchronous +> methods when using EF. +> Previous versions of EF supported the synchronous APIs by calling `.Wait()` on the returned `Task`; this is known as "sync over async", and is a highly discouraged technique that can lead to deadlocks. See the EF 9.0 [breaking change note](xref:core/what-is-new/ef-core-9.0/breaking-changes#cosmos-nosync) for more information. Beyond the differences in relational and document databases, and limitations in the SDK, the EF Core provider for Azure Cosmos DB NoSQL does not include everything that _could_ be implemented using the combination of EF Core and the Cosmos SDK. Potential enhancements in this area are tracked by [issues in the EF Core GitHub repo marked with the label `area-cosmos`](https://github.com/dotnet/efcore/issues?q=is%3Aopen+is%3Aissue+label%3Aarea-cosmos+sort%3Areactions-%2B1-desc+label%3Atype-enhancement) The best way to indicate the importance of an issue is to vote (👍) for it. This data will then feed into the [planning process](xref:core/what-is-new/release-planning) for the next release. diff --git a/entity-framework/core/providers/cosmos/modeling.md b/entity-framework/core/providers/cosmos/modeling.md new file mode 100644 index 0000000000..b10e147b86 --- /dev/null +++ b/entity-framework/core/providers/cosmos/modeling.md @@ -0,0 +1,334 @@ +--- +title: Modeling - Azure Cosmos DB Provider - EF Core +description: Configuring the model with the Azure Cosmos DB EF Core Provider +author: roji +ms.date: 09/19/2024 +uid: core/providers/cosmos/modeling +--- +# Configuring the model with the EF Core Azure Cosmos DB Provider + +## Containers and entity types + +In Azure Cosmos DB, JSON documents are stored in containers. Unlike tables in relational databases, Cosmos DB containers can contain documents with different shapes - a container does not impose a uniform schema on its documents. However, various configuration options are defined at the container level, and therefore affect all documents contained within it. See the [Cosmos DB documentation on containers](/azure/cosmos-db/resource-model) for more information. + +By default, EF maps all entity types to the same container; this is usually a good default in terms of performance and pricing. The default container is named after the your .NET context type (`OrderContext` in this case). To change the default container name, use : + +```csharp +modelBuilder.HasDefaultContainer("Store"); +``` + +To map an entity type to a different container use : + +```csharp +modelBuilder.Entity().ToContainer("Orders"); +``` + +Before mapping entity types to different containers, make sure you understand the potential performance and pricing implications (e.g. with regards to dedicated and shared throughput); [see the Cosmos DB documentation to learn more](/azure/cosmos-db/resource-model). + +## IDs and keys + +Cosmos DB requires all documents to have an `id` JSON property which uniquely identifies them. Like other EF providers, the EF Cosmos provider will attempt to find a property named `Id` or `Id`, and configure that property as the key of your entity type, mapping it to the `id` JSON property. You can configure any property to be the key property by using ; see [the general EF documentation on keys](xref:core/modeling/keys) for more information. + +Developers coming to Cosmos DB from other database sometimes expect the key (`Id`) property to be generated automatically. For example, on SQL Server, EF configures numeric key properties to be IDENTITY columns, where auto-incrementing values are generated in the database. In contrast, Cosmos DB does not support automatic generation of properties, and so key properties must be explicitly set. Inserting an entity type with an unset key property will simply insert the CLR default value for that property (e.g. 0 for `int`), and a second insert will fail; EF issues a warning if you attempt to do this. + +If you'd like to have a GUID as your key property, you can configure EF to generate random values at the client: + +```csharp +modelBuilder.Entity().Property(b => b.Id).HasValueGenerator(); +``` + +## Discriminators + +Since multiple entity types may be mapped to the same container, EF Core always adds a `$type` discriminator property to all JSON documents you save (this property was called `Discriminator` before EF 9.0); this allows EF to recognize documents being loaded from the database, and materialize the right .NET type. Developers coming from relational databases may be familiar with discriminators in the context of [table-per-hierarchy inheritance (TPH)](xref:core/modeling/inheritance#table-per-hierarchy-and-discriminator-configuration); in Cosmos, discriminators are used not just in inheritance mapping scenarios, but also because the same container can contain completely different document types. + +The discriminator property name and values can be configured with the standard EF APIs, [see these docs for more information](xref:core/modeling/inheritance). If you're mapping a single entity type to a container, are confident that you'll never be mapping another one, and would like to get rid of the discriminator property, call [HasNoDiscriminator](/dotnet/api/Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder.HasNoDiscriminator): + +```csharp +modelBuilder.Entity().HasNoDiscriminator(); +``` + +Since the same container can contain entity types of different types, and the JSON `id` property must be unique within a container partition, you cannot have the same `id` value for entities of different types in the same container partition. Compare this to relational databases, where each entity type is mapped to a different table, and therefore has its own, separate key space. It is therefore your responsibility to ensure the `id` uniqueness of documents you insert into a container. If you need to have different entity types with the same `id` values, you can instruct EF to automatically insert the discriminator into the `id` property as follows: + +```csharp +modelBuilder.Entity().HasDiscriminatorInJsonId(); +``` + +While this may make it easier to work with `id` values, it may make it harder to interoperate with external applications working with your documents, as they now must be aware of EF's concatenated `id` format, as well as the discriminator values, which are by default derived from your .NET types. Note that this was the default behavior prior to EF 9.0. + +An additional option is to instruct EF to insert only the _root discriminator_, which is the discriminator of the root entity type of the hierarchy, into the `id` property: + +```csharp +modelBuilder.Entity().HasRootDiscriminatorInJsonId(); +``` + +This is similar, but allows EF to use efficient [point reads](xref:core/providers/cosmos/querying#point-reads) in more scenarios. If you need to insert a discriminator into the `id` property, consider inserting the root discriminator for better performance. + +## Partition keys + +Azure Cosmos DB uses partitioning to achieve horizontal scaling; proper modeling and careful selection of the partition key is vital for achieving good performance and keeping costs down. It's highly recommended to read [the Cosmos DB documentation on partitioning](/azure/cosmos-db/partition-data) and to plan your partitioning strategy in advance. + +To configure the partitioning key with EF, call [HasPartitionKey](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosEntityTypeBuilderExtensions.HasPartitionKey), passing it a regular property on your entity type: + +```csharp +modelBuilder.Entity().HasPartitionKey(o => o.PartitionKey); +``` + +Any property can be made into a partition key 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; trying to insert a new entity type with an unset partition key property will result in an error. + +Note that the partition key is defined at the container level. This notably means that it's not possible to map multiple entity types to the same container, and for those entity types to have different partition keys. If you need to define different partition keys, map the relevant entity types to different containers. + +### Hierarchical partition keys + +Cosmos DB also supports _hierarchical_ partition keys to optimize data distribution even further; [see the Cosmos DB documentation for more details](/azure/cosmos-db/hierarchical-partition-keys). EF 9.0 added support for hierarchical partition keys; to configure these, simply pass up to 3 properties to [HasPartitionKey](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosEntityTypeBuilderExtensions.HasPartitionKey): + +```csharp +modelBuilder.Entity().HasPartitionKey(o => new { e.TenantId, e.UserId, e.SessionId }); +``` + +With such a hierarchical partition key, queries can be easily sent only to the a relevant subset of sub-partitions. For example, if you query for the Orders of a specific tenant, those queries will only be executed against the sub-partitions for that tenant. + +If you don't configure a partition key with EF, a warning will be logged at startup; EF Core will create containers with the partition key set to `__partitionKey`, and won't supply any value for it when inserting items. While this can work and may be a good way to start as you're exploring Cosmos DB and your data modeling, it is highly discouraged to deploy a production application without a well-configured partition key strategy. + +Once your partition keys properties are properly configured, you can provide values for them in queries; see [Querying with partition keys](xref:core/providers/cosmos/querying#partition-keys) for more information. + +## Provisioned throughput + +If you use EF Core to create the Azure Cosmos DB database or containers you can configure [provisioned throughput](/azure/cosmos-db/set-throughput) for the database by calling or . For example: + + +[!code-csharp[ModelThroughput](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=ModelThroughput)] + +To configure provisioned throughput for a container call or . For example: + + +[!code-csharp[EntityTypeThroughput](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=EntityTypeThroughput)] + +## Time-to-live + +Entity types in the Azure Cosmos DB model can now be configured with a default time-to-live. For example: + +```csharp +modelBuilder.Entity().HasDefaultTimeToLive(3600); +``` + +Or, for the analytical store: + +```csharp +modelBuilder.Entity().HasAnalyticalStoreTimeToLive(3600); +``` + +Time-to-live for individual entities can be set using a property mapped to "ttl" in the JSON document. For example: + + +[!code-csharp[TimeToLiveProperty](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=TimeToLiveProperty)] + +> [!NOTE] +> A default time-to-live must configured on the entity type for the "ttl" to have any effect. See [_Time to Live (TTL) in Azure Cosmos DB_](/azure/cosmos-db/nosql/time-to-live) for more information. + +The time-to-live property is then set before the entity is saved. For example: + + +[!code-csharp[SetTtl](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=SetTtl)] + +The time-to-live property can be a [shadow property](xref:core/modeling/shadow-properties) to avoid polluting the domain entity with database concerns. For example: + + +[!code-csharp[TimeToLiveShadowProperty](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=TimeToLiveShadowProperty)] + +The shadow time-to-live property is then set by [accessing the tracked entity](xref:core/change-tracking/entity-entries). For example: + + +[!code-csharp[SetTtlShadow](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=SetTtlShadow)] + +## Embedded entities + +> [!NOTE] +> Related entity types are configured as owned by default. To prevent this for a specific entity type call . + +For Azure Cosmos DB, owned entities are embedded in the same item as the owner. To change a property name use [ToJsonProperty](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosEntityTypeBuilderExtensions.ToJsonProperty): + +[!code-csharp[PropertyNames](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=PropertyNames)] + +With this configuration the order from the example above is stored like this: + +```json +{ + "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 +} +``` + +Collections of owned entities are embedded as well. For the next example we'll use the `Distributor` class with a collection of `StreetAddress`: + +[!code-csharp[Distributor](../../../../samples/core/Cosmos/ModelBuilding/Distributor.cs?name=Distributor)] + +The owned entities don't need to provide explicit key values to be stored: + +[!code-csharp[OwnedCollection](../../../../samples/core/Cosmos/ModelBuilding/Sample.cs?name=OwnedCollection)] + +They will be persisted in this way: + +```json +{ + "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 +} +``` + +Internally EF Core always needs to have unique key values for all tracked entities. The primary key created by default for collections of owned types consists of the foreign key properties pointing to the owner and an `int` property corresponding to the index in the JSON array. To retrieve these values entry API could be used: + +[!code-csharp[ImpliedProperties](../../../../samples/core/Cosmos/ModelBuilding/Sample.cs?name=ImpliedProperties)] + +> [!TIP] +> When necessary the default primary key for the owned entity types can be changed, but then key values should be provided explicitly. + +### Collections of primitive types + +Collections of supported primitive types, such as `string` and `int`, are discovered and mapped automatically. Supported collections are all types that implement or . For example, consider this entity type: + + +[!code-csharp[BookEntity](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosPrimitiveTypesSample.cs?name=BookEntity)] + +Both the list and the dictionary can be populated and inserted into the database in the normal way: + + +[!code-csharp[Insert](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosPrimitiveTypesSample.cs?name=Insert)] + +This results in the following JSON document: + +```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 +} +``` + +These collections can then be updated, again in the normal way: + + +[!code-csharp[Updates](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosPrimitiveTypesSample.cs?name=Updates)] + +Limitations: + +* Only dictionaries with string keys are supported. +* Support for querying into primitive collections was added in EF Core 9.0. + +## Optimistic concurrency with eTags + +To configure an entity type to use [optimistic concurrency](xref:core/saving/concurrency) call . This call will create an `_etag` property in [shadow state](xref:core/modeling/shadow-properties) and set it as the concurrency token. + +[!code-csharp[Main](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=ETag)] + +To make it easier to resolve concurrency errors you can map the eTag to a CLR property using . + +[!code-csharp[Main](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=ETagProperty)] diff --git a/entity-framework/core/providers/cosmos/querying.md b/entity-framework/core/providers/cosmos/querying.md new file mode 100644 index 0000000000..793faeabae --- /dev/null +++ b/entity-framework/core/providers/cosmos/querying.md @@ -0,0 +1,312 @@ +--- +title: Querying - Azure Cosmos DB Provider - EF Core +description: Querying with the Azure Cosmos DB EF Core Provider +author: roji +ms.date: 09/19/2024 +uid: core/providers/cosmos/querying +--- +# Querying with the EF Core Azure Cosmos DB Provider + +## Querying basics + +[EF Core LINQ queries](xref:core/querying/index) can be executed against Azure Cosmos DB in the same way as for other database providers. For example: + +```csharp +public class Session +{ + public Guid Id { get; set; } + public string Category { get; set; } + + public string TenantId { get; set; } = null!; + public Guid UserId { get; set; } + public int SessionId { get; set; } +} + +var stringResults = await context.Sessions + .Where( + e => e.Category.Length > 4 + && e.Category.Trim().ToLower() != "disabled" + && e.Category.TrimStart().Substring(2, 2).Equals("xy", StringComparison.OrdinalIgnoreCase)) + .ToListAsync(); +``` + +> [!NOTE] +> The Azure Cosmos DB provider does not translate the same set of LINQ queries as other providers. +> For example, the EF `Include()` operator isn't supported on Cosmos, since cross-document queries aren't supported in the database. + +## Partition keys + +The advantage of partitioning is to have your queries execute only against the partition where the relevant data is found, saving costs and ensuring faster result speed. Queries which don't specify partition keys are executed on all the partitions, which can be quite costly. + +Starting with EF 9.0, EF automatically detects and extracts partition key comparisons in your LINQ query's `Where` operators. Let's assume we execute the following query against our `Session` entity type, which is configured with a hierarchical partition key: + +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.Entity() + .HasPartitionKey(b => new { b.TenantId, b.UserId, b.SessionId }) +} + +var tenantId = "Microsoft"; +var userId = new Guid("99A410D7-E467-4CC5-92DE-148F3FC53F4C"); +var username = "scott"; + +var sessions = await context.Sessions + .Where( + e => e.TenantId == tenantId + && e.UserId == userId + && e.SessionId > 0 + && e.Username == username) + .ToListAsync(); +``` + +Examining the logs generated by EF, we see this query executed as follows: + +```sql +Executed ReadNext (166.6985 ms, 2.8 RU) ActivityId='312da0d2-095c-4e73-afab-27072b5ad33c', Container='test', Partition='["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c"]', Parameters=[] +SELECT VALUE c +FROM root c +WHERE ((c["SessionId"] > 0) AND CONTAINS(c["Username"], "a")) +``` + +In these logs, we notice the following: + +* The first two comparisons - on `TenantId` and `UserId` - have been lifted out, and appear in the ReadNext's "Partition" rather than in the `WHERE` clause; this means that query will only execute on the subpartitions for those values. +* `SessionId` is also part of the hierarchical partition key, but instead of an equality comparison, it uses a greater-than operator (`>`), and therefore cannot be lifted out. It is part of the `WHERE` clause like any regular property. +* `Username` is a regular property - not part of the partition key - and therefore remains in the `WHERE` clause as well. + +Note that even though not all three partition key properties are provided, hierarchical partition keys still allow targeting only the subpartitions which correspond to the first two properties. While this isn't as efficient as targeting a single partition (as identified by all three properties), it's still much more efficient than targeting all partitions. + +Rather than referencing partition key properties in a `Where` operator, you can explicitly specify them by using the operator: + +```c# +var sessions = await context.Sessions + .WithPartitionKey(tenantId, userId) + .Where(e => e.SessionId > 0 && e.Username.Contains("a")) + .ToListAsync(); +``` + +This executes in the same way as the above query, and can be preferable if you want to make partition keys more explicit in your queries. Using may be necessary in versions of EF prior to 9.0 - keep an eye on the logs to ensure that queries are using partition keys as expected. + +## Point reads + +While Azure Cosmos DB allows for powerful querying via SQL, such queries can be quite expensive. Cosmos DB also supports _point reads_, which can be used when both the `id` property and the entire partition key are known. Such point reads directly identify a specific document in a specific partition, and execute extremely efficiently and with reduced costs. If at all possible, it's worth designing your system in a way which leverages point reads as much as possible. To read more, see the [Cosmos DB documentation](/azure/cosmos-db/nosql/how-to-dotnet-read-item). + +In the previous section, we saw EF identifying and extracting partition key comparisons from the `Where` clause for more efficient querying, restricting processing only to the relevant partitions. It's possible to go a step further, and provide the `id` property in the query as well. Let's examine the following query: + +```c# +var session = await context.Sessions.SingleAsync( + e => e.Id == someId + && e.TenantId == tenantId + && e.UserId == userId + && e.SessionId == sessionId); +``` + +In this query, a value for the `Id` property is provided (which is mapped to the Cosmos DB `id` property), as well as values for all the partition key properties. Furthermore, there are no additional components to the query. When all these conditions are met, EF is able to execute the query as a point read: + +```console +Executed ReadItem (46 ms, 1 RU) ActivityId='d7391311-2266-4811-ae2d-535904c42c43', Container='test', Id='9', Partition='["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c",10.0]' +``` + +Note the `ReadItem`, which indicates that the query was executed as an efficient point read - no SQL query is involved. + +Note that as with partition key extraction, significant improvements have been made to this mechanism in EF 9.0; older versions do not reliably detect and use point reads. + +## Pagination + +> [!NOTE] +> This feature was introduced in EF Core 9.0. + +Pagination refers to retrieving results in pages, rather than all at once; this is typically done for large resultsets, where a user interface is displayed, allowing users to navigate through pages of the results. + +A common way to implement pagination with databases is to use the `Skip` and `Take` LINQ operators (`OFFSET` and `LIMIT` in SQL). Given a page size of 10 results, the third page can be fetched with EF Core as follows: + +```csharp +var position = 20; +var nextPage = context.Session + .OrderBy(s => s.Id) + .Skip(position) + .Take(10) + .ToList(); +``` + +Unfortunately, this technique is quite inefficient and can considerably increase querying costs. Cosmos DB provides a special mechanism for paginating through the result of a query, via the use of _continuation tokens_: + +```csharp +CosmosPage firstPage = await context.Sessions + .OrderBy(s => s.Id) + .ToPageAsync(pageSize: 10, continuationToken: null); + +string continuationToken = page.ContinuationToken; +foreach (var session in firstPage.Values) +{ + // Display/send the sessions to the user +} +``` + +Rather than terminating the LINQ query with `ToListAsync` or similar, we use the `ToPageAsync` method, instructing it to get at most 10 items in every page (note that there may be fewer items in the database). Since this is our first query, we'd like to get results from the beginning, and pass `null` as the continuation token. `ToPageAsync` returns a `CosmosPage`, which exposes a continuation token and the values in the page (up to 10 items). Your program will typically send those values to the client, along with the continuation token; this will allow resuming the query later and fetching more results. + +Let's assume the user now clicks on the "Next" button in their UI, asking for the next 10 items. You can then execute the query as follows: + +```csharp +CosmosPage nextPage = await context.Sessions.OrderBy(s => s.Id).ToPageAsync(10, continuationToken); +string continuationToken = nextPage.ContinuationToken; +foreach (var session in nextPage.Values) +{ + // Display/send the sessions to the user +} +``` + +We execute the same query, but this time we pass in the continuation token received from the first execution; this instructs Cosmos DB to continue the query where it left off, and fetch the next 10 items. This method of paginating is extremely efficient and cost-effective compared to using `Skip` and `Take`. + +To learn more about pagination in Cosmos DB, [see this page](/azure/cosmos-db/nosql/query/pagination). + +> [!NOTE] +> Cosmos DB does not support backwards pagination. +> +> `ToPageAsync` is currently annotated as experimental, since it may be replaced with a more general EF pagination API that isn't Cosmos-specific. Although using the current API will generate a compilation warning (`EF9102`), doing so should be safe - future changes may require minor tweaks in the API shape. + +## `FindAsync` + +[`FindAsync`](xref:core/change-tracking/entity-entries#find-and-findasync) is a useful API for getting a an entity by its primary key, and avoiding a database roundtrip when the entity has already been loaded and is tracked by the context. + +Usually, the primary key of an entity type consists e.g. of an `Id` property. When using the EF Cosmos DB provider, the primary key also contains the partition key properties; this is the case since Cosmos DB allows different partitions to contain documents with the same JSON `id` property, and so only the combined `id` and partition key uniquely identify a single document in a container: + +```csharp +public class Session +{ + public Guid Id { get; set; } + public string PartitionKey { get; set; } + ... +} + +var mySession = await context.FindAsync(id, pkey); +``` + +If you have a hierarchical partition key, you must pass all partition key values to `FindAsync`, in the order in which they were configured. + +> [!NOTE] +> Use `FindAsync` only when the entity might already be tracked by your context, and you want to avoid the database roundtrip. +> Otherwise, simply use `SingleAsync` - there is no performance difference between the two when the entity needs to be loaded from the database. + +## SQL queries + +Queries can also be written [directly in SQL](xref:core/querying/sql-queries). For example: + +```csharp +var rating = 3; +_ = await context.Blogs + .FromSql($"SELECT VALUE c FROM root c WHERE c.Rating > {rating}") + .ToListAsync(); +``` + +This query results in the following query execution: + +```sql +SELECT VALUE s +FROM ( + SELECT VALUE c FROM root c WHERE c.Angle1 <= @p0 +) s +``` + +Note that `FromSql` was introduced in EF 9.0. In previous versions, `FromSqlRaw` can be used instead, although note that that method is vulnerable to SQL injection attacks. + +For more information on SQL querying, see the [relational documentation on SQL queries](xref:core/querying/sql-queries); most of that content is relevant for the Cosmos provider as well. + +## Function mappings + +This section shows which .NET methods and members are translated into which SQL functions when querying with the Azure Cosmos DB provider. + +### Date and time functions + +.NET | SQL | Added in +------------------------------------------ | --------------------------------------------------------------------------------- | -------- +DateTime.UtcNow | [GetCurrentDateTime()](/azure/cosmos-db/nosql/query/getcurrentdatetime) +DateTimeOffset.UtcNow | [GetCurrentDateTime()](/azure/cosmos-db/nosql/query/getcurrentdatetime) +dateTime.Year1 | [DateTimePart("yyyy", dateTime)](/azure/cosmos-db/nosql/query/datetimepart) | EF Core 9.0 +dateTimeOffset.Year1 | [DateTimePart("yyyy", dateTimeOffset)](/azure/cosmos-db/nosql/query/datetimepart) | EF Core 9.0 +dateTime.AddYears(years)1 | [DateTimeAdd("yyyy", dateTime)](/azure/cosmos-db/nosql/query/datetimeadd) | EF Core 9.0 +dateTimeOffset.AddYears(years)1 | [DateTimeAdd("yyyy", dateTimeOffset)](/azure/cosmos-db/nosql/query/datetimeadd) | EF Core 9.0 + +1 The other component members are translated as well (Month, Day...). + +### Numeric functions + +.NET | SQL | Added in +-------------------------- | --------------------------------------------------- | -------- +double.DegreesToRadians(x) | [RADIANS(@x)](/azure/cosmos-db/nosql/query/radians) | EF Core 8.0 +double.RadiansToDegrees(x) | [DEGREES(@x)](/azure/cosmos-db/nosql/query/degrees) | EF Core 8.0 +EF.Functions.Random() | [RAND()](/azure/cosmos-db/nosql/query/rand) +Math.Abs(value) | [ABS(@value)](/azure/cosmos-db/nosql/query/abs) +Math.Acos(d) | [ACOS(@d)](/azure/cosmos-db/nosql/query/acos) +Math.Asin(d) | [ASIN(@d)](/azure/cosmos-db/nosql/query/asin) +Math.Atan(d) | [ATAN(@d)](/azure/cosmos-db/nosql/query/atan) +Math.Atan2(y, x) | [ATN2(@y, @x)](/azure/cosmos-db/nosql/query/atn2) +Math.Ceiling(d) | [CEILING(@d)](/azure/cosmos-db/nosql/query/ceiling) +Math.Cos(d) | [COS(@d)](/azure/cosmos-db/nosql/query/cos) +Math.Exp(d) | [EXP(@d)](/azure/cosmos-db/nosql/query/exp) +Math.Floor(d) | [FLOOR(@d)](/azure/cosmos-db/nosql/query/floor) +Math.Log(a, newBase) | [LOG(@a, @newBase)](/azure/cosmos-db/nosql/query/log) +Math.Log(d) | [LOG(@d)](/azure/cosmos-db/nosql/query/log) +Math.Log10(d) | [LOG10(@d)](/azure/cosmos-db/nosql/query/log10) +Math.Pow(x, y) | [POWER(@x, @y)](/azure/cosmos-db/nosql/query/power) +Math.Round(d) | [ROUND(@d)](/azure/cosmos-db/nosql/query/round) +Math.Sign(value) | [SIGN(@value)](/azure/cosmos-db/nosql/query/sign) +Math.Sin(a) | [SIN(@a)](/azure/cosmos-db/nosql/query/sin) +Math.Sqrt(d) | [SQRT(@d)](/azure/cosmos-db/nosql/query/sqrt) +Math.Tan(a) | [TAN(@a)](/azure/cosmos-db/nosql/query/atan) +Math.Truncate(d) | [TRUNC(@d)](/azure/cosmos-db/nosql/query/trunc) + +> [!TIP] +> In addition to the methods listed here, corresponding [generic math](/dotnet/standard/generics/math) implementations +> and [MathF](/dotnet/api/system.mathf) methods are also translated. For example, `Math.Sin`, `MathF.Sin`, `double.Sin`, +> and `float.Sin` all map to the `SIN` function in SQL. + +### String functions + +.NET | SQL | Added in +----------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------- +Regex.IsMatch(input, pattern) | [RegexMatch(@pattern, @input)](/azure/cosmos-db/nosql/query/regexmatch) | EF Core 7.0 +Regex.IsMatch(input, pattern, options) | [RegexMatch(@input, @pattern, @options)](/azure/cosmos-db/nosql/query/regexmatch) | EF Core 7.0 +string.Concat(str0, str1) | @str0 + @str1 +string.Equals(a, b, StringComparison.Ordinal) | [STRINGEQUALS(@a, @b)](/azure/cosmos-db/nosql/query/stringequals) +string.Equals(a, b, StringComparison.OrdinalIgnoreCase) | [STRINGEQUALS(@a, @b, true)](/azure/cosmos-db/nosql/query/stringequals) +stringValue.Contains(value) | [CONTAINS(@stringValue, @value)](/azure/cosmos-db/nosql/query/contains) +stringValue.Contains(value, StringComparison.Ordinal) | [CONTAINS(@stringValue, @value, false)](/azure/cosmos-db/nosql/query/contains) | EF Core 9.0 +stringValue.Contains(value, StringComparison.OrdinalIgnoreCase) | [CONTAINS(@stringValue, @value, true)](/azure/cosmos-db/nosql/query/contains) | EF Core 9.0 +stringValue.EndsWith(value) | [ENDSWITH(@stringValue, @value)](/azure/cosmos-db/nosql/query/endswith) +stringValue.EndsWith(value, StringComparison.Ordinal) | [ENDSWITH(@stringValue, @value, false)](/azure/cosmos-db/nosql/query/endswith) | EF Core 9.0 +stringValue.EndsWith(value, StringComparison.OrdinalIgnoreCase) | [ENDSWITH(@stringValue, @value, true)](/azure/cosmos-db/nosql/query/endswith) | EF Core 9.0 +stringValue.Equals(value, StringComparison.Ordinal) | [STRINGEQUALS(@stringValue, @value)](/azure/cosmos-db/nosql/query/stringequals) +stringValue.Equals(value, StringComparison.OrdinalIgnoreCase) | [STRINGEQUALS(@stringValue, @value, true)](/azure/cosmos-db/nosql/query/stringequals) +stringValue.FirstOrDefault() | [LEFT(@stringValue, 1)](/azure/cosmos-db/nosql/query/left) +stringValue.IndexOf(value) | [INDEX_OF(@stringValue, @value)](/azure/cosmos-db/nosql/query/index-of) +stringValue.IndexOf(value, startIndex) | [INDEX_OF(@stringValue, @value, @startIndex)](/azure/cosmos-db/nosql/query/index-of) +stringValue.LastOrDefault() | [RIGHT(@stringValue, 1)](/azure/cosmos-db/nosql/query/right) +stringValue.Length | [LENGTH(@stringValue)](/azure/cosmos-db/nosql/query/length) +stringValue.Replace(oldValue, newValue) | [REPLACE(@stringValue, @oldValue, @newValue)](/azure/cosmos-db/nosql/query/replace) +stringValue.StartsWith(value) | [STARTSWITH(@stringValue, @value)](/azure/cosmos-db/nosql/query/startswith) +stringValue.StartsWith(value, StringComparison.Ordinal) | [STARTSWITH(@stringValue, @value, false)](/azure/cosmos-db/nosql/query/startswith) | EF Core 9.0 +stringValue.StartsWith(value, StringComparison.OrdinalIgnoreCase) | [STARTSWITH(@stringValue, @value, true)](/azure/cosmos-db/nosql/query/startswith) | EF Core 9.0 +stringValue.Substring(startIndex) | [SUBSTRING(@stringValue, @startIndex, LENGTH(@stringValue))](/azure/cosmos-db/nosql/query/substring) +stringValue.Substring(startIndex, length) | [SUBSTRING(@stringValue, @startIndex, @length)](/azure/cosmos-db/nosql/query/substring) +stringValue.ToLower() | [LOWER(@stringValue)](/azure/cosmos-db/nosql/query/lower) +stringValue.ToUpper() | [UPPER(@stringValue)](/azure/cosmos-db/nosql/query/upper) +stringValue.Trim() | [TRIM(@stringValue)](/azure/cosmos-db/nosql/query/trim) +stringValue.TrimEnd() | [RTRIM(@stringValue)](/azure/cosmos-db/nosql/query/rtrim) +stringValue.TrimStart() | [LTRIM(@stringValue)](/azure/cosmos-db/nosql/query/ltrim) + +### Miscellaneous functions + +.NET | SQL | Notes +--------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----- +collection.Contains(item) | @item IN @collection +EF.Functions.CoalesceUndefined(x, y)1 | [x ?? y](/azure/cosmos-db/nosql/query/ternary-coalesce-operators#coalesce-operator) | Added in EF Core 9.0 +EF.Functions.IsDefined(x) | [IS_DEFINED(x)](/azure/cosmos-db/nosql/query/is-defined) | Added in EF Core 9.0 +EF.Functions.VectorDistance(vector1, vector2)2 | [VectorDistance(vector1, vector2)](/azure/cosmos-db/nosql/query/vectordistance) | Added in EF Core 9.0, Experimental +EF.Functions.VectorDistance(vector1, vector2, bruteForce)2 | [VectorDistance(vector1, vector2, bruteForce)](/azure/cosmos-db/nosql/query/vectordistance) | Added in EF Core 9.0, Experimental +EF.Functions.VectorDistance(vector1, vector2, bruteForce, distanceFunction)2 | [VectorDistance(vector1, vector2, bruteForce, distanceFunction)](/azure/cosmos-db/nosql/query/vectordistance) | Added in EF Core 9.0, Experimental + +1 Note that `EF.Functions.CoalesceUndefined` coalesces `undefined`, not `null`. To coalesce `null`, use the regular C# `??` operator. + +2 [See the documentation](xref:core/providers/cosmos/vector-search) for information on using vector search in Azure Cosmos DB. Cosmos DB vector searching is experimental and the APIs are subject to change. diff --git a/entity-framework/core/providers/cosmos/vector-search.md b/entity-framework/core/providers/cosmos/vector-search.md new file mode 100644 index 0000000000..5027b5f27d --- /dev/null +++ b/entity-framework/core/providers/cosmos/vector-search.md @@ -0,0 +1,58 @@ +--- +title: Vector Search - Azure Cosmos DB Provider - EF Core +description: Vector search with the Azure Cosmos DB EF Core Provider +author: roji +ms.date: 09/20/2024 +uid: core/providers/cosmos/vector-search +--- +# Vector search + +> [!WARNING] +> Azure Cosmos DB vector search is currently in preview. As a result, using EF's vector search APIs will generate an "experimental API" warning (`EF9103`) which must be suppressed. The APIs and capabilities may change in breaking ways in the future. + +Azure Cosmos DB now offers preview support for vector similarity search. Vector search is a fundamental part of some application types, include AI, semantic search and others. The Cosmos DB support for vector search allows storing your data and vectors, and performing your queries in a single database, which can considerably simplify your architecture and remove the need for an additional, dedicated vector database solution in your stack. To learn more about Cosmos DB vector search, [see the documentation](/azure/cosmos-db/nosql/vector-search). + +To use vector search, you must first [enroll in the preview feature](/azure/cosmos-db/nosql/vector-search#enroll-in-the-vector-search-preview-feature). Then, [define the vector policies on your container](/azure/cosmos-db/nosql/vector-search#container-vector-policies), which determine which JSON property in your documents contain vectors, various vector-related information for those properties (dimensions, data type, distance function). + +Once your container is properly set up, add a vector property to your model in the path you defined in the container policy, and configure it with EF as a vector: + +```c# +public class Blog +{ + ... + + public float[] Vector { get; set; } +} + +public class BloggingContext +{ + ... + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .Property(b => b.Embeddings) + .IsVector(DistanceFunction.Cosine, dimensions: 1536); + } +} +``` + +At this point your model is configured. Insertion of vector data is done just like any other data type with EF: + +```c# +float[] vector = /* generate vector data from text, image, etc. */ +context.Add(new Blog { Vector = vector }); +await context.SaveChangesAsync(); +``` + +Finally, use the `EF.Functions.VectorDistance()` function in LINQ queries to perform vector similarity search: + +```c# +float[] anotherVector = /* generate vector data from text, image, etc. */ +var blogs = await context.Blogs + .OrderBy(s => EF.Functions.VectorDistance(s.Vector, anotherVector)) + .Take(5) + .ToListAsync(); +``` + +This will returns the top five Blogs, based on the similarity of their `Vector` property and the externally-provided `anotherVector` data. diff --git a/entity-framework/core/querying/database-functions.md b/entity-framework/core/querying/database-functions.md index 11889d42fb..dda44347f7 100644 --- a/entity-framework/core/querying/database-functions.md +++ b/entity-framework/core/querying/database-functions.md @@ -52,4 +52,4 @@ Apart from mappings provided by EF Core providers, users can also define custom - [SqlServer built-in function mappings](xref:core/providers/sql-server/functions) - [Sqlite built-in function mappings](xref:core/providers/sqlite/functions) -- [Azure Cosmos DB built-in function mappings](xref:core/providers/cosmos/functions) +- [Azure Cosmos DB built-in function mappings](xref:core/providers/cosmos/querying#function-mappings) diff --git a/entity-framework/core/querying/pagination.md b/entity-framework/core/querying/pagination.md index 13937c48c4..75a3a0821f 100644 --- a/entity-framework/core/querying/pagination.md +++ b/entity-framework/core/querying/pagination.md @@ -12,9 +12,12 @@ Pagination refers to retrieving results in pages, rather than all at once; this > [!WARNING] > Regardless of the pagination method used, always make sure that your ordering is fully unique. For example, if results are ordered only by date, but there can be multiple results with the same date, then results could be skipped when paginating as they're ordered differently across two paginating queries. Ordering by both date and ID (or any other unique property or combination of properties) makes the ordering fully unique and avoids this problem. Note that relational databases do not apply any ordering by default, even on the primary key. +> [!NOTE] +> Azure Cosmos DB has its own mechanism for pagination, [see the dedicated documentation page](xref:core/providers/cosmos/querying#pagination). + ## Offset pagination -A common way to implement pagination with databases is to use the `Skip` and `Take` (`OFFSET` and `LIMIT` in SQL). Given a page size of 10 results, the third page can be fetched with EF Core as follows: +A common way to implement pagination with databases is to use the `Skip` and `Take` LINQ operators (`OFFSET` and `LIMIT` in SQL). Given a page size of 10 results, the third page can be fetched with EF Core as follows: [!code-csharp[Main](../../../samples/core/Querying/Pagination/Program.cs?name=OffsetPagination&highlight=4)] diff --git a/entity-framework/core/what-is-new/ef-core-9.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-9.0/breaking-changes.md index 810ee5f2e9..b074810784 100644 --- a/entity-framework/core/what-is-new/ef-core-9.0/breaking-changes.md +++ b/entity-framework/core/what-is-new/ef-core-9.0/breaking-changes.md @@ -20,33 +20,170 @@ EF Core 9 targets .NET 8. This means that existing applications that target .NET ## Summary +> [!NOTE] +> If you are using Azure Cosmos DB, please see the [separate section below on Cosmos DB breaking changes](#cosmos-breaking-changes). + | **Breaking change** | **Impact** | |:-----------------------------------------------------------------------------------------------------|------------| -| [Sync I/O via the Azure Cosmos DB provider is no longer supported](#cosmos-nosync) | Medium | | [EF.Functions.Unhex() now returns `byte[]?`](#unhex) | Low | | [SqlFunctionExpression's nullability arguments' arity validated](#sqlfunctionexpression-nullability) | Low | -## Medium-impact changes +## Low-impact changes + + + +### EF.Functions.Unhex() now returns `byte[]?` + +[Tracking Issue #33864](https://github.com/dotnet/efcore/issues/33864) + +#### Old behavior + +The EF.Functions.Unhex() function was previously annotated to return `byte[]`. + +#### New behavior + +Starting with EF Core 9.0, Unhex() is now annotated to return `byte[]?`. + +#### Why + +Unhex() is translated to the SQLite `unhex` function, which returns NULL for invalid inputs. As a result, Unhex() returned `null` for those cases, in violation of the annotation. + +#### Mitigations + +If you are sure that the text content passed to Unhex() represents a valid, hexadecimal string, you can simply add the null-forgiving operator as an assertion that the invocation will never return null: + +```csharp +var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync(); +``` + +Otherwise, add runtime checks for null on the return value of Unhex(). + + + +### SqlFunctionExpression's nullability arguments' arity validated + +[Tracking Issue #33852](https://github.com/dotnet/efcore/issues/33852) + +#### Old behavior + +Previously it was possible to create a `SqlFunctionExpression` with a different number of arguments and nullability propagation arguments. + +#### New behavior + +Starting with EF Core 9.0, EF now throws if the number of arguments and nullability propagation arguments do not match. + +#### Why + +Not having matching number of arguments and nullability propagation arguments can lead to unexpected behavior. + +#### Mitigations + +Make sure the `argumentsPropagateNullability` has same number of elements as the `arguments`. When in doubt use `false` for nullability argument. + +## Cosmos breaking changes + +Extensive work has gone into making the Cosmos DB provider better in 9.0. The changes include a number of high-impact breaking changes; if you are upgrading an existing application, please read the following carefully. + +| **Breaking change** | **Impact** | +|:---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| [The discriminator property is now named `$type` instead of `Discriminator`](#cosmos-discriminator-name-change) | High | +| [The `id` property no longer contqins the discriminator by default](#cosmos-id-property-changes) | High | +| [Sync I/O via the Azure Cosmos DB provider is no longer supported](#cosmos-nosync) | Medium | +| [SQL queries must now project JSON values directly](#cosmos-sql-queries-with-value) | Medium | +| [Undefined results are now automatically filtered from query results](#cosmos-undefined-filtering) | Medium | +| [Incorrectly translated queries are no longer translated](#cosmos-incorrect-translations) | Medium | +| [`HasIndex` now throws instead of being ignored](#cosmos-hasindex-throws) | Low | +| [`IncludeRootDiscriminatorInJsonId` was renamed to `HasRootDiscriminatorInJsonId` after 9.0.0-rc.2](#cosmos-IncludeRootDiscriminatorInJsonId-rename) | Low | + +### High-impact changes + + + +#### The discriminator property is now named `$type` instead of `Discriminator` + +[Tracking Issue #34269](https://github.com/dotnet/efcore/issues/34269) + +##### Old behavior + +EF automatically adds a discriminator property to JSON documents to identify the entity type that the document represents. In previous versions of EF, this JSON property used to be named `Discriminator` by default. + +##### New behavior + +Starting with EF Core 9.0, the discriminator property is now called `$type` by default. If you have existing documents in Cosmos DB from previous versions of EF, these use the old `Discriminator` naming, and after upgrading to EF 9.0, queries against those documents will fail. + +##### Why + +An emerging JSON practice uses a `$type` property in scenarios where a document's type needs to be identified. For example, .NET's System.Text.Json also supports polymorphism, using `$type` as its default discriminator property name ([docs](/dotnet/standard/serialization/system-text-json/polymorphism#customize-the-type-discriminator-name)). To align with the rest of the ecosystem and make it easier to interoperate with external tools, the default was changed. + +##### Mitigations + +The easiest mitigation is to simply configure the name of the discriminator property to be `Discriminator`, just as before: + +```csharp +modelBuilder.Entity().HasDiscriminator("Discriminator"); +``` + +Doing this for all your top-level entity types will make EF behave just like before. + +At this point, if you wish, you can also update all your documents to use the new `$type` naming. + +TODO: Cosmos SDK code sample for updating the documents + + + +#### The `id` property now contains only the EF key property by default + +[Tracking Issue #34179](https://github.com/dotnet/efcore/issues/34179) + +##### Old behavior + +Previously, EF inserted the discriminator value of your entity type into the `id` property of the document. For example, if you saved a `Blog` entity type with an `Id` property containing 8, the JSON `id` property would contain `Blog|8`. + +##### New behavior + +Starting with EF Core 9.0, the JSON `id` property no longer contains the discriminator value, and only contains the avlue of your key property. For the above example, the JSON `id` property would simply be `8`. If you have existing documents in Cosmos DB from previous versions of EF, these have the discriminator value in the JSON `id` property, and after upgrading to EF 9.0, queries against those documents will fail. + +##### Why + +Since the JSON `id` property must be unique, the discriminator was previously added to it so as to allow different entities to exist with the same key value; for example, this allowed having both a `Blog` and a `Post` with an `Id` property containing 8 within the same container and partition. This was assumed to be expected by developers used to relational databases, where each entity type is mapped its own table, and therefore has its own key-space. + +EF 9.0 generally changed the mapping to be more aligned with common Cosmos DB practices and expectations, rather than to correspond to the expectations of users coming from relational databases. In addition, having the discriminator value in the `id` property made it more difficult for external tools and systems to interact with EF-generated JSON documents; such external systems aren't generally aware of the EF discriminator values, which are by default derived from .NET types. + +##### Mitigations + +The easiest mitigation is to simply configure EF to include the discriminator in the JSON `id` property, as before. A new configuration option has been introduced for this purpose: + +```csharp +modelBuilder.Entity().HasDiscriminatorInJsonId(); +``` + +Doing this for all your top-level entity types will make EF behave just like before. + +At this point, if you wish, you can also update all your documents to rewrite their JSON `id` property. Note that this is only possible if entities of different types don't share the same id value within the same container. + +TODO: Cosmos SDK code sample for updating the documents + +### Medium-impact changes -### Sync I/O via the Azure Cosmos DB provider is no longer supported +#### Sync I/O via the Azure Cosmos DB provider is no longer supported [Tracking Issue #32563](https://github.com/dotnet/efcore/issues/32563) -#### Old behavior +##### Old behavior Previously, calling synchronous methods like `ToList` or `SaveChanges` would cause EF Core to block synchronously using `.GetAwaiter().GetResult()` when executing async calls against the Azure Cosmos DB SDK. This can result in deadlock. -#### New behavior +##### New behavior Starting with EF Core 9.0, EF now throws by default when attempting to use synchronous I/O. The exception message is "Azure Cosmos DB does not support synchronous I/O. Make sure to use and correctly await only async methods when using Entity Framework Core to access Azure Cosmos DB. See [https://aka.ms/ef-cosmos-nosync](https://aka.ms/ef-cosmos-nosync) for more information." -#### Why +##### Why Synchronous blocking on asynchronous methods can result in deadlock, and the Azure Cosmos DB SDK only supports async methods. -#### Mitigations +##### Mitigations In EF Core 9.0, the error can be suppressed with: @@ -59,54 +196,213 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) That being said, applications should stop using sync APIs with Azure Cosmos DB since this is not supported by the Azure Cosmos DB SDK. The ability to suppress the exception will be removed in a future release of EF Core, after which the only option will be to use async APIs. -## Low-impact changes + - +#### SQL queries must now project JSON values directly -### EF.Functions.Unhex() now returns `byte[]?` +[Tracking Issue #25527](https://github.com/dotnet/efcore/issues/25527) -[Tracking Issue #33864](https://github.com/dotnet/efcore/issues/33864) +##### Old behavior -#### Old behavior +Previously, EF generated queries such as the following: -The EF.Functions.Unhex() function was previously annotated to return `byte[]`. +```sql +SELECT c["City"] FROM root c +``` -#### New behavior +Such queries cause Cosmos DB to wrap each result in a JSON object, as follows: + +```json +[ + { + "City": "Berlin" + }, + { + "City": "México D.F." + } +] +``` -Starting with EF Core 9.0, Unhex() is now annotated to return `byte[]?`. +##### New behavior -#### Why +Starting with EF Core 9.0, EF now adds the `VALUE` modifier to queries as follows: -Unhex() is translated to the SQLite `unhex` function, which returns NULL for invalid inputs. As a result, Unhex() returned `null` for those cases, in violation of the annotation. +```sql +SELECT VALUE c["City"] FROM root c +``` -#### Mitigations +Such queries cause Cosmos DB to return the values directly, without being wrapped: -If you are sure that the text content passed to Unhex() represents a valid, hexadecimal string, you can simply add the null-forgiving operator as an assertion that the invocation will never return null: +```json +[ + "Berlin", + "México D.F." +] +``` -```c# -var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync(); +If your application makes use of [SQL queries](xref:core/providers/cosmos/querying#sql-queries), such queries are likely broken after upgrading to EF 9.0, as they don't include the `VALUE` modifier. + +##### Why + +Wrapping each result in an additional JSON object can cause performance degradation in some scenarios, bloats the JSON result payload, and isn't the natural way to work with Cosmos DB. + +##### Mitigations + +To mitigate, simply add the `VALUE` modifier to the projections of your SQL queries, as shown above. + + + +#### Undefined results are now automatically filtered from query results + +[Tracking Issue #25527](https://github.com/dotnet/efcore/issues/25527) + +##### Old behavior + +Previously, EF generated queries such as the following: + +```sql +SELECT c["City"] FROM root c ``` -Otherwise, add runtime checks for null on the return value of Unhex(). +Such queries cause Cosmos DB to wrap each result in a JSON object, as follows: + +```json +[ + { + "City": "Berlin" + }, + { + "City": "México D.F." + } +] +``` - +If any of the results were undefined (e.g. the `City` property was absent from the document), an empty document was returned, and EF would return `null` for that result. -### SqlFunctionExpression's nullability arguments' arity validated +##### New behavior -[Tracking Issue #33852](https://github.com/dotnet/efcore/issues/33852) +Starting with EF Core 9.0, EF now adds the `VALUE` modifier to queries as follows: -#### Old behavior +```sql +SELECT VALUE c["City"] FROM root c +``` -Previously it was possible to create a `SqlFunctionExpression` with a different number of arguments and nullability propagation arguments. +Such queries cause Cosmos DB to return the values directly, without being wrapped: -#### New behavior +```json +[ + "Berlin", + "México D.F." +] +``` -Starting with EF Core 9.0, EF now throws if the number of arguments and nullability propagation arguments do not match. +The Cosmos DB behavior is to automatically filter `undefined` values out of results; this means that if one of the `City` properties is absent from the document, the query would return just a single result, rather than two results, with one being `null`. -#### Why +##### Why -Not having matching number of arguments and nullability propagation arguments can lead to unexpected behavior. +Wrapping each result in an additional JSON object can cause performance degradation in some scenarios, bloats the JSON result payload, and isn't the natural way to work with Cosmos DB. -#### Mitigations +##### Mitigations -Make sure the `argumentsPropagateNullability` has same number of elements as the `arguments`. When in doubt use `false` for nullability argument. +If getting `null` values for undefined results is important for your application, coalesce the `undefined` values to `null` using the new `EF.Functions.Coalesce` operator: + +```csharp +var users = await context.Customer + .Select(c => EF.Functions.CoalesceUndefined(c.City, null)) + .ToListAsync(); +``` + + + +#### Incorrectly translated queries are no longer translated + +[Tracking Issue #34123](https://github.com/dotnet/efcore/issues/34123) + +##### Old behavior + +Previously, EF translated queries such as the following: + +```csharp +var sessions = await context.Sessions + .Take(5) + .Where(s => s.Name.StartsWith("f")) + .ToListAsync(); +``` + +However, the SQL translation for this query was incorrect: + +```sql +SELECT c +FROM root c +WHERE ((c["Discriminator"] = "Session") AND STARTSWITH(c["Name"], "f")) +OFFSET 0 LIMIT @__p_0 +``` + +In SQL, the `WHERE` clause is evaluated _before_ the `OFFSET` and `LIMIT` clauses; but in the LINQ query above, the `Take` operator appears before the `Where` operator. As a result, such queries could return incorrect results. + +##### New behavior + +Starting with EF Core 9.0, such queries are no longer translated, and an exception is thrown. + +##### Why + +Incorrect translations can cause silent data corruption, which can introduce hard-to-discover bugs in your application. EF always prefer to fail-fast by throwing up-front rather than to possibly cause data corruption. + +##### Mitigations + +If you were happy with the previous behavior and would like to execute the same SQL, simply swap around the order of LINQ operators: + +```csharp +var sessions = await context.Sessions + .Where(s => s.Name.StartsWith("f")) + .Take(5) + .ToListAsync(); +``` + +Unfortunately, Cosmos does not currently support the `OFFSET` and `LIMIT` clauses in SQL subqueries, which is what the proper translation of the original LINQ query requires. + +### Low-impact changes + + + +#### `HasIndex` now throws instead of being ignored + +[Tracking Issue #34023](https://github.com/dotnet/efcore/issues/34023) + +##### Old behavior + +Previously, calls to were ignored by the EF Cosmos DB provider. + +##### New behavior + +The provider now throws if is specified. + +##### Why + +In Cosmos DB, all properties are indexed by default, and no indexing needs to be specified. While it's possible to define a custom indexing policy, this isn't currently supported by EF, and can be done via the Azure Portal without EF support. Since calls weren't doing anything, they are no longer allowed. + +##### Mitigations + +Remove any calls to . + + + +#### `IncludeRootDiscriminatorInJsonId` was renamed to `HasRootDiscriminatorInJsonId` after 9.0.0-rc.2 + +[Tracking Issue #34717](https://github.com/dotnet/efcore/pull/34717) + +##### Old behavior + +The `IncludeRootDiscriminatorInJsonId` Cosmos API was introduced in 9.0.0 rc.1. + +##### New behavior + +For the final release of EF Core 9.0, the API was renamed to `HasRootDiscriminatorInJsonId` + +##### Why + +Another related API was renamed to start with `Has` instead of `Include`, and so this one was renamed for consistency as well. + +##### Mitigations + +If your code is using the `IncludeRootDiscriminatorInJsonId` API, simply change it to reference `HasRootDiscriminatorInJsonId` instead. diff --git a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md index 80105540bd..525d95a6b6 100644 --- a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md @@ -24,18 +24,60 @@ EF9 targets .NET 8, and can therefore be used with either [.NET 8 (LTS)](https:/ ## Azure Cosmos DB for NoSQL -We are working on significant updates in EF9 to the EF Core database provider for Azure Cosmos DB for NoSQL. +EF 9.0 brings substantial improvements to the EF Core provider for Azure Cosmos DB; significant parts of the provider have been rewritten to provide new functionality, allow new forms of queries, and better align the provider with Cosmos DB best practices. The main high-level improvements are listed below; for a full list, [see this epic issue](https://github.com/dotnet/efcore/issues/33033). -### Hierarchical partition keys +> [!WARNING] +> As part of the improvements going into the provider, a number of high-impact breaking changes had to be made; if you are upgrading an existing application, please read the [breaking changes section](xref:core/what-is-new/ef-core-9.0/breaking-changes#cosmos-breaking-changes) carefully. -> [!TIP] -> The code shown here comes from [HierarchicalPartitionKeysSample.cs](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Miscellaneous/NewInEFCore9.Cosmos/HierarchicalPartitionKeysSample.cs). +### Improvements querying with partition keys and document IDs Each document stored in the Cosmos database has a unique resource ID. In addition, each document can contain a "partition key" which determines the logical partitioning of data such that the database can be effectively scaled. More information on choosing partition keys can be found in [_Partitioning and horizontal scaling in Azure Cosmos DB_](/azure/cosmos-db/partitioning-overview). -Recent releases of Azure Cosmos DB for NoSQL (Cosmos SDK version 3.33.0 or later) have expanded partitioning capabilities to support [subpartitioning through the specification of up to three levels of hierarchy in the partition key](/azure/cosmos-db/hierarchical-partition-keys). EF Core 9 supports specification of hierarchical partition keys in the model, automatic extraction of these values from queries, and manual specification of a hierarchical partition key for a given query. +In EF 9.0, the Cosmos DB provider is significantly better at identifying partition key comparisons in your LINQ queries, and extracting them out to make your queries are only sent to the relevant partition; this can greatly improve the performance of your queries and reduce costs. For example: + +```csharp +var sessions = await context.Sessions + .Where(b => b.PartitionKey == "someValue" && b.Username.StartsWith("x")) + .ToListAsync(); +``` + +In this query, the provider automatically recognizes the comparison on `PartitionKey`; if we examine the logs, we'll see the following: + +```console +Executed ReadNext (189.8434 ms, 2.8 RU) ActivityId='8cd669ed-2ca5-4f2b-8923-338899071361', Container='test', Partition='["someValue"]', Parameters=[] +SELECT VALUE c +FROM root c +WHERE STARTSWITH(c["Username"], "x") +``` + +Note that the `WHERE` clause does not contain `PartitionKey`: that comparison has been "lifted" out and is used to execute the query only against the relevant partition. In previous versions, the comparison was left in the `WHERE` clause in many situations, causing the query to be executed against all partitions and resulting in increased costs and reduced performance. + +In addition, if your query also provides a value for the document's ID property, and doesn't include any other query operations, the provider can apply an additional optimization: + +```csharp +var somePartitionKey = "someValue"; +var someId = 8; +var sessions = await context.Sessions + .Where(b => b.PartitionKey == somePartitionKey && b.Id == someId) + .SingleAsync(); +``` + +The logs show the following for this query: + +```console +Executed ReadItem (73 ms, 1 RU) ActivityId='13f0f8b8-d481-47f0-bf41-67f7deb008b2', Container='test', Id='8', Partition='["someValue"]' +``` + +Here, no SQL query is sent at all. Instead, the provider performs an an extremely efficient _point read_ (`ReadItem` API), which directly fetches the document given the partition key and ID. This is the most efficient and cost-effective kind of read you can perform in Cosmos DB; [see the Cosmos DB documentation](/azure/cosmos-db/nosql/how-to-dotnet-read-item) for more information about point reads. -#### Configuring hierarchical partition keys +To learn more about querying with partition keys and point reads, [see the querying documentation page](xref:core/providers/cosmos/querying). + +### Hierarchical partition keys + +> [!TIP] +> The code shown here comes from [HierarchicalPartitionKeysSample.cs](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Miscellaneous/NewInEFCore9.Cosmos/HierarchicalPartitionKeysSample.cs). + +Azure Cosmos DB originally supported a single partition key, but has since expanded partitioning capabilities to support [subpartitioning through the specification of up to three levels of hierarchy in the partition key](/azure/cosmos-db/hierarchical-partition-keys). EF Core 9 brings full support for hierarchical partition keys, allowing you take advantage of the better performance and cost savings associated with this feature. Partition keys are specified using the model building API, typically in . There must be a mapped property in the entity type for each level of the partition key. For example, consider a `UserSession` entity type: @@ -70,108 +112,7 @@ The following code specifies a three-level partition key using the `TenantId`, ` Notice how, starting with EF Core 9, properties of any mapped type can be used in the partition key. For `bool` and numeric types, like the `int SessionId` property, the value is used directly in the partition key. Other types, like the `Guid UserId` property, are automatically converted to strings. -#### Saving documents with hierarchical partition keys - -Saving a new document with a hierarchical partition key is the same as saving any new document with EF Core. The primary key and partition key properties must have non-default values, or EF Core value generation can be used to create values. For example, the following code inserts `UserSession` documents where the `Id` property is generated by EF Core, and all the partition key properties have been set explicitly: - - -[!code-csharp[Inserts](../../../../samples/core/Miscellaneous/NewInEFCore9.Cosmos/HierarchicalPartitionKeysSample.cs?name=Inserts)] - -The logs from calling `SaveChangesAsync` show in the following `CreateItem` calls: - -```output -info: 6/10/2024 18:41:04.456 CosmosEventId.ExecutedCreateItem[30104] (Microsoft.EntityFrameworkCore.Database.Command) - Executed CreateItem (167 ms, 7.81 RU) ActivityId='23891b55-7375-40e5-aa4b-2c57ca6a376e', Container='UserSessionContext', Id='UserSession|d5e2614b-71f2-4e6b-d41a-08dc89748055', Partition='["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c",7.0]' -info: 6/10/2024 18:41:04.478 CosmosEventId.ExecutedCreateItem[30104] (Microsoft.EntityFrameworkCore.Database.Command) - Executed CreateItem (14 ms, 7.81 RU) ActivityId='7fdcfb3e-455c-45dd-b444-02b66575a28f', Container='UserSessionContext', Id='UserSession|01cc0102-5212-4785-d41b-08dc89748055', Partition='["Microsoft","adae5dde-8a67-432d-9dec-fd7ec86fd9f6",7.0]' -info: 6/10/2024 18:41:04.491 CosmosEventId.ExecutedCreateItem[30104] (Microsoft.EntityFrameworkCore.Database.Command) - Executed CreateItem (13 ms, 7.81 RU) ActivityId='3f7e6026-8edf-4f2c-8918-09434dc039bf', Container='UserSessionContext', Id='UserSession|e5a467c0-bb1e-4ffe-d41c-08dc89748055', Partition='["Microsoft","61967254-aff8-493a-b7f8-e62da36d8367",7.0]' -info: 6/10/2024 18:41:04.507 CosmosEventId.ExecutedCreateItem[30104] (Microsoft.EntityFrameworkCore.Database.Command) - Executed CreateItem (15 ms, 7.81 RU) ActivityId='04c6f4b2-0ad0-4708-874e-dc8967726d18', Container='UserSessionContext', Id='UserSession|fd47726a-fb68-4c63-d41d-08dc89748055', Partition='["Microsoft","bc0150cf-5147-44b8-8823-865f4f2323e1",7.0]' -``` - -Notice that the partition key values have been extracted from the entity instance and included in the call to `CreateItem` to ensure maximum efficiency on the server. - -#### Point reads using hierarchical partition keys - -By convention, EF Core includes the partition key properties in the primary key definition for the entity type. For example, inspecting the [model debug view](xref:core/modeling/index#debug-view) shows the following mapping for the `UserSession` entity type: - -```output -EntityType: UserSession - Properties: - Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd - TenantId (string) Required PK AfterSave:Throw - UserId (Guid) Required PK AfterSave:Throw - SessionId (int) Required PK AfterSave:Throw - Discriminator (no field, string) Shadow Required AfterSave:Throw - Username (string) - __id (no field, string) Shadow Required AlternateKey AfterSave:Throw - __jObject (no field, JObject) Shadow BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate - Keys: - Id, TenantId, UserId, SessionId PK - __id, TenantId, UserId, SessionId -``` - -Notice that the primary key definition is `Id, TenantId, UserId, SessionId`. This means that can be used to lookup a document. For example: - - -[!code-csharp[FindAsync](../../../../samples/core/Miscellaneous/NewInEFCore9.Cosmos/HierarchicalPartitionKeysSample.cs?name=FindAsync)] - -Logging from EF Core shows that a point-read (using `ReadItem`) is executed for maximum efficiency: - -```output -info: 6/10/2024 18:41:04.651 CosmosEventId.ExecutingReadItem[30101] (Microsoft.EntityFrameworkCore.Database.Command) - Reading resource 'UserSession|e5a467c0-bb1e-4ffe-d41c-08dc89748055' item from container 'UserSessionContext' in partition '["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c",7.0]'. -info: 6/10/2024 18:41:04.668 CosmosEventId.ExecutedReadItem[30103] (Microsoft.EntityFrameworkCore.Database.Command) - Executed ReadItem (8 ms, 1 RU) ActivityId='a016f26c-6bd0-4c66-953b-a8f1297df41a', Container='UserSessionContext', Id='UserSession|e5a467c0-bb1e-4ffe-d41c-08dc89748055', Partition='["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c",7.0]' -``` - -#### Queries using hierarchical partition keys - -EF Core will extract the partition key values from queries and apply them to the Cosmos query API to ensure the queries are constrained appropriately to the fewest number of partitions possible. For example, consider a LINQ query that supplies values for all levels of the partition key: +When querying, EF automatically extracts the partition key values from queries and applies them to the Cosmos query API to ensure the queries are constrained appropriately to the fewest number of partitions possible. For example, consider the following LINQ query that supplies the partition key values: -[!code-csharp[TopTwoPartitionKey](../../../../samples/core/Miscellaneous/NewInEFCore9.Cosmos/HierarchicalPartitionKeysSample.cs?name=TopTwoPartitionKey)] +* The Cosmos provider now fully supports EF's primitive collections, allowing you to perform LINQ querying on collections of e.g. ints or strings. See [What's new in EF8: primitive collections](xref:core/what-is-new/ef-core-8.0/whatsnew#primitive-collections) for more information. +* Support for arbitrary querying over non-primitive collections has been added as well. +* Lots of additional LINQ operators are now supported: indexing into collections, `Length`/`Count`, `ElementAt`, `Contains`, and many others. +* Support for aggregate operators such as `Count` and `Sum` has been added. +* Many function translations have added (see the [function mappings documentation](xref:core/providers/cosmos/querying#function-mappings) for the full list of supported translations): + * Translations for `DateTime` and `DateTimeOffset` component members (`DateTime.Year`, `DateTimeOffset.Month`...) have been added. + * `EF.Functions.IsDefined` and `EF.Functions.CoalesceUndefined` now allow dealing with `undefined` values. + * `string.Contains`, `StartsWith` and `EndsWith` now support `StringComparison.OrdinalIgnoreCase`. -EF Core still extracts the partition key values when executing this query: +For the full list of querying improvements, see [this issue](https://github.com/dotnet/efcore/issues/33033): -```output -info: 6/10/2024 19:24:46.581 CosmosEventId.ExecutingSqlQuery[30100] (Microsoft.EntityFrameworkCore.Database.Command) - Executing SQL query for container 'UserSessionContext' in partition '["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c"]' [Parameters=[]] - SELECT c - FROM root c - WHERE ((c["Discriminator"] = "UserSession") AND CONTAINS(c["Username"], "a")) +### Improved modeling aligned to Cosmos and JSON standards + +EF 9.0 maps to Cosmos DB documents in ways which are more natural for a JSON-based document database, and help interoperate with other systems accessing your documents. Although this entails breaking changes, APIs exist which allow reverting back to the pre-9.0 behavior in all cases. + +#### Simplified `id` properties without discriminators + +First, previous versions of EF inserted the discriminator value into the JSON `id` property, producing documents such as the following: + +```json +{ + "id": "Blog|1099", + ... +} ``` -This query does not include the `SessionId`, so it cannot target a single partition. However, it will still be a targeted, cross-partition query returning data for all sessions of a single tenant and user ID. +This was done in order to allow for documents of different types (e.g. Blog and Post) and the same key value (1099) to exist within the same container partition. Starting with EF 9.0, the `id` property contains contains only the key value: -Likewise, if only the top value in the hierarchy is specified, then it will be used on its own. For example: +```json +{ + "id": 1099, + ... +} +``` - -[!code-csharp[TopOnePartitionKey](../../../../samples/core/Miscellaneous/NewInEFCore9.Cosmos/HierarchicalPartitionKeysSample.cs?name=TopOnePartitionKey)] +Note this is a breaking change, since EF will no longer be able to query existing documents with the old `id` format. An API has been introduced to revert to the previous behavior, see the [breaking change note](xref:core/what-is-new/ef-core-9.0/breaking-changes#cosmos-id-property-changes) and the [the documentation](xref:core/providers/cosmos/index#Discriminators) for more details. -Which results in the following logs: +#### Discriminator property renamed to `$type` -```output -info: 6/11/2024 09:30:42.532 CosmosEventId.ExecutingSqlQuery[30100] (Microsoft.EntityFrameworkCore.Database.Command) - Executing SQL query for container 'UserSessionContext' in partition '["Microsoft"]' [Parameters=[]] - SELECT c - FROM root c - WHERE ((c["Discriminator"] = "UserSession") AND CONTAINS(c["Username"], "a")) +The default discriminator property was previously named `Discriminator`. EF 9.0 changes the default to `$type`: + +```json +{ + "id": 1099, + "$type": "Blog", + ... +} ``` -Since this query only contains the `TenantId` part of the partition key it cannot target a single partition. However, as with the previous example it will still be a targeted, cross-partition query returning data for all sessions and users in a single tenant. +This follows the emerging standard for JSON polymorphism, allowing better interoperability with other tools. For example, .NET's System.Text.Json also supports polymorphism, using `$type` as its default discriminator property name ([docs](/dotnet/standard/serialization/system-text-json/polymorphism#customize-the-type-discriminator-name)). -It is important to understand that using the second and/or third values of the hierarchical partition key, without including the first value, will result in a query that covers all partitions. For example, consider a query including both `SessionId` and `UserId`, but not including `TenantId`: +Note this is a breaking change, since EF will no longer be able to query existing documents with the old discriminator property name. See the [breaking change note](xref:core/what-is-new/ef-core-9.0/breaking-changes#cosmos-discriminator-name-change) for details on how to revert to the previous naming. - -[!code-csharp[BottomTwoPartitionKey](../../../../samples/core/Miscellaneous/NewInEFCore9.Cosmos/HierarchicalPartitionKeysSample.cs?name=BottomTwoPartitionKey)] +### Vector similarity search (preview) -The logs show that this is translated without a partition key, since the `TenantId` is missing: +Azure Cosmos DB now offers preview support for vector similarity search. Vector search is a fundamental part of some application types, include AI, semantic search and others. The Cosmos DB support for vector search allows storing your data and vectors and performing your queries in a single database, which can considerably simplify your architecture and remove the need for an additional, dedicated vector database solution in your stack. To learn more about Cosmos DB vector search, [see the documentation](/azure/cosmos-db/nosql/vector-search). -```output -info: 6/11/2024 09:30:42.553 CosmosEventId.ExecutingSqlQuery[30100] (Microsoft.EntityFrameworkCore.Database.Command) - Executing SQL query for container 'UserSessionContext' in partition 'None' [Parameters=[]] - SELECT c - FROM root c - WHERE (c["Discriminator"] = "UserSession") +Once your Cosmos DB container is properly set up, using vector search via EF is a simple matter of adding a vector property and configuring it: + +```c# +public class Blog +{ + ... + + public float[] Vector { get; set; } +} + +public class BloggingContext +{ + ... + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .Property(b => b.Embeddings) + .IsVector(DistanceFunction.Cosine, dimensions: 1536); + } +} ``` -> [!NOTE] -> [Issue #33960](https://github.com/dotnet/efcore/issues/33960) is tracking a bug in this translation. +Once that's done, use the `EF.Functions.VectorDistance()` function in LINQ queries to perform vector similarity search: -### Role-based access +```c# +var blogs = await context.Blogs + .OrderBy(s => EF.Functions.VectorDistance(s.Vector, vector)) + .Take(5) + .ToListAsync(); +``` -Azure Cosmos DB for NoSQL includes a [built-in role-based access control (RBAC) system](/azure/cosmos-db/role-based-access-control). This is now supported by EF9 for both management and use of containers. No changes are required to application code. See [Issue #32197](https://github.com/dotnet/efcore/issues/32197) for more information. +For more information, see the [documentation on vector search](xref:core/providers/cosmos/vector-search). -### Synchronous access blocked by default +### Pagination support -> [!TIP] -> The code shown here comes from [CosmosSyncApisSample.cs](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Miscellaneous/NewInEFCore9.Cosmos/CosmosSyncApisSample.cs). +The Cosmos DB provider now allows for paginating through query results via _continuation tokens_, which is far more efficient and cost-effective than the traditional use of `Skip` and `Take`: -Azure Cosmos DB for NoSQL does not support synchronous (blocking) access from application code. Previously, EF masked this by default by blocking for you on async calls. However, this both encourages sync use, which is bad practice, and [may cause deadlocks](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). Therefore, starting with EF9, an exception is thrown when synchronous access is attempted. For example: +```c# +var firstPage = await context.Posts + .OrderBy(p => p.Id) + .ToPageAsync(pageSize: 10, continuationToken: null); -```output -System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Database.SyncNotSupported': - Azure Cosmos DB does not support synchronous I/O. Make sure to use and correctly await only async methods when using - Entity Framework Core to access Azure Cosmos DB. See https://aka.ms/ef-cosmos-nosync for more information. - This exception can be suppressed or logged by passing event ID 'CosmosEventId.SyncNotSupported' to the 'ConfigureWarnings' - method in 'DbContext.OnConfiguring' or 'AddDbContext'. - at Microsoft.EntityFrameworkCore.Diagnostics.EventDefinition.Log[TLoggerCategory](IDiagnosticsLogger`1 logger, Exception exception) - at Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal.CosmosLoggerExtensions.SyncNotSupported(IDiagnosticsLogger`1 diagnostics) - at Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal.CosmosClientWrapper.DeleteDatabase() - at Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal.CosmosDatabaseCreator.EnsureDeleted() - at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureDeleted() +var continuationToken = page.ContinuationToken; +foreach (var post in page.Values) +{ + // Display/send the posts to the user +} ``` -As the exception says, sync access can still be used for now by configuring the warning level appropriately. For example, in `OnConfiguring` on your `DbContext` type: +The new `ToPageAsync` operator returns a `CosmosPage`, which exposes a continuation token that can be used to efficiently resume the query at a later point, fetching the next 10 items: -```csharp -protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.ConfigureWarnings(b => b.Ignore(CosmosEventId.SyncNotSupported)); +```c# +var nextPage = await context.Sessions.OrderBy(s => s.Id).ToPageAsync(10, continuationToken); ``` -Note, however, that we plan to fully remove sync support in EF11, so start updating to use async methods like `ToListAsync` and `SaveChangesAsync` as soon as possible! +For more information, [see the documentation section on pagination](xref:core/providers/cosmos/querying#pagination). -### Enhanced primitive collections +### FromSql for safer SQL querying -> [!TIP] -> The code shown here comes from [CosmosPrimitiveTypesSample.cs](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Miscellaneous/NewInEFCore9.Cosmos/CosmosPrimitiveTypesSample.cs). +The Cosmos DB provider has allowed SQL querying via . However, that API can be susceptible to SQL injection attacks when user-provided data is interpolated or concatenated into the SQL. In EF 9.0, you can now use the new `FromSql` method, which always integrates parameterized data as a parameter outside the SQL: -The Cosmos DB provider has supported primitive collections in a limited form since EF Core 6. This is support is being enhanced in EF9, starting with consolidation of the metadata and API surfaces for primitive collections in document databases to align with primitive collections in relational databases. This means that primitive collections can now be explicitly mapped using the model building API, allowing for facets of the element type to be configured. For example, to map a list of required (i.e. non-null) strings: +```c# +var maxAngle = 8; +_ = await context.Blogs + .FromSql($"SELECT VALUE c FROM root c WHERE c.Angle1 <= {maxAngle}") + .ToListAsync(); +``` - -[!code-csharp[ConfigureCollection](../../../../samples/core/Miscellaneous/NewInEFCore9.Cosmos/CosmosPrimitiveTypesSample.cs?name=ConfigureCollection)] +For more information, [see the documentation section on pagination](xref:core/providers/cosmos/querying#pagination). + +### Role-based access + +Azure Cosmos DB for NoSQL includes a [built-in role-based access control (RBAC) system](/azure/cosmos-db/role-based-access-control). This is now supported by EF9 for both management and use of containers. No changes are required to application code. See [Issue #32197](https://github.com/dotnet/efcore/issues/32197) for more information. + +### Synchronous I/O is now blocked by default + +Azure Cosmos DB for NoSQL does not support synchronous (blocking) APIs from application code. Previously, EF masked this by blocking for you on async calls. However, this both encourages synchronous I/O use, which is bad practice, and [may cause deadlocks](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). Therefore, starting with EF 9, an exception is thrown when synchronous access is attempted. For example: + +Synchronous I/O can still be used for now by configuring the warning level appropriately. For example, in `OnConfiguring` on your `DbContext` type: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.ConfigureWarnings(b => b.Ignore(CosmosEventId.SyncNotSupported)); +``` -See [What's new in EF8: primitive collections](xref:core/what-is-new/ef-core-8.0/whatsnew#primitive-collections) for more information on the model building API. +Note, however, that we plan to fully remove sync support in EF 11, so start updating to use async methods like `ToListAsync` and `SaveChangesAsync` as soon as possible! ## AOT and pre-compiled queries As mentioned in the introduction, there is a lot of work going on behind the scenes to allow EF Core to run without just-in-time (JIT) compilation. Instead, EF compile ahead-of-time (AOT) everything needed to run queries in the application. This AOT compilation and related processing will happen as part of building and publishing the application. At this point in the EF9 release, there is not much available that can be used by you, the app developer. However, for those interested, the completed issues in EF9 that support AOT and pre-compiled queries are: -- [Compiled model: Use static binding instead of reflection for properties and fields](https://github.com/dotnet/efcore/issues/24900) -- [Compiled model: Generate lambdas used in change tracking](https://github.com/dotnet/efcore/issues/24904) -- [Make change tracking and the update pipeline compatible with AOT/trimming](https://github.com/dotnet/efcore/issues/29761) -- [Use interceptors to redirect the query to precompiled code](https://github.com/dotnet/efcore/issues/31331) -- [Make all SQL expression nodes quotable](https://github.com/dotnet/efcore/issues/33008) -- [Generate the compiled model during build](https://github.com/dotnet/efcore/issues/24894) -- [Discover the compiled model automatically](https://github.com/dotnet/efcore/issues/24893) -- [Make ParameterExtractingExpressionVisitor capable of extracting paths to evaluatable fragments in the tree](https://github.com/dotnet/efcore/issues/32999) -- [Generate expression trees in compiled models (query filters, value converters)](https://github.com/dotnet/efcore/issues/29924) -- [Make LinqToCSharpSyntaxTranslator more resilient to multiple declaration of the same variable in nested scopes](https://github.com/dotnet/efcore/issues/32716) -- [Optimize ParameterExtractingExpressionVisitor](https://github.com/dotnet/efcore/issues/32698) +* [Compiled model: Use static binding instead of reflection for properties and fields](https://github.com/dotnet/efcore/issues/24900) +* [Compiled model: Generate lambdas used in change tracking](https://github.com/dotnet/efcore/issues/24904) +* [Make change tracking and the update pipeline compatible with AOT/trimming](https://github.com/dotnet/efcore/issues/29761) +* [Use interceptors to redirect the query to precompiled code](https://github.com/dotnet/efcore/issues/31331) +* [Make all SQL expression nodes quotable](https://github.com/dotnet/efcore/issues/33008) +* [Generate the compiled model during build](https://github.com/dotnet/efcore/issues/24894) +* [Discover the compiled model automatically](https://github.com/dotnet/efcore/issues/24893) +* [Make ParameterExtractingExpressionVisitor capable of extracting paths to evaluatable fragments in the tree](https://github.com/dotnet/efcore/issues/32999) +* [Generate expression trees in compiled models (query filters, value converters)](https://github.com/dotnet/efcore/issues/29924) +* [Make LinqToCSharpSyntaxTranslator more resilient to multiple declaration of the same variable in nested scopes](https://github.com/dotnet/efcore/issues/32716) +* [Optimize ParameterExtractingExpressionVisitor](https://github.com/dotnet/efcore/issues/32698) Check back here for examples of how to use pre-compiled queries as the experience comes together. diff --git a/entity-framework/toc.yml b/entity-framework/toc.yml index 38d9a2c077..866626b0e6 100644 --- a/entity-framework/toc.yml +++ b/entity-framework/toc.yml @@ -432,12 +432,16 @@ items: - name: Overview href: core/providers/cosmos/index.md + - name: Modeling + href: core/providers/cosmos/modeling.md + - name: Querying + href: core/providers/cosmos/querying.md - name: Work with unstructured data href: core/providers/cosmos/unstructured-data.md + - name: Vector search + href: core/providers/cosmos/vector-search.md - name: Azure Cosmos DB limitations href: core/providers/cosmos/limitations.md - - name: Function mappings - href: core/providers/cosmos/functions.md - name: End-to-end sample href: core/providers/cosmos/planetary-docs-sample.md - name: In-memory (not recommended)