EF DbContext Attach работает только при отключенном создании прокси - PullRequest
1 голос
/ 07 июня 2019

Я столкнулся с проблемой, когда периодически получал сообщение об ошибке:

На объектный объект нельзя ссылаться несколькими экземплярами IEntityChangeTracker

всякий раз, когда пытается присоединить объект к DbContext.

ОБНОВЛЕНИЕ : Оригинальный пост ниже и TL; DR. Итак, вот упрощенная версия с дополнительным тестированием.

Сначала я получаю коллекцию документов. В этом запросе возвращено 2 элемента.

using (UnitOfWork uow = new UnitOfWork())
{      
    // uncomment below line resolves all errors    
    // uow.Context.Configuration.ProxyCreationEnabled = false; 

    // returns 2 documents in the collection
    documents = uow.DocumentRepository.GetDocumentByBatchEagerly(skip, take).ToList();
}

Сценарий 1:

using (UnitOfWork uow2 = new UnitOfWork())
{
    // This errors ONLY if the original `uow` context is not disposed.       
    uow2.DocumentRepository.Update(documents[0]);                             
}

Этот сценарий работает как ожидалось. Я могу вызвать ошибку IEntityChangeTracker, НЕ удаляя исходный контекст UOW.

Сценарий 2:

Итерация по 2 пунктам в коллекции документов.

foreach (Document document in documents)
{       
    _ = Task.Run(() =>
    {
        using (UnitOfWork uow3 = new UnitOfWork())
        {
            uow3.DocumentRepository.Update(document);
        });
    }
}

Оба элемента не могут подключиться к DbSet с ошибкой IEntityChangeTracker. Иногда каждый добивается успеха, и только один терпит неудачу. Я предполагаю, что это может быть связано с точным временем планировщика заданий. Но даже если они присоединяются одновременно, они являются различными объектами документа. Таким образом, они не должны отслеживаться никаким другим контекстом. Почему я получаю ошибку?

Если я раскомментирую ProxyCreationEnabled = false в исходном контексте uow, этот сценарий работает! Так как же они все еще отслеживаются, даже если контекст был уничтожен? Почему проблема в том, что они являются DynamicProxies, даже если они не привязаны или не отслеживаются каким-либо контекстом.


ОРИГИНАЛЬНЫЙ ПОЧТА:

У меня есть объект сущности с именем Document, и это связанная сущность, которая является коллекцией DocumentVersions.

В приведенном ниже коде объект документа и все связанные с ним объекты, включая DocumentVersions, уже были загружены до того, как его передали этому методу - что я продемонстрирую после.

 public async Task PutNewVersions(Document document)
 {
     // get versions
     List<DocumentVersion> versions = document.DocumentVersions.ToList();

     for (int i = 0; i < versions.Count; i++)
     {
         UnitOfWork uow = new UnitOfWork();
         try
         {
             versions[i].Attempt++;
             //... make some API call that succeeds
             versions[i].ContentUploaded = true;
             versions[i].Result = 1;
         }
         finally
         {                        
             uow.DocumentVersionRepository.Update(versions[i]); // error hit in this method
             uow.Save();                        
         }
     }         
 }

Метод Update просто присоединяет сущность и изменяет состояние. Он является частью класса GenericRepository, от которого наследуются все мои хранилища сущностей:

 public virtual void Update(TEntity entityToUpdate)
 {            
        dbSet.Attach(entityToUpdate); // error is hit here        
        context.Entry(entityToUpdate).State = EntityState.Modified;
 }

Сущность документа и все связанные сущности загружаются с нетерпением, используя метод в хранилище сущностей документа:

public class DocumentRepository : GenericRepository<Document>
{
    public DocumentRepository(MyEntities context) : base(context)
    {
        this.context = context;            
    }

    public IEnumerable<Document> GetDocumentByBatchEagerly(int skip, int take)
    {                
        return (from document in context.Documents                    
                .Include(...)
                .Include(...)
                .Include(...)
                .Include(...)
                .Include(d => d.DocumentVersions)
                .AsNoTracking()
            orderby document.DocumentKey descending
            select document).Skip(skip).Take(take);
    }
}

В описании метода для .AsNoTracking () говорится, что «возвращенные сущности не будут кэшироваться в DbContext». Большой!

Тогда почему вышеописанный метод .Attach () считает, что на эту сущность DocumentVersion уже есть ссылка в другом IEntityChangeTracker? Я предполагаю, что это означает, что на него ссылается другой DbContext, то есть тот, который вызывает GetDocumentByBatchEagerly (). И почему эта проблема только периодически возникает? Кажется, что это случается реже, когда я перешагиваю код.

Я решил эту проблему, добавив следующую строку в конструктор DocumentRepository, приведенный выше:

this.context.Configuration.ProxyCreationEnabled = false;

Я просто не понимаю, почему это решает проблему.

Это также означает, что если я когда-нибудь захочу использовать DocumentRepository для чего-то другого и захочу использовать отслеживание изменений и отложенную загрузку, я не смогу. Похоже, что нет опции «на запрос» для отключения динамических прокси, как в случае «без отслеживания».

Для полноты, вот как используется метод GetDocumentsByBatchEagerly, чтобы продемонстрировать, что он использует свой собственный экземпляр UnitOfWork:

public class MigrationHandler
{  
    UnitOfWork uow = new UnitOfWork();

    public async Task FeedPipelineAsync()
    {
        bool moreResults = true;
        do
        {                
            // documents retrieved with AsNoTracking()
            List<Document> documents = uow.DocumentRepository.GetDocumentByBatchEagerly(skip, take).ToList();

            if (documents.Count == 0) moreResults = false;
            skip += take;

            // push each record into TPL Dataflow pipeline
            foreach (Document document in documents)
            {                    
                // Entry point for the data flow pipeline which links to 
                // a block that calls PutNewVersions()
                await dataFlowPipeline.DocumentCreationTransformBlock.SendAsync(document);
            }
        } while (moreResults);

        dataFlowPipeline.DocumentCreationTransformBlock.Complete();

        // await completion of each block at the end of the pipeline
        await Task.WhenAll(
            dataFlowPipeline.FileDocumentsActionBlock.Completion,
            dataFlowPipeline.PutVersionActionBlock.Completion);  
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...