Wednesday, April 29, 2026

EF Core 11.0: Querying JSON Columns with JsonPathExists and JsonContains

In this post, let's have a look at two new functions introduced in EF Core 11.0EF.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

No comments:

Post a Comment