EF DbContext Attach仅在禁用代理创建时有效

时间:2019-06-07 07:35:11

标签: entity-framework-6

我遇到一个间歇性接收错误的问题:

  

IEntityChangeTracker的多个实例不能引用一个实体对象

每当尝试将实体附加到DbContext时。

更新:原始帖子在下面,是TL; DR。因此,这是经过更多测试的简化版本。

首先,我得到了Documents集合。此查询返回了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]);                             
}

此方案按预期工作。我可以通过不处理原始uow上下文来强制IEntityChangeTracker错误。

方案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类的一部分,我所有的Entity Repository都继承自该类:

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

0 个答案:

没有答案
相关问题