Skip to content

Commit

Permalink
Cosmos updates (#4255)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajcvickers authored Feb 18, 2023
1 parent 7253089 commit 8e3bce5
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 138 deletions.
140 changes: 70 additions & 70 deletions entity-framework/core/providers/cosmos/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,82 +11,82 @@ This page shows which .NET members are translated into which SQL functions when

## Date and time functions

.NET | SQL
--------------------- | ---
DateTime.UtcNow | GetCurrentDateTime()
DateTimeOffset.UtcNow | GetCurrentDateTime()
| .NET | SQL |
|-----------------------|----------------------|
| DateTime.UtcNow | GetCurrentDateTime() |
| DateTimeOffset.UtcNow | GetCurrentDateTime() |

## Numeric functions

.NET | SQL
--------------------- | ---
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)
MathF.Abs(x) | ABS(@x)
MathF.Acos(x) | ACOS(@x)
MathF.Asin(x) | ASIN(@x)
MathF.Atan(x) | ATAN(@x)
MathF.Atan2(y, x) | ATN2(@y, @x)
MathF.Ceiling(x) | CEILING(@x)
MathF.Cos(x) | COS(@x)
MathF.Exp(x) | EXP(@x)
MathF.Floor(x) | FLOOR(@x)
MathF.Log(x, y) | LOG(@x, @y)
MathF.Log(x) | LOG(@x)
MathF.Log10(x) | LOG10(@x)
MathF.Pow(x, y) | POWER(@x, @y)
MathF.Round(x) | ROUND(@x)
MathF.Sign(x) | SIGN(@x)
MathF.Sin(x) | SIN(@x)
MathF.Sqrt(x) | SQRT(@x)
MathF.Tan(x) | TAN(@x)
MathF.Truncate(x) | TRUNC(@x)
| .NET | SQL |
|-----------------------|-------------------|
| 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) |
| MathF.Abs(x) | ABS(@x) |
| MathF.Acos(x) | ACOS(@x) |
| MathF.Asin(x) | ASIN(@x) |
| MathF.Atan(x) | ATAN(@x) |
| MathF.Atan2(y, x) | ATN2(@y, @x) |
| MathF.Ceiling(x) | CEILING(@x) |
| MathF.Cos(x) | COS(@x) |
| MathF.Exp(x) | EXP(@x) |
| MathF.Floor(x) | FLOOR(@x) |
| MathF.Log(x, y) | LOG(@x, @y) |
| MathF.Log(x) | LOG(@x) |
| MathF.Log10(x) | LOG10(@x) |
| MathF.Pow(x, y) | POWER(@x, @y) |
| MathF.Round(x) | ROUND(@x) |
| MathF.Sign(x) | SIGN(@x) |
| MathF.Sin(x) | SIN(@x) |
| MathF.Sqrt(x) | SQRT(@x) |
| MathF.Tan(x) | TAN(@x) |
| MathF.Truncate(x) | TRUNC(@x) |

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

Expand Down
136 changes: 133 additions & 3 deletions entity-framework/core/providers/cosmos/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Azure Cosmos DB Provider - EF Core
description: Documentation for the database provider that allows Entity Framework Core to be used with Azure Cosmos DB.
author: AndriySvyryd
ms.date: 01/11/2022
ms.date: 02/12/2023
uid: core/providers/cosmos/index
---
# EF Core Azure Cosmos DB Provider
Expand Down Expand Up @@ -42,7 +42,7 @@ As for other providers the first step is to call [UseCosmos](/dotnet/api/Microso
[!code-csharp[Configuration](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=Configuration)]

> [!WARNING]
> The endpoint and key are hardcoded here for simplicity, but in a production app these should be [stored securely](/aspnet/core/security/app-secrets#secret-manager).
> The endpoint and key are hardcoded here for simplicity, but in a production app these should be [stored securely](/aspnet/core/security/app-secrets#secret-manager). See [Connecting and authenticating](xref:core/providers/cosmos/index#connecting-and-authenticating) for different ways to connect to Azure Cosmos DB.
In this example `Order` is a simple entity with a reference to the [owned type](xref:core/modeling/owned-entities) `StreetAddress`.

Expand All @@ -57,14 +57,88 @@ Saving and querying data follows the normal EF pattern:
> [!IMPORTANT]
> Calling [EnsureCreatedAsync](/dotnet/api/Microsoft.EntityFrameworkCore.Storage.IDatabaseCreator.EnsureCreatedAsync) is necessary to create the required containers and insert the [seed data](xref:core/modeling/data-seeding) if present in the model. However `EnsureCreatedAsync` should only be called during deployment, not normal operation, as it may cause performance issues.
## Connecting and authenticating

The Azure Cosmos DB provider for EF Core has multiple overloads of the [UseCosmos](/dotnet/api/Microsoft.EntityFrameworkCore.CosmosDbContextOptionsExtensions.UseCosmos) method. These overloads support the different ways that a connection can be made to the database, and the different ways of ensuring that the connection is secure.

> [!IMPORTANT]
> Make sure to understand [_Secure access to data in Azure Cosmos DB_](/azure/cosmos-db/secure-access-to-data) to understand the security implications and best practices for using each overload of the `UseCosmos` method.
| Connection Mechanism | UseCosmos Overload | More information |
|----------------------------|------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|
| Account endpoint and key | `UseCosmos<DbContext>(accountEndpoint, accountKey, databaseName)` | [Primary/secondary keys](/azure/cosmos-db/secure-access-to-data#primary-keys) |
| 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

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

<!--
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)]

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

<!--
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)]

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:

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

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

Expand Down Expand Up @@ -119,6 +193,62 @@ modelBuilder.Entity<Family>(
-->
[!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<Hamlet>().HasDefaultTimeToLive(3600);
```

Or, for the analytical store:

```csharp
modelBuilder.Entity<Hamlet>().HasAnalyticalStoreTimeToLive(3600);
```

Time-to-live for individual entities can be set using a property mapped to "ttl" in the JSON document. For example:

<!--
modelBuilder.Entity<Village>()
.HasDefaultTimeToLive(3600)
.Property(e => e.TimeToLive)
.ToJsonProperty("ttl");
-->
[!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:

<!--
var village = new Village { Id = "DN41", Name = "Healing", TimeToLive = 60 };
context.Add(village);
await context.SaveChangesAsync();
-->
[!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:

<!--
modelBuilder.Entity<Hamlet>()
.HasDefaultTimeToLive(3600)
.Property<int>("TimeToLive")
.ToJsonProperty("ttl");
-->
[!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:

<!--
var hamlet = new Hamlet { Id = "DN37", Name = "Irby" };
context.Add(hamlet);
context.Entry(hamlet).Property("TimeToLive").CurrentValue = 60;
await context.SaveChangesAsync();
-->
[!code-csharp[SetTtlShadow](../../../../samples/core/Miscellaneous/NewInEFCore6.Cosmos/CosmosModelConfigurationSample.cs?name=SetTtlShadow)]

## Embedded entities

> [!NOTE]
Expand Down
Loading

0 comments on commit 8e3bce5

Please sign in to comment.