如何清理Entity Framework对象上下文?

47

我正在向对象上下文中添加多个实体。

try
{
    forach (var document in documents)
    {
        this.Validate(document); // May throw a ValidationException.

        this.objectContext.AddToDocuments(document);
    }

    this.objectContext.SaveChanges();
}
catch
{
    // How to clean-up the object context here?

    throw;
}

如果一些文档通过了验证,而一些文档未通过验证,则所有通过验证的文档仍将添加到对象上下文中。我必须清理对象上下文,因为它可能会被重用,而会发生以下情况。

var documentA = new Document { Id = 1, Data = "ValidData" };
var documentB = new Document { Id = 2, Data = "InvalidData" };
var documentC = new Document { Id = 3, Data = "ValidData" };

try
{
    // Adding document B will cause a ValidationException but only
    // after document A is added to the object context.
    this.DocumentStore.AddDocuments(new[] { documentA, documentB, documentC });
}
catch (ValidationException)
{
}

// Try again without the invalid document B. This causes an exception because
// of a duplicate primary key - document A with id 1 is added a second time.
this.DocumentStore.AddDocuments(new[] { documentA, documentC });
这将再次将文档A添加到对象上下文,并因为有重复的主键而导致SaveChanges()抛出异常。
因此,在出现验证错误的情况下,我必须删除所有已经添加的文档。当然,我可以先执行验证,仅在它们成功验证后添加所有文档,但遗憾的是这并不能解决整个问题 - 如果SaveChanges()失败,则所有文档仍然保持已添加但未保存的状态。
我尝试了分离所有由...返回的对象。
 this.objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added)

但是我得到了一个异常,指出该对象没有附加。那么,如何摆脱所有已添加但未保存的对象呢?


8
我建议不要重复使用对象上下文(ObjectContext)。尽管在某些情况下这样做是有意义的,但这种情况非常罕见。请记住,对象上下文被使用的时间越长,它就会变得越臃肿,如果它进入一个不一致状态(例如,某些操作只执行了部分),那么您可能会遇到一些不一致的副作用。 - Alex James
6个回答

47

Daniel的回答对我有用,但是在6+版本中,EntityFramework API是不同的。这里是我添加到我的自定义仓库容器中的一个方法,它将从DbContext的ChangeTracker中分离所有实体:

    /// <summary>
    /// Detaches all of the DbEntityEntry objects that have been added to the ChangeTracker.
    /// </summary>
    public void DetachAll() {

        foreach (DbEntityEntry dbEntityEntry in this.Context.ChangeTracker.Entries().ToArray()) {

            if (dbEntityEntry.Entity != null) {
                dbEntityEntry.State = EntityState.Detached;
            }
        }
    }

40

这只是一个小错误,但我会把问题留在这里——也许可以帮助其他人。

我遇到了以下问题:

var objectStateEntries = this.objectContext
                             .ObjectStateManager
                             .GetObjectStateEntries(EntityState.Added);

foreach (var objectStateEntry in objectStateEntries)
{
    this.objectContext.Detach(objectStateEntry);
}

虽然我想要以下内容

foreach (var objectStateEntry in objectStateEntries)
{
    this.objectContext.Detach(objectStateEntry.Entity);
}

但是看不到它。


8
顺便提一下,可以使用位运算标志(bitwise flags)来执行以下操作:var objectStateEntries = this._context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged);以分离所有已附加的对象。 - Crab Bucket
请注意,在某些情况下,objectStateEntry.Entity 将为 null(objectStateEntity 描述了与任何实体的关系),因此您应该在其中放置一个空值检查。 - Mark Hildreth

5

EF 5.0 起使用 ChangeTracker.Clear() 方法,该方法可清除 DbContext 中所有已跟踪的实体。


1

对于使用Entity Framework Core 1.0 RC2+的人,我更新了Garrys的答案。

参考资料

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;

更新的代码
        /// <summary>
        /// Detaches all of the EntityEntry objects that have been added to the ChangeTracker.
        /// </summary>
        public void DetachAll()
        {
            foreach (EntityEntry entityEntry in this.Context.ChangeTracker.Entries().ToArray())
            {
                if (entityEntry.Entity != null)
                {
                    entityEntry.State = EntityState.Detached;
                }
            }
        }

1
var entries = ((DbContext)ef).ChangeTracker.Entries();
foreach (var entry in entries)
{
    entry.State = System.Data.EntityState.Detached;
}

1
有点晚了,但你考虑过工作单元模式吗?
简单来说,它允许你拥有一个事务,然后可以回滚(dispose)或提交(savechanges)该事务。
注意:这里的“事务”是指将更改分组为一次操作,而不是 SQL 的 BEGIN TRAN。所有这些都由 SaveChanges() 方法为你处理。
类似于:
Public Class UnitOfWork
    Implements IUnitOfWork
    Implements IDisposable
    Private ReadOnly Property ObjectContext As Interfaces.IObjectContext

    Public Sub New(ByVal objectContext As Interfaces.IObjectContext)
        _objectContext = objectContext
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        If _objectContext IsNot Nothing Then
            _objectContext.Dispose()
        End If
        GC.SuppressFinalize(Me)
    End Sub

    Public Sub Commit() Implements IUnitOfWork.Commit
        _objectContext.SaveChanges()
    End Sub
End Class

每次创建工作单元时,它都会接受一个对象上下文 - 我们使用Unity生成这些,以便在旧的上下文被处理后创建一个新的上下文。

2
使用工作单元仍然不能将对象从上下文中分离出来。它也无法回答这个问题。 - Agent Shark
@AgentShark 这完全取决于您如何实现您的“Rollback”方法。此外,在已接受的答案中重复正确代码是没有意义的,因为它明确显示了如何分离对象。 - Basic
我重新阅读了这个问题。是的,通过工作单元强制更新会很有用。然而,在EF中,当添加具有相关实体的对象时,您必须小心。存在不同的方法和不同的行为来将对象添加/保存到上下文中。 - Agent Shark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接