EF Core отложенная загрузка приводит к исключению в методе Equals объекта - PullRequest
0 голосов
/ 14 февраля 2019

Резюме: Используя ленивую загрузку EF Core, я получаю сообщение: «Вторая операция началась в этом контексте перед завершением предыдущей операции».Но, похоже, это не так.Я могу обойти это, используя «поле id» вместо «ленивый объект».Есть ли другой способ?

Вот подробности:

Я создал очень простой Aspnet.Core WEB API 2.1 с EF Core 2.1.8.

Главноеявляется то, что я настроил отложенную загрузку, установив Microsoft.EntityFrameworkCore.Proxies и настроив его в моем классе DbContext следующим образом:

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
    base.OnConfiguring(optionsBuilder);
    optionsBuilder.UseLazyLoadingProxies();
  }

При запуске я просто добавил:

services.AddDbContext<XDbContext>(options => options.UseSqlServer(connection_string_here));

А вот и сервис API:

  [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);
  }

Когда я запускаю его, foreach пытается загрузить коллекцию «лениво», а затем выдает следующее исключение:

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это можно увидеть в трассировке стека, проблема возникает в Equals моего объекта.

  public override bool Equals(object obj) {
    //some lines here...
    return object.Equals(this.PlannedSprint, outro.PlannedSprint) &&
      object.Equals(this.PlannedTask, outro.PlannedTask); //EXCEPTION HERE
  }

Интересно, что он получает свойство PlannedSprint без проблем.Но он выдает исключение, когда пытается получить свойство PlannedTask.

Итак, может быть уместно взглянуть на сопоставления:

  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();
  }
}

Я сделал следующий ненавистный обход(используя идентификатор вместо сущности):

  public override bool Equals(object obj) {
    //some lines here...
    return object.Equals(this.PlannedSprintId, outro.PlannedSprintId) &&
      object.Equals(this.PlannedTaskId, outro.PlannedTaskId);
  }

Это довольно интересно.Теперь это работает, так как я избавился от ленивой загрузки в методе.Но я ожидал исключения в моем сервисе, где ленивая загрузка все еще происходит.Но это просто сработало.

Сейчас я очень подозрительно отношусь к EF Core, поскольку мне пришлось изменить свою модель (метод равных сущностей) из-за технологии постоянства.

Мои вопросы:

  1. Есть ли другое решение?
  2. Мои оригинальные Equals не работали, потому что я что-то не так сделал с EF Core?Я что-то неправильно понимаю?
  3. Если я не могу вернуться к своей первоначальной реализации Equals, то как узнать, когда я могу доверять при отложенной загрузке?

Дополнительные примечания:

  1. NHibernate отлично с этим работает.На самом деле, я перевожу систему из NH в EF.
  2. В исходном проекте, если я изменю DbContext с Scoped на Singleton, он работает.Я потратил 2 дня на расследование, если два экземпляра работают одновременно.Затем я сделал совершенно новый проект, чрезвычайно упрощенный, чтобы сузить ситуацию.В новом проекте, когда тест DbContext выполняется в качестве одиночного, возникает другое исключение: невозможно получить доступ к удаленному объекту.Распространенной причиной этой ошибки является удаление контекста, который был разрешен путем внедрения зависимости, а затем попытка использовать тот же экземпляр контекста в другом месте вашего приложения.Это может произойти, если вы вызываете Dispose () для контекста или заключаете контекст в оператор using.Если вы используете внедрение зависимостей, вы должны позволить контейнеру внедрения зависимостей позаботиться об удалении экземпляров контекста.
  3. Я провел множество тестов, изменяя жизненный цикл DbContext, оставляя событие DI и создавая его экземпляр внутри метода API,но результат тот же.
  4. Я читал много постов в StackOverflow и других источниках об одном и том же исключении, но большинство из них касались явных асинхронных операций.

Полный код для воспроизведения: https://github.com/brheringer/EFCoreIssue

...