我们假设你的实体中有这些课程。
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; }
}
您有一个用户界面来更新Parent
及其Children
,这意味着如果用户添加新Child
,则必须插入,如果用户编辑现有{ {1}}然后您需要更新,如果用户删除Child
,则必须删除。现在很明显,如果你使用以下代码
Child
它无法检测到public void Update(Parent obj)
{
_parent.Attach(obj);
_dbContext.Entry(obj).State = EntityState.Modified;
_dbContext.SaveChanges();
}
内的更改,因为EF无法检测到导航属性中的更改。
我一直在问这个问题4次,得到的答案很复杂。那么实际上可以做到这一点而不会变得复杂吗?此问题可以通过在Child
和Parent
之间分隔用户界面来解决问题,但我不想,因为在一个菜单中合并Child
和Child
是在业务应用程序开发中非常常见,并且更加用户友好。
更新: 我正在尝试下面的解决方案,但它不起作用。
Parent
EF没有检测到儿童内部的变化,而是无法告诉孩子如何处理。例如,如果public ActionResult(ParentViewModel model)
{
var parentFromDB = context.Parent.Get(model.ParentID);
if (parentFromDB != null)
{
parentFromDB.Childs = model.Childs;
}
context.SaveChanges();
}
第一次从DB中提取3个孩子,那么我会删除第2个和第3个孩子。然后我在保存时获得parentFromDB
。
我相信这就是发生的事情: The relationship could not be changed because one or more of the foreign-key properties is non-nullable
这让我回到原点,因为在我的场景中,我无法从数据库中获取并更新条目并致电The relationship could not be changed because one or more of the foreign-key properties is non-nullable
。
答案 0 :(得分:10)
因为EF无法检测导航属性中的更改
这似乎是对_dbContext.Entry(obj).State = EntityState.Modified
未将导航属性标记为已修改的事实的一种有点扭曲的描述。
当然EF跟踪导航属性的变化。它跟踪附加到上下文的所有实体的属性和关联的更改。因此,你的问题的答案,现在已经积极陈述......
是否可以立即更新EF中的子集合
...是:是。
唯一的事情是:你没有开箱即用 。
更新任何实体的“开箱即用”方式,无论是父项还是某个集合中的子项,都是:
SaveChanges()
。这就是全部。如果跟踪更改,您永远不会明确设置实体State
。
但是,在断开连接(n层)的情况下,这会变得更加复杂。我们序列化和反序列化实体,因此不能有任何跟踪其更改的上下文。如果我们想将实体存储在数据库中,那么现在我们的任务就是让EF知道这些变化。基本上有两种方法可以做到这一点:
当涉及到关联时,我们总是必须绘制状态。我们必须从数据库中获取当前实体,并确定添加/删除了哪些子项。没有办法从反序列化的对象图本身推断出这一点。
有各种方法来缓解这种无聊和精心绘制国家的任务,但这超出了本Q&amp; A的范围。一些参考文献:
答案 1 :(得分:1)
我已经花费了数小时尝试不同的解决方案,以找到解决该问题的一些不错的方法。列表太长了,我无法在这里全部写出来,但是很少...
dbSet.Local
以避免跟踪错误什么都没有做,但是最后,这是只需很小的改动就能解决整个问题的方法。
使用此解决方案,您需要手动停止设置状态。只需调用一次
dbSet.Update()
方法,EF就会负责内部状态管理。注意::即使您正在使用实体的分离图,甚至使用具有嵌套父子关系的实体,此方法仍然有效。
代码前
public void Update(Parent obj)
{
_parent.Attach(obj);
_dbContext.Entry(obj).State = EntityState.Modified;
_dbContext.SaveChanges();
}
代码后:
public void Update(Parent obj)
{
dbSet.Update(obj);
_dbContext.SaveChanges();
}
答案 2 :(得分:0)
它让你很奇怪。
这需要延迟加载以获取孩子(显然会根据您的使用情况进行修改)
//得到父母
var parent = context.Parent.Where(x => x.Id == parentId).SingleOrDefault();
为您编写了一整套测试方法。 (适用于您的情况)
EmailMessage(父级)是父级,它没有或很多EmailAttachment(子级)
[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();
}
}
更新
按照你在评论中的说法做你的AutoMappper Stuff ......
然后当你准备保存并将它作为正确的类型返回时,即一次代表实体(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();
答案 3 :(得分:0)
如果您使用的是entityframework核心,它将提供dbSet.Update()方法,该方法可以处理任何级别的对象树中的任何更新。 作为参考,请检查文档链接here