0

我有一个尝试设置为空的可选外键。无论我尝试了什么,在 SaveChanges() 上,更新语句都会将外键设置为前一个值而不是 null。

简化子类:

public class Child
{
    [Key, Column(Order = 0), ScaffoldColumn(false)]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [ForeignKey("Parent")]
    public int? ParentId { get; set; }

    public virtual Parent Parent { get; set; }
}

简化的父类:

public class Parent
{
    [Key, Column(Order = 0), ScaffoldColumn(false)]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public virtual ICollection<Child> Children { get; set; }
}

我尝试过的事情:

  1. 加载 Child 对象并将 ParentId 设置为 null 并将 Parent 设置为 null
  2. 加载 Child 对象并将 ParentId 设置为 null 并强制修改实体状态
  3. 加载包含 Parent 对象的 Child 对象,然后将值设置为 null 并强制修改实体状态
  4. 加载父对象,然后是子对象和父对象中的 .Remove(child)
  5. 加载 Parent 对象,然后从 Parent 加载 Child 对象和 .Remove(child) 并将 Child.ParentId 设置为 null 并将 Child.Parent 设置为 null。

目前我有:

public void RemoveChildFromParent(int childId, int parentId)
{
    Parent parent = _context.Parents.Include(x => x.Children).FirstOrDefault(u => u.Id == parentId);
    Child child = parent.Children.SingleOrDefault(u => u.Id == childId);
    parent.Children.Remove(child);
    child.ParentId = null;
    child.Parent = null;
    child.StateOfEntity = StateOfEntity.Modified;

    _context.ApplyStateChanges();
    _context.SaveChanges();
}

在保存更改时,SQL 更新语句仍将子对象上的 ParentId 设置为旧值,我收到此错误:

System.InvalidOperationException was unhandled by user code
  HResult=-2146233079
  Message=The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
  Source=System.Data.Entity
  StackTrace:
       at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options)
       at System.Data.Entity.Internal.InternalContext.SaveChanges()
       at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
       at System.Data.Entity.DbContext.SaveChanges()
       at Insight.DataLayer.InsightContext.SaveChanges()
       at Insight.DataLayer.ChildRepository.RemoveChildFromParent(Int32 childId, Int32 parentId)
       at Insight.BusinessLayer.ParentManager.RemoveChild(Int32 id, Int32 parentId)
       at Insight.PresentationLayer.Controllers.ParentController.RemoveChild(Int32 id, Int32 parentId)
       at lambda_method(Closure , ControllerBase , Object[] )
       at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
       at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
       at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<>c__DisplayClass39.<BeginInvokeActionMethodWithFilters>b__33()
       at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()
  InnerException: 

另外,不确定它是否重要,但我有 LazyLoadingEnabled = false 和 AutoDetectChangesEnabled = false。

4

2 回答 2

1

我不确定是否有一个“优雅”的解决方案(也许更改表结构?),但我决定使用DbContext.Database.ExecuteSqlCommand()并手动编写更新语句,而不是在这个小问题上花费更多时间。

这绝对感觉像是在实体框架方法中的一种变通方法,但它仅限于这种情况,需要很少的时间来完成,并且可以按预期工作。

于 2013-10-01T14:14:55.803 回答
0

另外,不确定是否重要,但我有AutoDetectChangesEnabled = false......

是的,这很重要。您是否默认禁用了自动更改检测(例如在您的上下文构造函数中)?这是危险的,因为如果禁用自动检测,您必须知道并理解何时需要手动调用更改检测——这不是一件小事。通常AutoDetectChangesEnabled = false只有在您确定它不会导致意外结果并且确实需要它时才应设置(通常是出于性能原因,在运行具有许多实体更新、插入或删除的批量操作时)。我肯定会true 默认离开。

我不知道究竟是做什么_context.ApplyStateChanges的(似乎是一个自定义方法),但查询后代码的所有其他行都不会调用任何 EF 方法,直到SaveChanges(并且描述的 5 个过程也不是)这正是其中一种情况(如上面链接的博客文章中所述),如果没有进一步注意,禁用自动更改检测将不起作用。

要解决此问题,您可以尝试在之前(或之前)调用_context.DetectChanges();您的代码片段。但是,将所有子项加载到父项的过程非常昂贵,最简单的解决方案就是加载子项,将 FK 设置为并保存更改 - 所有这些都启用了自动更改检测:SaveChangesApplyStateChangesnull

using (var context = new MyContext())
{
    // as said, the following line should be your default
    context.Configuration.AutoDetectChangesEnabled = true;

    var child = context.Children.SingleOrDefault(c => c.Id == childId);
    if (child != null)
    {
        child.ParentId = null;
        context.SaveChanges();
    }
}
于 2013-10-01T17:19:03.997 回答