Я столкнулся с проблемой, когда периодически получал сообщение об ошибке:
На объектный объект нельзя ссылаться несколькими экземплярами 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);
}
}