Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions entity-framework/core/providers/cosmos/querying.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,51 @@ Note that <xref:Microsoft.EntityFrameworkCore.CosmosQueryableExtensions.FromSql*

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 Azure Cosmos DB provider as well.

## Undefined values in projections

In Azure Cosmos DB, a document property can be `undefined` — this happens when the property is simply not present in the JSON document. This can occur when navigating through an optional relationship that is absent for some documents, or when a property was added to the model after existing documents were written.

Starting with EF Core 11, when any part of an anonymous type or DTO projection evaluates to `undefined`, an `InvalidOperationException` is thrown with the message "A part of the projection was undefined, use the coalesce operator to handle possible undefined values." See the [breaking changes documentation](xref:core/what-is-new/ef-core-11.0/breaking-changes#cosmos-undefined-projection) for details on upgrading from EF Core 10 and earlier.

To handle undefined values in projections, use <xref:Microsoft.EntityFrameworkCore.CosmosDbFunctionsExtensions.IsDefined*> to filter out documents where a value is missing:

```csharp
var results = await context.Entities
.Where(x => EF.Functions.IsDefined(x.Associate!.NestedAssociate!.Id))
.Select(x => new { x.Associate!.NestedAssociate!.Id })
.ToListAsync();
```

Alternatively, use <xref:Microsoft.EntityFrameworkCore.CosmosDbFunctionsExtensions.CoalesceUndefined*> to substitute a default value for any property that could be `undefined`:

```csharp
var results = await context.Entities
.Select(x => new { Id = EF.Functions.CoalesceUndefined(x.Associate!.NestedAssociate!.Id, Guid.Empty) })
.ToListAsync();
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Also mention that any naked projection will be translated as SELECT VALUE and any top-level instantiation (DTO/anon/entity/complex) in the projection won't use SELECT VALUE. This means that a naked projection will silently skip undefined values, so if this isn't a desirable behavior the projected value should be wrapped in a DTO or an anonymous type. Add a small snippet to illustrate

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a "Naked projections and SELECT VALUE" subsection in commit Address review feedback that explains:

  • Naked projections are translated as SELECT VALUE and silently skip undefined values
  • Top-level instantiations (DTO/anonymous type/entity/complex) don't use SELECT VALUE and throw when undefined values are encountered
  • If silent skipping is undesirable, wrap the value in an anonymous type/DTO

Includes code snippets illustrating both cases.

### Naked projections and SELECT VALUE

A _naked projection_ — where a single value is projected directly without wrapping it in a DTO or anonymous type — is translated using `SELECT VALUE` in Cosmos DB SQL. As a result, any documents where the projected value is `undefined` are **silently skipped** and not included in the results:

```csharp
// Naked projection - translated as SELECT VALUE, undefined results are silently omitted
var ids = await context.Entities
.Select(x => x.Associate!.NestedAssociate!.Id)
.ToListAsync();
```

In contrast, any top-level instantiation in the projection (anonymous type, DTO, entity, or complex type) does **not** use `SELECT VALUE`. When a part of such a projection is `undefined`, an `InvalidOperationException` is thrown as described above.

If silently skipping undefined results is not the desired behavior, wrap the projected value in an anonymous type or DTO to get a consistent error instead:

```csharp
// Wrapped in an anonymous type - does not use SELECT VALUE, throws if undefined
var results = await context.Entities
.Select(x => new { x.Associate!.NestedAssociate!.Id })
.ToListAsync();
```

## Function mappings

This section shows which .NET methods and members are translated into which SQL functions when querying with the Azure Cosmos DB provider.
Expand Down
60 changes: 60 additions & 0 deletions entity-framework/core/what-is-new/ef-core-11.0/breaking-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This page documents API and behavior changes that have the potential to break ex
|:--------------------------------------------------------------------------------------------------------------- | -----------|
| [Sync I/O via the Azure Cosmos DB provider has been fully removed](#cosmos-nosync) | Medium |
| [Microsoft.Data.SqlClient has been updated to 7.0](#sqlclient-7) | Medium |
| [Cosmos: exception thrown when a projection evaluates to undefined](#cosmos-undefined-projection) | Medium |
| [Cosmos: illegal `id` characters are no longer escaped](#cosmos-no-id-escape) | Medium |
| [SQL Server compatibility level now defaults to 160](#sqlserver-compatibility-level-160) | Low |
| [EF Core now throws by default when no migrations are found](#migrations-not-found) | Low |
Expand Down Expand Up @@ -126,6 +127,65 @@ If your application uses composite keys whose values can contain the characters
- **Existing data**: Documents previously stored in Cosmos DB have `id` values using the old escape sequences (e.g. `Post|1|^2F`). After upgrading to EF Core 11, EF will generate unescaped `id` values (e.g. `Post|1|/`) and will no longer find those existing documents. To continue accessing existing data without migration, opt back into the old behavior using the `AppContext` switch described above—however, be aware that the id-collision bug will still be present.
- **New data**: If you are creating a new application or database, avoid using these illegal characters in key values, as they are not valid in Cosmos DB resource `id` values. See the [Azure documentation](xref:Microsoft.Azure.Documents.Resource.Id) for details.

<a name="cosmos-undefined-projection"></a>

### Cosmos: exception thrown when a projection evaluates to undefined

[Tracking Issue #34067](https://github.com/dotnet/efcore/issues/34067)

#### Old behavior

Previously, when projecting properties in anonymous type or DTO projections via navigation over optional relationships where a segment of the path was absent in the Cosmos DB document (causing the projected value to be `undefined`), the behavior was inconsistent:

- With **single-property** anonymous type or DTO projections, EF translated the query using `SELECT VALUE`, which silently filtered out any documents where the projected value was `undefined`. This meant fewer results were returned than expected, with no indication of the missing data.
- With **multi-property** anonymous type or DTO projections, an `InvalidOperationException` with the message "Nullable object must have a value" was thrown.

For example, given an entity `Entity` with an optional owned `Associate` which in turn has an optional owned `NestedAssociate`:

```csharp
// Previously silently returned fewer results (undefined results were filtered out)
var singlePropResults = await context.Entities
.Select(x => new { x.Associate!.NestedAssociate!.Id })
.ToListAsync();

// Previously threw InvalidOperationException: Nullable object must have a value
var multiPropResults = await context.Entities
.Select(x => new { x.Associate!.NestedAssociate!.Id, x.Associate!.NestedAssociate!.String })
.ToListAsync();
```

#### New behavior

Starting with EF Core 11.0, an `InvalidOperationException` is thrown in both cases when any part of the projection evaluates to `undefined` in Azure Cosmos DB. The exception message is:

> A part of the projection was undefined, use the coalesce operator to handle possible undefined values.

#### Why

The previous behavior was inconsistent. Single-property projections could silently discard results, making it easy to miss data without any indication of the problem. The new behavior ensures consistent, predictable error reporting whenever a projection encounters an undefined value.

#### Mitigations

Use <xref:Microsoft.EntityFrameworkCore.CosmosDbFunctionsExtensions.IsDefined*> to filter out documents where the projected value is missing:

```csharp
var results = await context.Entities
.Where(x => EF.Functions.IsDefined(x.Associate!.NestedAssociate!.Id))
.Select(x => new { x.Associate!.NestedAssociate!.Id })
.ToListAsync();
```

Alternatively, use <xref:Microsoft.EntityFrameworkCore.CosmosDbFunctionsExtensions.CoalesceUndefined*> to provide a default value for properties that could be `undefined`:

```csharp
var results = await context.Entities
.Select(x => new
{
Id = EF.Functions.CoalesceUndefined(x.Associate!.NestedAssociate!.Id, Guid.Empty)
})
.ToListAsync();
```

## Low-impact changes

<a name="sqlserver-compatibility-level-160"></a>
Expand Down
19 changes: 19 additions & 0 deletions entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,25 @@ For more information, [see the documentation](xref:core/providers/cosmos/saving#

This feature was contributed by [@JoasE](https://github.com/JoasE) - many thanks!

<a name="cosmos-undefined-projection"></a>

### Consistent behavior for undefined values in projections

Previously, when projecting scalar properties via optional navigations where a document path was absent (resulting in an `undefined` value in Cosmos DB), behavior was inconsistent: single-property anonymous type projections silently dropped those results, while multi-property projections threw a cryptic exception.

EF Core 11 now consistently throws an `InvalidOperationException` when any part of a projection evaluates to `undefined`. Use <xref:Microsoft.EntityFrameworkCore.CosmosDbFunctionsExtensions.IsDefined*> to filter or <xref:Microsoft.EntityFrameworkCore.CosmosDbFunctionsExtensions.CoalesceUndefined*> to provide fallbacks:

```csharp
var results = await context.Entities
.Where(x => EF.Functions.IsDefined(x.Associate!.NestedAssociate!.Id))
.Select(x => new { x.Associate!.NestedAssociate!.Id })
.ToListAsync();
```

For more information, see the [breaking changes documentation](xref:core/what-is-new/ef-core-11.0/breaking-changes#cosmos-undefined-projection).

This feature was contributed by [@JoasE](https://github.com/JoasE) - many thanks!

## Migrations

<a name="migrations-exclude-fk"></a>
Expand Down