8

(注意:这不是这个问题的重复,即使它有相同的例外。)

我有一个穷人的交易,策略是:

  1. 插入父子记录。
  2. 执行长时间运行的操作。
  3. 如果长时间运行失败,去删除之前插入的父子记录。

当我尝试第 3 步时,我收到以下消息:

操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

我大体上理解这意味着什么,但我认为我是在遵守规则,无论我多么努力地遵守规则,我都不确定为什么会收到这条消息。

我们使用自我跟踪实体,我的代码实际上是这样的:

var parent = new Parent(1,2,3);
var child = new Child(4,5,6);
parent.Children.Add(child);

MyContext.Parents.ApplyChanges(parent);
MyContext.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);

// At this point, inserts were successful and entities are in an Unchanged state.
// Also at this point, I see that parent.Children.Count == 1

var shouldDeleteEntities = false;
try
{
  // This is not database-related. This process does some
  // encryption/decryption and uploads some files up to
  // Azure blob storage. It doesn't touch the DB.
  SomeLongRunningProcess();
}
catch
{
  // Oops, something bad happened. Let's delete the entities!
  shouldDeleteEntities = true;
}

// At this point, both entities are in an Unchanged state, child still
// appears in parent.Children, nothing is wrong that I can see.
parent.MarkAsDeleted();
child.MarkAsDeleted();

// I've tried MyContext.ApplyChanges here for both entities, no change.

// At this point, everything appears to be in the state that
// they're supposed to be!
try
{
  MyContext.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
}
catch
{
  // This exception was thrown and I can't figure out why!
}

这个逻辑有什么问题?为什么我不能简单地删除这两条记录?MyContext.ApplyChanges我打电话后试过打电话MarkAsDeleted。我已经尝试了各种各样的事情,无论如何,无论我多么努力地告诉上下文我想要删除它们,它都会不断抛出这个异常。

4

1 回答 1

4

@Slauma在上述评论中提供了这个答案,但要求我发布答案。

问题是实体框架的自我跟踪实体模板中实际上存在一个“错误”(Microsoft 不再建议您使用)。可以在此处找到专门针对此主题的博客文章。

具体来说,问题是 ContextObjectStateManager与(附加的)实体不同步,ChangeTracker.State并且您最终拥有对象,entity.ChangeTracker.State == ObjectState.Deleted但当context.ObjectStateManager认为状态设置为EntityState.Unchanged. 这两个显然是非常不同的。因此,此修复有效地查找附加到上下文的任何对象,EntityState.Unchanged但深入挖掘并检查每个对象的ChangeTracker.State修复ObjectState.Deleted内容。

可以在 Context 的 T4 模板中通过将#region Handle Initial Entity State块替换为以下代码来解决此问题(对我们来说效果很好)的简单且功能非常全面的解决方法:

#region Handle Initial Entity State

var existingEntities = context
    .ObjectStateManager
    .GetObjectStateEntries(System.Data.EntityState.Unchanged)
    .Select(x => x.Entity as IObjectWithChangeTracker)
    .Where(x => x != null);

var deletes = entityIndex.AllEntities
                    .Where(x => x.ChangeTracker.State == ObjectState.Deleted)
                    .Union(existingEntities
                            .Where(x => x.ChangeTracker.State == ObjectState.Deleted));

var notDeleted = entityIndex.AllEntities
                    .Where(x => x.ChangeTracker.State != ObjectState.Deleted)
                    .Union(existingEntities
                            .Where(x => x.ChangeTracker.State != ObjectState.Deleted));

foreach (IObjectWithChangeTracker changedEntity in deletes)
{
    HandleDeletedEntity(context, entityIndex, allRelationships, changedEntity);
}

foreach (IObjectWithChangeTracker changedEntity in notDeleted)
{
    HandleEntity(context, entityIndex, allRelationships, changedEntity);
}

#endregion
于 2013-04-14T19:46:26.193 回答