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
Query: review (JSON) materializer code for queries with NoTrackingWithIdentityResolution #33097
Comments
issues to investigate: Json_branch_collection_distinct_and_other_collection - NRE Basic_json_projection_owner_entity_duplicated_NoTracking - invalid token |
Json_branch_collection_distinct_and_other_collection
Reason for NRE exceptions is that in InjectEntityMaterializers (specifically in ProcessEntityShaper) we build a piece of code which will try to retrieve entity from the state manager based on key. expressions.Add(
Assign(
entryVariable,
Call(
QueryCompilationContext.QueryContextParameter,
TryGetEntryMethodInfo,
Constant(primaryKey),
NewArrayInit(
typeof(object),
primaryKey.Properties
.Select(
p => valueBufferExpression.CreateValueBufferReadValueExpression(
typeof(object),
p.GetIndex(),
p))),
Constant(!shaper.IsNullable),
hasNullKeyVariable))); we loop through primary key properties and try to extract the values to get the key. This works fine for Owned types, but not for JSON when collections are involved. For JSON, we have synthesized key which doesn't correspond to anything in the database, we construct it during materialization, but the code here doesn't account for that - we try to find that value from the reader, we don't so we put null value instead. This in turn marks the key as nullKey and as a result entire entity (that should be there) is not being materialized, but the null value is returned. If there is supposed to be another nested collection afterwards, NRE is thrown. |
Example test case: [ConditionalFact]
public void Test_NTWIR_owned_type()
{
using (var ctx = new MyContext())
{
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
var e = new MyEntity
{
Owned = new OwnedRoot
{
Number = 10,
Nested = new List<OwnedBranch>
{
//new OwnedBranch { Foo = "f1", InnerNested = new List<OwnedLeaf> { new OwnedLeaf { Bar = 55 } } },
//new OwnedBranch { Foo = "f2", InnerNested = new List<OwnedLeaf> { new OwnedLeaf { Bar = 266 }, new OwnedLeaf { Bar = 277 } } }
new OwnedBranch { Foo = "f1" },
new OwnedBranch { Foo = "f2" }
}
}
};
ctx.Entities.Add(e);
ctx.SaveChanges();
}
using (var ctx = new MyContext())
{
var result = ctx.Entities
.OrderBy(x => x.Id)
.Select(
x => new
{
First = x.Owned.Nested.ToList(),
})
.AsNoTrackingWithIdentityResolution().ToList();
}
}
public class MyContext : DbContext
{
public DbSet<MyEntity> Entities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>().OwnsOne(x => x.Owned, b =>
{
b.ToJson();
//b.OwnsMany(xx => xx.Nested, bb => bb.OwnsMany(xxx => xxx.InnerNested));
b.OwnsMany(xx => xx.Nested);
});
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Repro;Trusted_Connection=True;MultipleActiveResultSets=true");
}
}
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public OwnedRoot Owned { get; set; }
}
public class OwnedRoot
{
public int Number { get; set; }
public List<OwnedBranch> Nested { get; set; }
}
public class OwnedBranch
{
public string Foo { get; set; }
//public List<OwnedLeaf> InnerNested { get; set; }
}
//public class OwnedLeaf
//{
// public int Bar { get; set; }
//} working shaper (regular owned):
failing shaper (json)
|
covered by #33101, closing |
JSON specific materializer code treats NoTrackingWithIdentityResolution as if it was a regular NoTracking query. However, EntityMaterializerInjectingExpressionVisitor mostly pays attention to the presence of change tracker (i.e. materializer template code that is generated is roughly the same between Tracking and NoTrackingWithIdentityResolution.
This results in discrepancy, that can lead to bugs (e.g. #33073)
On top of that, we block some scenarios for Tracking or NoTracking queries, e.g. you can't project owned type without it's owner in the tracking query. But what should be the behavior for NoTrackingWithIdentityResolution? We should look at all those cases as well.
The text was updated successfully, but these errors were encountered: