【问题标题】:Entity framework, problems updating related objects实体框架,更新相关对象的问题
【发布时间】:2012-10-25 12:36:39
【问题描述】:

我目前正在使用最新版本的 Entity Framework 开发一个项目,但遇到了一个似乎无法解决的问题。

当涉及到更新现有对象时,我可以相当容易地更新对象属性,直到它涉及到一个引用另一个类的属性。

在下面的示例中,我有一个名为 Foo 的类,它存储各种属性,其中 2 个是其他类的实例

public class Foo
{
     public int Id {get; set;}
     public string Name {get; set;}
     public SubFoo SubFoo {get; set}
     public AnotherSubFoo AnotherSubFoo {get; set}
}

当我使用下面的Edit() 方法时,我传入了我希望更新的对象,我可以设法让Name 正确更新,但是我还没有设法找到一种方法来获取SubFoo 的属性要更改。例如,如果SubFoo 类具有Name 属性,并且该属性已更改并且在我的数据库和newFoo 之间有所不同,则不会更新。

public Foo Edit(Foo newFoo)
{
    var dbFoo = context.Foo
        .Include(x => x.SubFoo)
        .Include(x => x.AnotherSubFoo)
        .Single(c => c.Id == newFoo.Id);

    var entry = context.Entry<Foo>(dbFoo);
    entry.OriginalValues.SetValues(dbFoo);
    entry.CurrentValues.SetValues(newFoo);

    context.SaveChanges();

    return newFoo;
}

任何帮助或指点将不胜感激。

更新: 根据 Slauma 的评论,我已将方法修改为

public Foo Edit(Foo newFoo)
{
    var dbFoo = context.Foo
        .Include(x => x.SubFoo)
        .Include(x => x.AnotherSubFoo)
        .Single(c => c.Id == newFoo.Id);

    context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
    context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo);

    context.SaveChanges();

    return newFoo;
}

现在运行时,我收到错误:

实体类型 Collection`1 不是当前模型的一部分 上下文。

为了解决这个问题,我添加了代码来尝试将 newFoo 子类附加到上下文中,但是这是通过错误提示 ObjectManager 已经有一个相同的实体:

ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同的多个对象 键

【问题讨论】:

    标签: c# entity-framework ef-code-first


    【解决方案1】:

    CurrentValues.SetValues 仅更新标量属性但不更新相关实体,因此您必须对每个相关实体执行相同操作:

    public Foo Edit(Foo newFoo)
    {
        var dbFoo = context.Foo
                           .Include(x => x.SubFoo)
                           .Include(x => x.AnotherSubFoo)
                           .Single(c => c.Id == newFoo.Id);
    
        context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
        context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo);
        context.Entry(dbFoo.AnotherSubFoo).CurrentValues.SetValues(newFoo.AnotherSubFoo);
    
        context.SaveChanges();
    
        return newFoo;
    }
    

    如果可以完全删除或创建关系,您还需要明确处理这些情况:

    public Foo Edit(Foo newFoo)
    {
        var dbFoo = context.Foo
                           .Include(x => x.SubFoo)
                           .Include(x => x.AnotherSubFoo)
                           .Single(c => c.Id == newFoo.Id);
    
        context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
        if (dbFoo.SubFoo != null)
        {
            if (newFoo.SubFoo != null)
            {
                if (dbFoo.SubFoo.Id == newFoo.SubFoo.Id)
                    // no relationship change, only scalar prop.
                    context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo);
                else
                {
                    // Relationship change
                    // Attach assumes that newFoo.SubFoo is an existing entity
                    context.SubFoos.Attach(newFoo.SubFoo);
                    dbFoo.SubFoo = newFoo.SubFoo;
                }
            }
            else // relationship has been removed
                dbFoo.SubFoo = null;
        }
        else
        {
            if (newFoo.SubFoo != null) // relationship has been added
            {
                // Attach assumes that newFoo.SubFoo is an existing entity
                context.SubFoos.Attach(newFoo.SubFoo);
                dbFoo.SubFoo = newFoo.SubFoo;
            }
            // else -> old and new SubFoo is null -> nothing to do
        }
    
        // the same logic for AnotherSubFoo ...
    
        context.SaveChanges();
    
        return newFoo;
    }
    

    如果关系已更改标量属性,您最终还需要将附加实体的状态设置为Modified

    编辑

    如果 - 根据您的评论 - Foo.SubFoo 实际上是一个集合而不仅仅是一个引用,您将需要这样的东西来更新相关实体:

    public Foo Edit(Foo newFoo)
    {
        var dbFoo = context.Foo
                           .Include(x => x.SubFoo)
                           .Include(x => x.AnotherSubFoo)
                           .Single(c => c.Id == newFoo.Id);
    
        // Update foo (works only for scalar properties)
        context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
    
        // Delete subFoos from database that are not in the newFoo.SubFoo collection
        foreach (var dbSubFoo in dbFoo.SubFoo.ToList())
            if (!newFoo.SubFoo.Any(s => s.Id == dbSubFoo.Id))
                context.SubFoos.Remove(dbSubFoo);
    
        foreach (var newSubFoo in newFoo.SubFoo)
        {
            var dbSubFoo = dbFoo.SubFoo.SingleOrDefault(s => s.Id == newSubFoo.Id);
            if (dbSubFoo != null)
                // Update subFoos that are in the newFoo.SubFoo collection
                context.Entry(dbSubFoo).CurrentValues.SetValues(newSubFoo);
            else
                // Insert subFoos into the database that are not
                // in the dbFoo.subFoo collection
                dbFoo.SubFoo.Add(newSubFoo);
        }
    
        // and the same for AnotherSubFoo...
    
        db.SaveChanges();
    
        return newFoo;
    }
    

    【讨论】:

    • 我不明白为什么 EF 在类似情况下如此过分。必须有更简单的方法来进行此类更新。
    • 当我实现这个时,我得到了这样的错误; “属性‘Id’是对象关键信息的一部分,不能修改。”
    • 在编辑部分我们需要再添加一个条件来检查 id == 0 类似 if (dbSubFoo != null && dbSubFoo.ID != 0) 因为如果你有 5 个新项目那么所有id 为 0,一旦我们添加第一个,以后所有的都将被视为现有,因为它会发现 id 为 0 的项目作为现有
    • @AnshulNigam,好点:)。对我来说 dbSubFoo.Id != Guid.Empty
    • 如果检查新添加的 id ==0,添加 "&& newSubFoo.Id != 0" 到 var dbSubFoo = dbFoo.SubFoo.SingleOrDefault(s => s.Id == newSubFoo.Id) ;以及允许添加超过 1 条新记录。
    【解决方案2】:

    只是想我会发布下面的链接,因为它确实帮助我了解了如何更新相关实体。

    Updating related data with the entity framework in an asp net mvc application

    注意:我确实稍微更改了 UpdateInstructorCourses 函数中显示的逻辑以满足我的需要。

    【讨论】:

      【解决方案3】:

      你也可以独立调用表格

      MyContext db = new MyContext
      // I like using asynchronous calls in my API methods 
      var OldFoo = await db.Foo.FindAsync(id);
      var OldAssociateFoo = db.AssociatedFoo;  
      var NewFoo = OldFoo;
      var NewAssociatedFoo = OldAssociatedFoo;
      
      NewFoo.SomeValue = "The Value";
      NewAssociatedFoo.OtherValue = 20;
      
      db.Entry(OldFoo).CurrentValues.SetValues(NewFoo);
      db.Entry(OldAssociatedFoo).CurrentValues.SetValues(NewAssociatedFoo);
      
      await db.SaveChangesAsync();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-10-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-19
        • 1970-01-01
        相关资源
        最近更新 更多