From 4e62efe05ff5b8055e26ddf3d200c32b2e6ad6f5 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 5 Feb 2026 22:37:38 +0100 Subject: [PATCH 1/2] Document SQL Server JSON_CONTAINS() See https://github.com/dotnet/efcore/issues/36656 --- .../core/what-is-new/ef-core-11.0/whatsnew.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md index 637c3a86ad..57d260fb1f 100644 --- a/entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md @@ -171,6 +171,35 @@ For more information, [see the documentation](xref:core/providers/cosmos/saving# This feature was contributed by [@JoasE](https://github.com/JoasE) - many thanks! +## SQL Server + + + +### Translate Contains over primitive collections using JSON_CONTAINS + +SQL Server 2025 introduced the [`JSON_CONTAINS`](/sql/t-sql/functions/json-contains-transact-sql) function, which checks whether a value exists in a JSON document. Starting with EF Core 11, LINQ `Contains` queries over primitive (or scalar) collections stored as JSON are translated to use this function, replacing the previous, less efficient `OPENJSON`-based translation. + +For example, the following query checks whether a blog's `Tags` collection contains a specific tag: + +```csharp +var blogs = await context.Blogs + .Where(b => b.Tags.Contains("ef-core")) + .ToListAsync(); +``` + +This generates the following SQL: + +```sql +SELECT [b].[Id], [b].[Name], [b].[Tags] +FROM [Blogs] AS [b] +WHERE JSON_CONTAINS([b].[Tags], 'ef-core') = 1 +``` + +`JSON_CONTAINS()` can notably make use of a [JSON index](/sql/t-sql/statements/create-json-index-transact-sql), if one is defined. + +> [!NOTE] +> `JSON_CONTAINS` does not support searching for null values. As a result, this translation is only applied when EF can determine that at least one side is non-nullable — either the item being searched for (e.g. a non-null constant or a non-nullable column), or the collection's elements. When this cannot be determined, EF falls back to the previous `OPENJSON`-based translation. + ## Migrations From 8de8a3dc85c3f22f1bc8f4edbc6040897af45f59 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 9 Feb 2026 22:10:58 +0100 Subject: [PATCH 2/2] Improvements --- .../core/what-is-new/ef-core-11.0/whatsnew.md | 75 ++++++++++++------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md index 57d260fb1f..b5969366d1 100644 --- a/entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md @@ -171,35 +171,6 @@ For more information, [see the documentation](xref:core/providers/cosmos/saving# This feature was contributed by [@JoasE](https://github.com/JoasE) - many thanks! -## SQL Server - - - -### Translate Contains over primitive collections using JSON_CONTAINS - -SQL Server 2025 introduced the [`JSON_CONTAINS`](/sql/t-sql/functions/json-contains-transact-sql) function, which checks whether a value exists in a JSON document. Starting with EF Core 11, LINQ `Contains` queries over primitive (or scalar) collections stored as JSON are translated to use this function, replacing the previous, less efficient `OPENJSON`-based translation. - -For example, the following query checks whether a blog's `Tags` collection contains a specific tag: - -```csharp -var blogs = await context.Blogs - .Where(b => b.Tags.Contains("ef-core")) - .ToListAsync(); -``` - -This generates the following SQL: - -```sql -SELECT [b].[Id], [b].[Name], [b].[Tags] -FROM [Blogs] AS [b] -WHERE JSON_CONTAINS([b].[Tags], 'ef-core') = 1 -``` - -`JSON_CONTAINS()` can notably make use of a [JSON index](/sql/t-sql/statements/create-json-index-transact-sql), if one is defined. - -> [!NOTE] -> `JSON_CONTAINS` does not support searching for null values. As a result, this translation is only applied when EF can determine that at least one side is non-nullable — either the item being searched for (e.g. a non-null constant or a non-nullable column), or the collection's elements. When this cannot be determined, EF falls back to the previous `OPENJSON`-based translation. - ## Migrations @@ -286,3 +257,49 @@ var results = await context.Blogs Both methods return `FullTextSearchResult`, giving you access to both the entity and the ranking value from SQL Server's full-text engine. This allows for more sophisticated result ordering and filtering based on relevance scores. For more information, see the [full documentation on full-text search](xref:core/providers/sql-server/full-text-search). + + + +### Translate Contains over primitive collections using JSON_CONTAINS + +SQL Server 2025 introduced the [`JSON_CONTAINS`](/sql/t-sql/functions/json-contains-transact-sql) function, which checks whether a value exists in a JSON document. Starting with EF Core 11, when targeting SQL Server 2025, LINQ `Contains` queries over primitive (or scalar) collections stored as JSON are translated to use this function, replacing the previous, less efficient `OPENJSON`-based translation. + +The following query checks whether a blog's `Tags` collection contains a specific tag: + +```csharp +var blogs = await context.Blogs + .Where(b => b.Tags.Contains("ef-core")) + .ToListAsync(); +``` + +Before EF 11 - or when targeting older SQL Server versions - this generates the following SQL: + +```sql +SELECT [b].[Id], [b].[Name], [b].[Tags] +FROM [Blogs] AS [b] +WHERE N'ef-core' IN ( + SELECT [t].[value] + FROM OPENJSON([b].[Tags]) WITH ([value] nvarchar(max) '$') AS [t] +) +``` + +With EF 11, configure EF to target SQL Server 2025 by setting the compatibility level as follows: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder + .UseSqlServer("", o => o.UseCompatibilityLevel(170)); +``` + +At that point, EF will instead generate the following SQL: + +```sql +SELECT [b].[Id], [b].[Name], [b].[Tags] +FROM [Blogs] AS [b] +WHERE JSON_CONTAINS([b].[Tags], 'ef-core') = 1 +``` + +`JSON_CONTAINS()` can notably make use of a [JSON index](/sql/t-sql/statements/create-json-index-transact-sql), if one is defined. + +> [!NOTE] +> `JSON_CONTAINS` does not support searching for null values. As a result, this translation is only applied when EF can determine that at least one side is non-nullable — either the item being searched for (e.g. a non-null constant or a non-nullable column), or the collection's elements. When this cannot be determined, EF falls back to the previous `OPENJSON`-based translation.