我得到这个错误时,我GetById()在一个实体,然后设置子实体的集合到我的新列表,来自MVC视图。
操作失败
关系是无法改变的
因为一个或多个外键
Properties是非空的。当一个
关系发生了变化
相关外键属性设置为
空值。如果外键是
不支持空值,新建
关系必须被定义
必须分配外键属性
另一个非空值或
必须删除不相关的对象。
我不太理解这句话:
这种关系无法改变
因为一个或多个外键
Properties是非空的。
我为什么要改变两个实体之间的关系?它应该在整个应用程序的生命周期内保持不变。
发生异常的代码只是简单地将集合中修改过的子类分配给现有的父类。这将有望满足取消子类,增加新的和修改。我本以为实体框架处理这个。
代码行可以提炼为:
var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();
我在几个小时前遇到过这个问题,并尝试了所有方法,但在我的情况下,解决方案与上面列出的不同。
如果你使用已经从数据库检索实体,并试图修改它的孩子的错误将发生,但如果你从数据库获得实体的新副本,不应该有任何问题。
不要用这个:
public void CheckUsersCount(CompanyProduct companyProduct)
{
companyProduct.Name = "Test";
}
用这个:
public void CheckUsersCount(Guid companyProductId)
{
CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
companyProduct.Name = "Test";
}
我使用了Mosh的解决方案,但我不清楚如何在代码中正确地实现组合键。
这就是解决方案:
public class Holiday
{
[Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int HolidayId { get; set; }
[Key, Column(Order = 1), ForeignKey("Location")]
public LocationEnum LocationId { get; set; }
public virtual Location Location { get; set; }
public DateTime Date { get; set; }
public string Name { get; set; }
}
You should delete old child items thisParent.ChildItems one by one manually. Entity Framework doesn't do that for you. It finally cannot decide what you want to do with the old child items - if you want to throw them away or if you want to keep and assign them to other parent entities. You must tell Entity Framework your decision. But one of these two decisions you HAVE to make since the child entities cannot live alone without a reference to any parent in the database (due to the foreign key constraint). That's basically what the exception says.
Edit
如果子项目可以添加,更新和删除,我会做什么:
public void UpdateEntity(ParentItem parent)
{
// Load original parent including the child item collection
var originalParent = _dbContext.ParentItems
.Where(p => p.ID == parent.ID)
.Include(p => p.ChildItems)
.SingleOrDefault();
// We assume that the parent is still in the DB and don't check for null
// Update scalar properties of parent,
// can be omitted if we don't expect changes of the scalar properties
var parentEntry = _dbContext.Entry(originalParent);
parentEntry.CurrentValues.SetValues(parent);
foreach (var childItem in parent.ChildItems)
{
var originalChildItem = originalParent.ChildItems
.Where(c => c.ID == childItem.ID && c.ID != 0)
.SingleOrDefault();
// Is original child item with same ID in DB?
if (originalChildItem != null)
{
// Yes -> Update scalar properties of child item
var childEntry = _dbContext.Entry(originalChildItem);
childEntry.CurrentValues.SetValues(childItem);
}
else
{
// No -> It's a new child item -> Insert
childItem.ID = 0;
originalParent.ChildItems.Add(childItem);
}
}
// Don't consider the child items we have just added above.
// (We need to make a copy of the list by using .ToList() because
// _dbContext.ChildItems.Remove in this loop does not only delete
// from the context but also from the child collection. Without making
// the copy we would modify the collection we are just interating
// through - which is forbidden and would lead to an exception.)
foreach (var originalChildItem in
originalParent.ChildItems.Where(c => c.ID != 0).ToList())
{
// Are there child items in the DB which are NOT in the
// new child item collection anymore?
if (!parent.ChildItems.Any(c => c.ID == originalChildItem.ID))
// Yes -> It's a deleted child item -> Delete
_dbContext.ChildItems.Remove(originalChildItem);
}
_dbContext.SaveChanges();
}
注意:这不是测试。它假设子项集合的类型是ICollection。(我通常有IList,然后代码看起来有点不同。)为了保持简单,我还去掉了所有存储库抽象。
我不知道这是否是一个好的解决方案,但我相信必须按照这些思路做一些艰苦的工作,以处理导航集合中的各种更改。我也很乐意看到一种更简单的方法。
您之所以会遇到这种情况,是因为组合和聚合之间存在差异。
在复合中,创建父对象时创建子对象,销毁父对象时销毁子对象。所以它的生命周期是由父节点控制的。例:一篇博客文章及其评论。如果一个帖子被删除,它的评论也应该被删除。对一篇不存在的文章发表评论是没有意义的。订单和订单项目也是如此。
在聚合中,子对象可以不考虑父对象而存在。如果父对象被销毁,子对象仍然可以存在,因为以后它可能被添加到不同的父对象。例如:播放列表和该播放列表中的歌曲之间的关系。如果播放列表被删除,歌曲不应该被删除。它们可能被添加到不同的播放列表中。
实体框架区分聚合和组合关系的方式如下:
对于复合:它期望子对象有一个复合主键(ParentID, ChildID)。这是通过设计来实现的,因为孩子的id应该在他们父母的范围内。
对于聚合:它期望子对象中的外键属性为空。
So, the reason you're having this issue is because of how you've set your primary key in your child table. It should be composite, but it's not. So, Entity Framework sees this association as aggregation, which means, when you remove or clear the child objects, it's not going to delete the child records. It'll simply remove the association and sets the corresponding foreign key column to NULL (so those child records can later be associated with a different parent). Since your column does not allow NULL, you get the exception you mentioned.
解决方案:
1-如果你有强烈的理由不想使用复合键,你需要显式地删除子对象。这可以比之前建议的解决方案更简单:
context.Children.RemoveRange(parent.Children);
2-否则,通过在你的子表上设置正确的主键,你的代码看起来会更有意义:
parent.Children.Clear();