In this post, let's have a look at two new functions introduced in
EF Core 11.0: EF.Functions.JsonPathExists() and EF.Functions.JsonContains(). Both are aimed at making it easier to query JSON data stored in your
database, without falling back to raw SQL.
For SQL Server, JsonPathExists() translates to the
JSON_PATH_EXISTS
function (available since SQL Server 2022), and JsonContains() translates to the
JSON_CONTAINS
function (which is new in SQL Server 2025).
At the time of writing, .NET 11 Preview 3 is the latest and we
need to be using EF Core 11 Preview 3 NuGet packages for these
functions to work.
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="11.0.0-preview.3.26207.106"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="11.0.0-preview.3.26207.106" />
Let's set up a simple Customer with a Contact as a complex type stored as a JSON column. The Contact wraps an Address. Note that JSON_CONTAINS requires SQL Server 2025, so we are also setting the compatibility level to
170 via UseCompatibilityLevel(170). Without that, EF Core won't translate JsonContains() for you.
public class Customer { public int Id { get; set; } public required string Name { get; set; } public required Contact Contact { get; set; } } public class Contact { public required Address Address { get; set; } } public class Address { public required string Street { get; set; } public required string City { get; set; } public required string State { get; set; } public required string PostalCode { get; set; } } public class MyDbContext : DbContext { public DbSet<Customer> Customers { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseSqlServer(@"<ConnectionString>", o => o.UseCompatibilityLevel(170)); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Customer>() .ComplexProperty(c => c.Contact, b => b.ToJson()); } }
EF.Functions.JsonPathExists()
This checks whether a given JSON path exists inside a JSON document. The
nice thing is, since our Contact is a complex type mapped to JSON, we can pass the property directly
and use a typed-style path against the underlying JSON shape. Say we want to
defensively pull customers whose Contact JSON actually has an Address.PostalCode:
// EF.Functions.JsonPathExists(): customers whose Contact JSON has an Address.PostalCode List<Customer> customersWithPostalCode = await context.Customers .Where(c => EF.Functions.JsonPathExists(c.Contact, "$.Address.PostalCode")) .ToListAsync();
And the SQL generated:
SELECT [c].[Id], [c].[Name], [c].[Contact] FROM [Customers] AS [c] WHERE JSON_PATH_EXISTS([c].[Contact], N'$.Address.PostalCode') = 1
EF.Functions.JsonContains()
While EF Core 11 already automatically translates LINQ
Contains queries over primitive collections to JSON_CONTAINS (when targeting SQL Server 2025), there are cases where you want to invoke
it directly, for example, to search for a value at a specific JSON path.
That's where this function comes in.
One small heads-up: at the time of writing, JsonContains() is flagged as experimental (diagnostic EF9106), so you'll need to
suppress it at the call site for the project to build.
Let's pull all the customers in Redmond:
// EF.Functions.JsonContains(): customers in Redmond #pragma warning disable EF9106 // JsonContains is for evaluation purposes only List<Customer> redmondCustomers = await context.Customers .Where(c => EF.Functions.JsonContains(c.Contact, "Redmond", "$.Address.City") == 1) .ToListAsync(); #pragma warning restore EF9106
And the generated SQL:
SELECT [c].[Id], [c].[Name], [c].[Contact] FROM [Customers] AS [c] WHERE JSON_CONTAINS([c].[Contact], N'Redmond', N'$.Address.City') = 1
Both functions can be used with scalar string properties, complex types, and
owned entity types mapped to JSON columns, so as you can see, you don't need
a raw JSON string column to take advantage of these. And as a
bonus, JSON_CONTAINS can take advantage of a
JSON index, if one is defined on the column.
More Read:
Hope this helps.
Happy Coding.
Regards,
Jaliya