dotnet / dotnet/efcore

Compiled query cache issues with ConstantExpression

Open
#33,673 2 comments 0 reactions 0 assignees View on GitHub
area-query customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

Lets assume we want to execute 2 queries that contains a constant expression with the same object but with different values inside this object. In provided example query has been created with a help of **EF.Const** and with a object (value) of type **List**.

For **queryResultWithOneId** query has been generated correctly.

Generated query:
`SELECT [i].[Id]
FROM [Items] AS [i]
WHERE [i].[Id] = 1`

However after changing contents of **idList** by adding a value (2) and executing the same query second time, generated query is no longer correct as it is exactly the same as the one generated for **queryResultWithOneId**.

Generated query:
`SELECT [i].[Id]
FROM [Items] AS [i]
WHERE [i].[Id] = 1`

Correct query:
`SELECT [i].[Id]
FROM [Items] AS [i]
WHERE [i].[Id] IN (1, 2)`

### How to reproduce

```C#
using Microsoft.EntityFrameworkCore;

using var context = new TestDbContext();

var idList = new List { 1 };

var queryResultWithOneId = context.Items
.Where(x => EF.Constant(idList).Contains(x.Id))
.ToQueryString();

idList.Add(2);

var queryResultWithTwoId = context.Items
.Where(x => EF.Constant(idList).Contains(x.Id))
.ToQueryString();

Console.ReadLine();

public class TestDbContext : DbContext
{
public DbSet Items { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True");
}
}

public class Item
{
public int Id { get; set; }
}
```

### Investigation

After a brief investigation, it became apparent that **ICompiledQueryCache** got a hit and returned a complied query from cache. It seems that **ICompiledQueryCacheKeyGenerator** generates a key based of created expression. This expression contains **ConstantExpression** that holds a reference (or value, depending on the provided constant type) to provided value. Consequently, modifying contents of **idList** also changes value stored in expression stored within cache key. When executing a second query, the query plan cache key has already been altered due to changing **idList** contents. Subsequently, **_memoryCache.TryGetValue** in **ICompiledQueryCache** got a hit and returns compiled query that was created when **idList** contained old values.

In sumary, changing values in reference types referenced in `ConstantExpression` changes cache key of previously generated query plan which causes cache to return invalid query plans.

### Include provider and version information

EF Core version:
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 8.0
Operating system: Windows 10
IDE: Visual Studio 2022 17.8.6

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.