Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
Closes dotnet#4808
Part of dotnet#4805 (new function translations for Cosmos)
  • Loading branch information
roji committed Sep 20, 2024
1 parent 4fd105a commit 113ab05
Show file tree
Hide file tree
Showing 10 changed files with 480 additions and 206 deletions.
5 changes: 5 additions & 0 deletions .openpublishing.redirection.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
}
83 changes: 0 additions & 83 deletions entity-framework/core/providers/cosmos/functions.md

This file was deleted.

113 changes: 41 additions & 72 deletions entity-framework/core/providers/cosmos/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,106 +70,75 @@ The Azure Cosmos DB provider for EF Core has multiple overloads of the [UseCosmo
| Account endpoint and token | `UseCosmos<DbContext>(accountEndpoint, tokenCredential, databaseName)` | [Resource tokens](/azure/cosmos-db/secure-access-to-data#primary-keys) |
| Connection string | `UseCosmos<DbContext>(connectionString, databaseName)` | [Work with account keys and connection strings](/azure/cosmos-db/scripts/cli/common/keys) |

## Queries

### LINQ queries
## Azure Cosmos DB options

[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:
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:

<!--
var stringResults = await context.Triangles.Where(
e => e.Name.Length > 4
&& e.Name.Trim().ToLower() != "obtuse"
&& e.Name.TrimStart().Substring(2, 2).Equals("uT", StringComparison.OrdinalIgnoreCase))
.ToListAsync();
-->
[!code-csharp[StringTranslations](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosQueriesSample.cs?name=StringTranslations)]
[!code-csharp[Configuration](../../../../samples/core/Cosmos/ModelBuilding/OptionsContext.cs?name=Configuration)]

> [!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.
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.

### SQL queries
## Cosmos-specific model customization

Queries can also be written [directly in SQL](xref:core/querying/sql-queries). For example:
### Containers and entity types

<!--
var maxAngle = 60;
var results = await context.Triangles.FromSqlRaw(
@"SELECT * FROM root c WHERE c[""Angle1""] <= {0} OR c[""Angle2""] <= {0}", maxAngle)
.ToListAsync();
-->
[!code-csharp[FromSql](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosQueriesSample.cs?name=FromSql)]
In Azure Cosmos DB, JSON documents are stored in containers. Unlike relational tables, containers can contain documents with different schemas

This query results in the following query execution:
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 <xref:Microsoft.EntityFrameworkCore.CosmosModelBuilderExtensions.HasDefaultContainer%2A>:

```sql
SELECT c
FROM (
SELECT * FROM root c WHERE c["Angle1"] <= @p0 OR c["Angle2"] <= @p0
) c
```c#
modelBuilder.HasDefaultContainer("Store");
```

Just like for relational `FromSql` queries, the hand written SQL can be further composed using LINQ operators. For example:

<!--
var maxAngle = 60;
var results = await context.Triangles.FromSqlRaw(
@"SELECT * FROM root c WHERE c[""Angle1""] <= {0} OR c[""Angle2""] <= {0}", maxAngle)
.Where(e => e.InsertedOn <= DateTime.UtcNow)
.Select(e => e.Angle1).Distinct()
.ToListAsync();
-->
[!code-csharp[FromSqlComposed](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosQueriesSample.cs?name=FromSqlComposed)]

This combination of SQL and LINQ is translated to:
To map an entity type to a different container use <xref:Microsoft.EntityFrameworkCore.CosmosEntityTypeBuilderExtensions.ToContainer%2A>:

```sql
SELECT DISTINCT c["Angle1"]
FROM (
SELECT * FROM root c WHERE c["Angle1"] <= @p0 OR c["Angle2"] <= @p0
) c
WHERE (c["InsertedOn"] <= GetCurrentDateTime())
```c#
modelBuilder.Entity<Order>().ToContainer("Orders");
```

## 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:
To identify the entity type that a given item represents, EF Core always adds a `$type` discriminator property to all JSON documents you save (this property was called `Discriminator` before EF 9.0); to change the name and values of the discriminator, [see these docs for more information](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[Configuration](../../../../samples/core/Cosmos/ModelBuilding/OptionsContext.cs?name=Configuration)]
```c#
modelBuilder.Entity<Order>().HasNoDiscriminator();
```

> [!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.
### IDs and keys

## Cosmos-specific model customization
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 `<type name>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 <xref:Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder%601.HasKey%2A>; see [the general EF documentation on keys](xref:core/modeling/keys) for more information.

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):
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.

[!code-csharp[DefaultContainer](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=DefaultContainer)]
If you'd like to have a GUID as your key property, you can configure EF to generate random values at the client:

To map an entity type to a different container use [ToContainer](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosEntityTypeBuilderExtensions.ToContainer):
```c#
modelBuilder.Entity<Session>().Property(b => b.Id).HasValueGenerator<GuidValueGenerator>();
```

[!code-csharp[Container](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=Container)]
### Partition keys

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).
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.

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):
To configure the partitioning key with EF, call [HasPartitionKey](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosEntityTypeBuilderExtensions.HasPartitionKey), passing it a regular property on your entity type:

[!code-csharp[NoDiscriminator](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=NoDiscriminator)]
```c#
modelBuilder.Entity<Order>().HasPartitionKey(o => o.PartitionKey);
```

### Partition keys
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.

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):
Cosmos DB also supports _hierarchical_ partition keys to optimize data distribution even further; [see the 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):

[!code-csharp[PartitionKey](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=PartitionKey)]
```c#
modelBuilder.Entity<Order>().HasPartitionKey(o => new { e.TenantId, e.UserId, e.SessionId });
```

> [!NOTE]
>The partition key property can be of any type as long as it is [converted to string](xref:core/modeling/value-conversions).
With such a hierarchical partition key, queries can be easily sent only to partitions containing data for a given tenant.

Once configured the partition key property should always have a non-null value. A query can be made single-partition by adding a <xref:Microsoft.EntityFrameworkCore.CosmosQueryableExtensions.WithPartitionKey%2A> call.
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.

[!code-csharp[PartitionKey](../../../../samples/core/Cosmos/ModelBuilding/Sample.cs?name=PartitionKey&highlight=14)]
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.

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`.
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

Expand Down Expand Up @@ -400,8 +369,8 @@ context.SaveChanges();

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.
* Only dictionaries with string keys are supported.
* Support for querying into primitive collections was added in EF Core 9.0.

## Working with disconnected entities

Expand Down
4 changes: 3 additions & 1 deletion entity-framework/core/providers/cosmos/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading

0 comments on commit 113ab05

Please sign in to comment.