-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Precompiled queries assume the model for a given DbContext will never change #13483
Comments
updating to Microsoft.EntityFrameworkCore.InMemory Version="2.1.4" did not resolve the issue |
from the in memoryQuery context i get from the precompiled view when removing firstordefault I see that the table contains the values but the enumerable is based on a snapshot that does not contain the values! _innerEnumerable.results.source.source. Microsoft.EntityFrameworkCore.InMemory.Storage.Internal.InMemoryTableSnapshot = 0 |
creating a new compiled query also returns the correct result |
meh I dont get it if I do the exact same code as in EF.CompileQuery in my class it works if I use the default method I get an empty snapshot. |
removing static from the FindItemQuery also fixed the issue, so ill put the Precompiled Queries into a di Singleton instead of using static |
Likely, you are not using same InMemoryDatabase. InMemoryDatabase may change if service provider is re-created. |
@JanEggers Can you post a runnable project/solution or complete code listing that demonstrates the issue? @smitpatel That was my thought too, but then, I can't reconcile that with, "If the test is executed alone it works most of the time. If I execute all the tests it sometimes returns null." |
I will try to create a repro. |
here is a repro: and yes it is another context but that should not be an issue as I pass the new context to the precompiled query when executing it. |
so this is the problem? I think that if the context is captured inside the compiled query it should be provided as a constructor argument. passing it with every execution makes no sense when there is some state leaking from the first usage. |
why isnt that something like: (with a const or some reflection magic to get the context name) if (typeof(TContext).GetTypeInfo().IsAssignableFrom(parameterExpression.Type.GetTypeInfo()))
{
return Expression.Parameter(
parameterExpression.Type,
"context")
} and here: queryContext.AddParameter(
"context",
context); |
Note for triage: something strange is going on here with the in-memory database. Repro code is below. Running with SQL Server results in:
while with the only change being
Note that the internal service provider is being managed by EF, The service provider created in the code is only used for SQL Server log fragment:
In-memory log:
Repro code: public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
private static readonly LoggerFactory Logger
= new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseLoggerFactory(Logger);
public DbSet<Item> Items { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyServiceCollection : ServiceCollection
{
public MyServiceCollection(string dbName)
{
this.AddDbContext<MyDbContext>(
o => o.UseInMemoryDatabase($@"Server=(localdb)\mssqllocaldb;Database={dbName};ConnectRetryCount=0"));
}
}
public class UnitTest1
{
public static readonly Func<MyDbContext, Item> PreCompiled = EF.CompileQuery<MyDbContext, Item>(ctx => ctx.Items.SingleOrDefault());
public void Test1()
{
using (var services = new MyServiceCollection("FOO").BuildServiceProvider())
{
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var ctx = scope.ServiceProvider.GetRequiredService<MyDbContext>();
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
ctx.Add(new Item { Name = "One" });
ctx.SaveChanges();
var item = PreCompiled(ctx);
Console.WriteLine($"Query one: {item?.Name}");
}
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var ctx = scope.ServiceProvider.GetRequiredService<MyDbContext>();
var item = PreCompiled(ctx);
Console.WriteLine($"Query two: {item?.Name}");
}
}
using (var services = new MyServiceCollection("BAR").BuildServiceProvider())
{
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var ctx = scope.ServiceProvider.GetRequiredService<MyDbContext>();
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
ctx.Add(new Item { Name = "Two" });
ctx.SaveChanges();
var item = PreCompiled(ctx);
Console.WriteLine($"Query one: {item?.Name}");
}
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var ctx = scope.ServiceProvider.GetRequiredService<MyDbContext>();
var item = PreCompiled(ctx);
Console.WriteLine($"Query two: {item?.Name}");
}
}
}
} |
I investigated further and guess I found the issue: the table is not found at that line because because the EntityType are not ReferenceEqual because one is from the first model and the other is from the second model the easiest fix would be to enable matching by name that is already in place but I dont know why it is disabled by default |
…nt in memory database - locate inmemory tables by entitytype.toString() - removed option useNameMatching as the new method works for all scenarios fixes dotnet#13483
@JanEggers Did you root cause why the in-memory database is rooted in a different internal service provider? |
@ajcvickers I did not check. but the different database is because of the different strings provided in UseInMemoryDatabase. my understanding is that each unique string gets its own database. and that is the way I intended my tests so I get a fresh isolated database for each test. from my point of view the real problem is that the entity types are not ref equal. so I guess i could also workaround the issue by creating the model first with a modelbuilder and then use the same model for multiple contexts. |
Notes from triage: we think there are two things going here:
|
Currently the query cache has 4 core components
So we are safe from query cache side. |
Note from triage: this is blocked on the new compiled query API. With the new API it should be possible to use the same DbContext type with different underlying models. However, specifying the model to use explicitly may be required when compiling the query. |
To continue the above, since compiled queries are a high-perf thing, we should avoid perform an additional lookup at runtime on the model. So the model could be referenced by the compiled query itself, and on execution simply compared with the given context's model (throw if not the same). |
Steps to reproduce
when I use EF.CompileQuery to generate a Query it fails to find the result. executing the linq normally returns the result.
I also verified that it is not a threading issue. the item is there all the time and if im setting the debugger to call FindItemQuery again it still returns null.
The behavior is observed in a unittest. using xunit. If the test is executed alone it works most of the time. If I execute all the tests it sometimes returns null
Further technical details
EF Core version: Microsoft.EntityFrameworkCore.InMemory Version="2.1.2"
Operating system: Win 10
IDE: (Visual Studio 2017 15.8)
The text was updated successfully, but these errors were encountered: