我遇到一个间歇性接收错误的问题:
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);
}
}