是否真的不可能在EF中更新子集合(也称为非hacky方式)?

war*_*990 11 c# entity-framework entity-framework-6

假设您的实体中有这些类.

public class Parent
{
    public int ParentID { get; set; }
    public virtual ICollection<Child> Children { get; set; }
}

public class Child
{
    public int ChildID { get; set; }
    public int ParentID { get; set; }
    public virtual Parent Parent { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

并且您有一个用户界面来更新ParentChildren,这意味着如果用户添加新的,Child那么您必须插入,如果用户编辑现有的Child则需要更新,如果用户删除了Child则必须删除.现在很明显,如果您使用以下代码

public void Update(Parent obj)
{
    _parent.Attach(obj);
    _dbContext.Entry(obj).State = EntityState.Modified;
    _dbContext.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

它将无法检测到内部的更改,Child因为EF无法检测到导航属性中的更改.

我一直在问这个问题4次,得到的答案很复杂.那么实际上可以做到这一点而不会变得复杂吗?这个问题可以通过分离之间的用户界面解决问题ParentChild,但我不想因为两者合并Child,并Parent在一个菜单是企业应用开发和用户更加友好很常见.

更新:我正在尝试下面的解决方案,但它不起作用.

public ActionResult(ParentViewModel model)
{
    var parentFromDB = context.Parent.Get(model.ParentID);

    if (parentFromDB != null)
    {
        parentFromDB.Childs = model.Childs;
    }

    context.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

EF不会检测孩子内部的变化,也无法说明如何处理孩子.例如,如果parentFromDB我第一次从DB中取出3个孩子,那么我会删除第2个和第3个孩子.然后我The relationship could not be changed because one or more of the foreign-key properties is non-nullable在保存时得到.

我相信这就是发生的事情: 由于一个或多个外键属性不可为空,因此无法更改关系

这让我回到原点,因为在我的场景中,我不能只从数据库中获取并更新条目和调用SaveChanges.

Ger*_*old 11

因为EF无法检测导航属性中的更改

这似乎是一个有点扭曲的描述,_dbContext.Entry(obj).State = EntityState.Modified不会将navigaton属性标记为已修改.

当然EF跟踪导航属性的变化.它跟踪附加到上下文的所有实体的属性和关联的更改.因此,你的问题的答案现在已经明确表示......

是否可以在EF中更新EF中的子集合

......是:是的.

唯一的事情是:你不要开箱即用.

更新任何实体的"开箱即用"方式,无论是父项还是某个集合中的子项,都是:

  • 从数据库中获取实体.
  • 修改其属性或向其集合添加/删除元素
  • 打电话SaveChanges().

就这样.如果跟踪更改,您永远不会State明确设置实体.

但是,在断开连接(n层)的情况下,这会变得更加复杂.我们序列化和反序列化实体,因此不能有任何跟踪其更改的上下文.如果我们想将实体存储在数据库中,那么现在我们的任务就是让EF知道这些变化.基本上有两种方法可以做到这一点:

  • 根据我们对实体的了解手动设置状态(例如:主键> 0表示它们存在且应该更新)
  • 绘制状态:从数据库中检索实体,然后将更改从反序列化的实体重新应用到它们.

在谈到协会时,我们总是要画国家.我们必须从数据库中获取当前实体,并确定添加/删除了哪些子项.没有办法从反序列化的对象图本身推断出这一点.

有各种方法来缓解这种无聊和精心绘制国家的任务,但这超出了本问答的范围.一些参考:


Sea*_*kit 1

因为你这样做很奇怪。

这需要延迟加载来获取子项(显然可以根据您的使用情况进行修改)

//获取父级

var parent = context.Parent.Where(x => x.Id == parentId).SingleOrDefault();
Run Code Online (Sandbox Code Playgroud)

给你写了完整的测试方法。(适用于您的情况)

EmailMessage(parent) 是父级,它没有或有多个 EmailAttachment's(child's)

 [TestMethod]
    public void TestMethodParentChild()
    {
        using (var context = new MyContext())
        {
            //put some data in the Db which is linked
            //---------------------------------
            var emailMessage = new EmailMessage
            {
                FromEmailAddress = "sss",
                Message = "test",
                Content = "hiehdue",
                ReceivedDateTime = DateTime.Now,
                CreateOn = DateTime.Now
            };
            var emailAttachment = new EmailAttachment
            {
                EmailMessageId = 123,
                OrginalFileName = "samefilename",
                ContentLength = 3,
                File = new byte[123]
            };
            emailMessage.EmailAttachments.Add(emailAttachment);
            context.EmailMessages.Add(emailMessage);
            context.SaveChanges();
            //---------------------------------


            var firstEmail = context.EmailMessages.FirstOrDefault(x => x.Content == "hiehdue");
            if (firstEmail != null)
            {
                //change the parent if you want

                //foreach child change if you want
                foreach (var item in firstEmail.EmailAttachments)
                {
                    item.OrginalFileName = "I am the shit";
                }
            }
            context.SaveChanges();


        }
    }
Run Code Online (Sandbox Code Playgroud)

更新

做你的 AutoMappper 的东西......正如你在评论中所说的那样。

然后,当您准备保存并将其恢复为正确的类型时,即一次代表实体(Db),然后执行此操作。

var modelParent= "Some auto mapper magic to get back to Db types."

var parent = context.Parent.FirstOrDefault(x => x.Id == modelParent.Id);
//use automapper here to update the parent again

if (parent != null)
{
  parent.Childs = modelParent.Childs;
}
//this will update all childs ie if its not in the new list from the return 
//it will automatically be deleted, if its new it will be added and if it
// exists it will be updated.
context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)