我得到这个错误时,我GetById()在一个实体,然后设置子实体的集合到我的新列表,来自MVC视图。
操作失败
关系是无法改变的
因为一个或多个外键
Properties是非空的。当一个
关系发生了变化
相关外键属性设置为
空值。如果外键是
不支持空值,新建
关系必须被定义
必须分配外键属性
另一个非空值或
必须删除不相关的对象。
我不太理解这句话:
这种关系无法改变
因为一个或多个外键
Properties是非空的。
我为什么要改变两个实体之间的关系?它应该在整个应用程序的生命周期内保持不变。
发生异常的代码只是简单地将集合中修改过的子类分配给现有的父类。这将有望满足取消子类,增加新的和修改。我本以为实体框架处理这个。
代码行可以提炼为:
var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();
我不知道为什么其他两个答案这么受欢迎!
我相信您认为ORM框架应该处理它是正确的——毕竟,这是它承诺交付的。否则,您的域模型就会被持久性问题所破坏。如果你正确地设置了级联设置,NHibernate就能很好地管理它。在实体框架中也有可能,他们只是希望你在建立数据库模型时遵循更好的标准,特别是当他们不得不推断应该做什么级联时:
您必须使用“识别关系”来正确地定义父-子关系。
如果你这样做,实体框架知道子对象是由父对象标识的,因此它必须是一个“级联删除孤儿”的情况。
除了上面的,你可能需要(从NHibernate的经验)
thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);
而不是完全替换列表。
更新
@Slauma的评论提醒我,分离实体是整体问题的另一部分。为了解决这个问题,您可以采用使用自定义模型绑定器的方法,通过尝试从上下文加载模型来构造模型。这篇博客文章展示了我的意思。
您之所以会遇到这种情况,是因为组合和聚合之间存在差异。
在复合中,创建父对象时创建子对象,销毁父对象时销毁子对象。所以它的生命周期是由父节点控制的。例:一篇博客文章及其评论。如果一个帖子被删除,它的评论也应该被删除。对一篇不存在的文章发表评论是没有意义的。订单和订单项目也是如此。
在聚合中,子对象可以不考虑父对象而存在。如果父对象被销毁,子对象仍然可以存在,因为以后它可能被添加到不同的父对象。例如:播放列表和该播放列表中的歌曲之间的关系。如果播放列表被删除,歌曲不应该被删除。它们可能被添加到不同的播放列表中。
实体框架区分聚合和组合关系的方式如下:
对于复合:它期望子对象有一个复合主键(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();
出现这个问题是因为我们试图删除父表,但仍然存在子表数据。
我们利用级联删除来解决这个问题。
在模型中创建dbcontext类中的方法。
modelBuilder.Entity<Job>()
.HasMany<JobSportsMapping>(C => C.JobSportsMappings)
.WithRequired(C => C.Job)
.HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
modelBuilder.Entity<Sport>()
.HasMany<JobSportsMapping>(C => C.JobSportsMappings)
.WithRequired(C => C.Sport)
.HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);
之后,在我们的API调用中
var JobList = Context.Job
.Include(x => x.JobSportsMappings) .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();
级联删除选项删除父表以及与父相关的子表,使用这个简单的代码。用这个简单的方法试试。
删除范围用于删除数据库中的记录列表
谢谢
我也解决了我的问题与Mosh的答案,我认为PeterB的答案是有点,因为它使用枚举作为外键。请记住,在添加这段代码之后,您将需要添加一个新的迁移。
我也可以推荐这篇博客的其他解决方案:
http://www.kianryan.co.uk/2013/03/orphaned-child/
代码:
public class Child
{
[Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Heading { get; set; }
//Add other properties here.
[Key, Column(Order = 1)]
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
这是一个很大的问题。在你的代码中实际发生的是这样的:
从数据库加载Parent并获得附加实体
您用分离的子集合替换它的子集合
您保存更改,但在此操作期间,所有的子元素都被认为是添加的,因为EF直到此时才知道它们。因此EF尝试将null设置为旧子节点的外键,并插入所有新的子节点=>重复行。
解决方法取决于你想做什么以及你想怎么做?
如果您正在使用ASP。NET MVC你可以尝试使用UpdateModel或TryUpdateModel。
如果你只是想手动更新现有的子程序,你可以简单地这样做:
foreach (var child in modifiedParent.ChildItems)
{
context.Childs.Attach(child);
context.Entry(child).State = EntityState.Modified;
}
context.SaveChanges();
附加实际上是不需要的(将状态设置为Modified也将附加实体),但我喜欢它,因为它使过程更加明显。
如果你想修改现有的,删除现有的和插入新的子元素,你必须这样做:
var parent = context.Parents.GetById(1); // Make sure that childs are loaded as well
foreach(var child in modifiedParent.ChildItems)
{
var attachedChild = FindChild(parent, child.Id);
if (attachedChild != null)
{
// Existing child - apply new values
context.Entry(attachedChild).CurrentValues.SetValues(child);
}
else
{
// New child
// Don't insert original object. It will attach whole detached graph
parent.ChildItems.Add(child.Clone());
}
}
// Now you must delete all entities present in parent.ChildItems but missing
// in modifiedParent.ChildItems
// ToList should make copy of the collection because we can't modify collection
// iterated by foreach
foreach(var child in parent.ChildItems.ToList())
{
var detachedChild = FindChild(modifiedParent, child.Id);
if (detachedChild == null)
{
parent.ChildItems.Remove(child);
context.Childs.Remove(child);
}
}
context.SaveChanges();