CollectionEntry created while materializing is stale if used later
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
Probably also true for EntityEntry and ReferenceEntry.
Repro:
```C#
public class Blog
{
public int Id { get; set; }
public IEntityCollection Posts { get; set; } = null!;
}
public class Post
{
public int Id { get; set; }
public int? BlogId { get; set; }
public Blog? Blog { get; set; }
}
public interface IEntityCollection : ICollection
{
bool IsLoaded { get; }
}
public class EntityCollection : List, IEntityCollection
{
private readonly CollectionEntry? _collectionEntry;
public EntityCollection()
{
}
public EntityCollection(CollectionEntry collectionEntry)
{
_collectionEntry = collectionEntry;
}
public bool IsLoaded
=> _collectionEntry?.IsLoaded ?? false;
}
public class SomeDbContext : DbContext
{
private static readonly MaterializationInterceptor MaterializationInterceptor = new();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.AddInterceptors(MaterializationInterceptor)
.UseSqlServer(@"Data Source=(LocalDb)\MSSQLLocalDB;Database=AllTogetherNow")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
public DbSet Blogs => Set();
}
public class Program
{
public static void Main()
{
using (var context = new SomeDbContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.Add(
new Blog
{
Posts = new EntityCollection { new(), new(), new() }
});
context.SaveChanges();
}
using (var context = new SomeDbContext())
{
var blog = context.Blogs.Single();
Console.WriteLine(blog.Posts.IsLoaded);
context.Entry(blog).Collection(e => e.Posts).Load();
Console.WriteLine(blog.Posts.IsLoaded);
}
}
}
public class MaterializationInterceptor : IMaterializationInterceptor
{
public object CreatedInstance(MaterializationInterceptionData materializationData, object entity)
{
var context = materializationData.Context;
foreach (var navigation in materializationData.EntityType.GetNavigations())
{
if (navigation.ClrType.IsGenericType
&& navigation.ClrType.GetGenericTypeDefinition() == typeof(IEntityCollection<>))
{
var collectionEntry = context.Entry(entity).Collection(navigation);
entity.GetType().GetProperty(navigation.Name)!.SetValue(
entity, Activator.CreateInstance(
typeof(EntityCollection<>).MakeGenericType(
navigation.TargetEntityType.ClrType), collectionEntry));
}
}
return entity;
}
}
```
Contributor guide
Assessment
This issue has not been assessed yet.