【问题标题】:Entity Framework won't detect changes of navigation properties实体框架不会检测到导航属性的变化
【发布时间】:2012-07-25 02:14:49
【问题描述】:

我无法检测导航属性的变化:

我的测试模型如下所示:

public class Person
{
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual Address Address { get; set; }
}

public class Address
{
    public int Id { get; set; }

    public string Name { get; set; }
}

我创建并保存了一个 Person 类型的对象,并分配了 Name 和 Address 属性。我的问题是,如果我从数据库中取回 Person 对象并更改 Address 属性(例如更改为 Null),那么 e.f.没有检测到变化! 我的代码是这样的:

using (var ctx = new EFContext())
{
    Person p = ctx.People.First();
    //p.Address IS NOT NULL!
    p.Address = null;
    var entry = ctx.Entry(p);
}

为什么entry.State不变

编辑:如果我调用 SaveChanges,记录被正确保存(地址变为空)!

编辑 2: 我已按照比利的建议创建了外键属性,如果我在 Visual Studio 中检查 Person 对象,则状态已修改。如果我不停止调试器检查对象的值状态不变!

编辑 3: 使用 ctx.People.Include(x => x.Address).First(); 加载 Person 对象解决问题。有没有办法避免调用 Include 并继续修改 Address 属性而不是 AddressId ?

【问题讨论】:

  • 如果调用 ctx.DetectChanges() 会发生什么?
  • 没什么。结果是一样的!!

标签: c# entity-framework-4


【解决方案1】:

首先:您必须按照@billy 的建议使用Include。您的评论“p.Address IS NOT NULL!”是正确的,因为您正在调试器中观看p.Address,从而触发了调试器中的延迟加载,因此将地址设置更改为@987654324检测到@。在发布模式下或当您不检查调试器中的属性时,您的代码将无法工作并且不会保存任何更改。

所以,编辑 3 的答案是:否。

第二:var entry = ctx.Entry(p) 只返回实体状态,而你并没有改变实体状态而是关系状态,或者更准确地说是你删除 关系。您无法使用DbContext API 检查关系状态,而只能使用ObjectContext API:

Person p = ctx.People.Include(x => x.Address).First();
p.Address = null;
var objCtx = ((IObjectContextAdapter)ctx).ObjectContext;
var objentr = objCtx.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted);

objentr 现在将有一个 RelationshipEntry 类型的条目:

当你调用SaveChanges()并删除关系时,EF会考虑这个关系条目和实体状态条目,即将数据库中PersonAddress外键列设置为NULL

关于编辑 2:更改外键属性(这是模型中的标量属性)是实体本身的更改,因此在这种情况下实体状态将为 Modified

【讨论】:

  • 不错的答案,但我有疑问。我如何确定特定条目发生了这种变化而不是其他变化,因为这是对特定上下文的查询。
  • 如何在 EF Core 中执行此操作,因为没有 ObjectContext
  • 我一直在使用类似的代码来跟踪更改。最近我开始在我的模型中添加外键。但是当我删除一个实体时,我也有外键的关系不会出现在ObjectStateManager 的已删除关系列表中。你知道这是为什么吗?
【解决方案2】:

您需要包含地址导航。支柱。在您的查询中,否则 EF 在您保存时不会考虑对其进行更改:

using (var ctx = new EFContext())
{
    Person p = ctx.People.Include(x => x.Address).First();
    //p.Address IS NOT NULL!
    p.Address = null;
    var entry = ctx.Entry(p);
}

你也可以在你的模型中使用外键,我非常喜欢:

public class Person
{
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual Address Address { get; set; }

    public int? AddressId {get; set;}
}

...

using (var ctx = new EFContext())
{
    Person p = ctx.People.First();
    p.AddressId = null;
    var entry = ctx.Entry(p);
}

【讨论】:

  • 我已经用 DetectChanges 测试了解决方案,但没办法.. 仍然处于未更改状态!如果我使用int?外键我想它会正常工作,因为我要更改“简单”属性的值,而不是直接更改导航属性。事实上,如果我更改 Person 对象的 Name 属性,一切正常!
【解决方案3】:

在我的应用程序中,在请求重新加载或用户离开项目/视图之前,我会执行一些检查以确保没有未保存的更改。

这基本上超出了当前接受的答案,但我想提供一个实现并提请注意,您必须在ObjectContext.ObjectStateManager 接收关系更改之前调用Context.ChangeTracker.DetectChanges()!我花了很多时间调试这个,傻!

_EagleContext.ChangeTracker.DetectChanges();

var objectContext = ((IObjectContextAdapter)_EagleContext).ObjectContext;
var changedEntities = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified);

if (_EagleContext.ChangeTracker.Entries().Any(e => e.State == EntityState.Modified)
    || changedEntities.Count() != 0)
{
    var dialogResult = MessageBox.Show("There are changes to save, are you sure you want to reload?", "Warning", MessageBoxButton.YesNo);
    if (dialogResult == MessageBoxResult.No)
    {
        return;
    }
}

// Continue with reloading...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-29
    • 2016-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多