dotnet / dotnet/efcore

CollectionEntry created while materializing is stale if used later

Open
#29,969 0 comments 0 reactions 0 assignees View on GitHub
area-change-tracking
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

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.