由于一个或多个外键属性不可为空,因此无法更改关系

Jax*_*ian 8 c# sql-server .net-4.0 entity-framework-4

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

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

  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!
}
Run Code Online (Sandbox Code Playgroud)

这个逻辑出了什么问题?为什么我不能简单地删除这两个记录?我打电话MyContext.ApplyChanges后试过打电话MarkAsDeleted.我已经试过各种东西而不管是什么,不管我如何努力,告诉我要的语境两个人删除,不断抛出此异常.

Jax*_*ian 4

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

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

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

#region Handle Initial Entity State可以在 Context 的 T4 模板中通过用以下代码替换该块来针对此问题创建一个简单且功能非常完善的解决方法(对我们来说效果很好) :

#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
Run Code Online (Sandbox Code Playgroud)