Summary: Using EF Core lazy loading, I get the message: "A second operation started on this context before a previous operation completed." But it does not seem to be the case. I can work-around it using the "id field" instead of the "lazy-loaded entity". Is there another way?
Here are the long details:
I created a very simple Aspnet.Core WEB API 2.1 with EF Core 2.1.8.
The main point is that i configured the lazy-loading by installing the Microsoft.EntityFrameworkCore.Proxies and configuring it in my DbContext class like this:
  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
    base.OnConfiguring(optionsBuilder);
    optionsBuilder.UseLazyLoadingProxies();
  }
In Startup, i just added:
services.AddDbContext<XDbContext>(options => options.UseSqlServer(connection_string_here));
And here is may API service:
  [HttpGet]
  [Route("test")]
  public ActionResult<IEnumerable<string>> Test()
  {
    string txt = "";
    var sprint = dbContext.SprintDbSet.Find(1);
    txt += sprint.Id + ":";
    foreach (var item in sprint.SprintBacklog) //EXCEPTION HERE
      txt += item.Id + "," + item.PlannedSprint.Id + "," + item.PlannedTask.Id + ". ";
    return Ok(txt);
  }
When i run it, the foreach try to load the collection "lazily", then it throws the following exception:
System.InvalidOperationException HResult=0x80131509 Message=A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe. Source=Microsoft.EntityFrameworkCore StackTrace: at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection() at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext() at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Load[TSource](IQueryable`1 source) at Microsoft.EntityFrameworkCore.Internal.EntityFinder`1.Load(INavigation navigation, InternalEntityEntry entry) at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.Load() at Microsoft.EntityFrameworkCore.Internal.LazyLoader.Load(Object entity, String navigationName) at Microsoft.EntityFrameworkCore.Proxies.Internal.LazyLoadingInterceptor.Intercept(IInvocation invocation) at Castle.DynamicProxy.AbstractInvocation.Proceed() at Castle.Proxies.SprintBacklogItemProxy.get_PlannedTask() at SprintBacklogItem.Equals(Object obj) in SprintBacklogItem.cs:line
As it can be seen in the stack trace, the issue occurs in the Equals of my entity.
  public override bool Equals(object obj) {
    //some lines here...
    return object.Equals(this.PlannedSprint, outro.PlannedSprint) &&
      object.Equals(this.PlannedTask, outro.PlannedTask); //EXCEPTION HERE
  }
It is interessting that it gets PlannedSprint property with no problem. But it throws an exception when it try to get PlannedTask property.
So, it may be relevant to take a look at the mappings:
  public class SprintBacklogItemMapping : IEntityTypeConfiguration<SprintBacklogItem>
  {
    public void Configure(EntityTypeBuilder<SprintBacklogItem> builder)
    {
      builder.ToTable("SprintBacklogItem");
      builder.HasKey(entity => entity.Id);
      builder.Property(entity => entity.Id).IsRequired().ValueGeneratedOnAdd();
      builder.HasOne(e => e.PlannedSprint).WithMany(s => s.SprintBacklog);
      builder.HasOne(e => e.PlannedTask).WithMany().IsRequired();
  }
}
I did the following hated work-around (using the id instead of the entity):
  public override bool Equals(object obj) {
    //some lines here...
    return object.Equals(this.PlannedSprintId, outro.PlannedSprintId) &&
      object.Equals(this.PlannedTaskId, outro.PlannedTaskId);
  }
That is pretty much interesting. It works now, since I got rid of the lazy-loading in the method. But I was expecting an exception in my service, where the lazy-loading still occurs. But it just worked.
I'm very suspicious about EF Core now, since i had to change my model (the equals method of the entity) because of the persistence tech.
My questions:
Extra notes:
Full code to reproduce: https://github.com/brheringer/EFCoreIssue
User contributions licensed under CC BY-SA 3.0