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