C# 保存包含列表的对象时,会给出;引用完整性约束冲突“;

C# 保存包含列表的对象时,会给出;引用完整性约束冲突“;,c#,asp.net-mvc,entity-framework,one-to-many,C#,Asp.net Mvc,Entity Framework,One To Many,我试图理解如何使用MVC和实体框架编辑一对多关系,我遇到了一些问题,我试图编辑同一视图中的一个(Person)和多个(Color)(一旦完成,我将转到多对多) 我看过很多其他的帖子,没有看到一个直接解决我所经历的问题的方法 BaseObject类: public class BaseObject { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Key] [Column(Order = 1)] pub

我试图理解如何使用MVC和实体框架编辑一对多关系,我遇到了一些问题,我试图编辑同一视图中的一个(
Person
)和多个(
Color
)(一旦完成,我将转到多对多)

我看过很多其他的帖子,没有看到一个直接解决我所经历的问题的方法

BaseObject
类:

public class BaseObject
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    [Column(Order = 1)]
    public Guid Oid { get; set; }
}
public class Person : BaseObject
{
    public string Name { get; set; }
    public virtual List<Color> Colors { get; set; }   
}
public class Color : BaseObject
{
    public string Name { get; set; }

    [DisplayName("Person")]
    public Guid? PersonID { get; set; }

    [ForeignKey("PersonID")]
    public virtual Person Person { get; set; }

}
Person
Class:

public class BaseObject
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    [Column(Order = 1)]
    public Guid Oid { get; set; }
}
public class Person : BaseObject
{
    public string Name { get; set; }
    public virtual List<Color> Colors { get; set; }   
}
public class Color : BaseObject
{
    public string Name { get; set; }

    [DisplayName("Person")]
    public Guid? PersonID { get; set; }

    [ForeignKey("PersonID")]
    public virtual Person Person { get; set; }

}
在此之后,我创建了我的Scafolding(和视图),在此基础上,我修改了
Person/Edit.cshtml
,以
@for(var I=0;I模型绑定器
方法:

当我运行应用程序时,打开一个现有的人,我会看到这个人的名字和颜色列表(蓝色和绿色)。修改与否,当我点击“保存”时,
db.Entry(person.State=EntityState.Modified返回以下错误:

System.InvalidOperationException:'发生引用完整性约束冲突:关系一端的'Person.Oid'属性值与另一端的'Color.PersonID'属性值不匹配。'

Person
对象处检查,它确实有2种颜色(使用模型绑定器获得),进入
Colors
集合我看到颜色的
名称已更改,
Person
对象已填充,但
PersonID
未填充

person.Colors[0].Name       "Blue updated"                  string
person.Colors[0].PersonID   null                            System.Guid?
+person.Colors[0].Person    {x.Models.One_to_Many.Person}   x.Models.One_to_Many.Person
我相信
PersonID
是我的问题,它需要用当前的
Person
填充

我是否需要绑定或包含在编辑获取中

public ActionResult Edit(Guid? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Person person = db.One.Find(id);
    if (person == null)
    {
        return HttpNotFound();
    }
    return View(person);
}
我不确定最好的方法是使用此设置或将其设置为“保存”

编辑 这是基于@gert arnold关于使用ViewModel的建议,我的计划是在我确定一对多和未来的多对多之后再使用ViewModel

保存模型的效果不是很好,如果你看到下面,我将能够使代码适应PeopleController,但将其放入ViewModel和Controller中肯定是有意义的

我现在创建了一个
PersonViewModel

public class PersonView
{
    public string Name { get; set; }
    public virtual List<Color> Colors { get; set; }
}
最后,
Edit.cshtml
与上面的非常相似(如果不是相同的话)。我相信我正在得到我想要的结果,我可以扩展以允许新的
Color
s与Ajax内联添加,等等


我知道我需要检查并捕捉错误,但我觉得这足以作为概念的证明。简言之,是否有人对该代码和方法有任何建议,我现在希望对其进行改进,并将其应用于我的实际项目。

考虑删除颜色实体中的PersonId列。设置映射以将“Person”键解析为PersonId列,但避免在子实体中同时公开父引用及其FK属性。这会导致各种问题,其中父引用和FK可能会不同步。根据映射的解析方式,这两个属性都已就位,您需要对所有引用属性执行如下操作:

private Person _person = null;
public virtual Person Person 
{
  get {return _person;}
  set 
  {
    _person = value;
    if (value != null)
      PersonID = value.OId;
    else
      PersonID = null;
  }
}
免责声明:我个人不使用代码优先映射。我选择先在EntityTypeConfiguration声明中使用DB,因为我使用的是有界上下文,并且更喜欢在没有ORM“修补”的情况下优化模式。通过显式配置,我可以使用.Map解析FK,而无需将其作为实体中的属性公开。我不知道首先使用代码是否/如何实现这一点


作为一般规则,我避免完全添加父实体引用,除非我知道需要反向解析。例如:如果我总是从Person转到list Color,我的Color对象中甚至不会有Person引用(或FK属性)。然而,对于像客户和订单这样的情况,我认为在我的订单上有一个客户参考是合理的,因为我希望搜索订单,并希望看到它们与什么客户关联

开始使用视图模型是一个明智的选择。这样做时,有多种方法可以“绘制状态”返回到EF实体。看看
,其中一个是:

person = db.People.Find(id);
person.Name = personViewModel.Name;
db.SaveChanges();
通过
Find
语句,EF开始跟踪
person
实体。EF注意到名称的修改,并将进行更新。将状态设置为
Modified
是不必要的,甚至不是最理想的,因为它会导致EF更新所有
person
的属性

绘制状态的另一种方法是:

person = new Person { Oid = personViewModel.Oid, Name = personViewModel.Name };
db.Persons.Attach(person);
db.Entry(person).Property(p => p.Name).IsModified = true;
db.SaveChanges();
这将删除数据库往返,这可能会提高性能,但需要更多的代码,特别是当涉及嵌套对象时。另一个主要缺点是,
person
,即所谓的存根实体,现在很可能无法通过验证,因为所需的属性可能是
null
。这可以通过禁用save时的验证来避免(
db.Configuration.ValidateOnSaveEnabled=false
),但是您需要EF提供的验证以外的其他机制


有关如何在这些选项之间进行选择的详细信息。

只要不需要指定嵌套对象绑定,
Bind
属性就可以了。除非绝对必要,否则我不会用它。使用视图模型/DTO控制对模型属性的访问。另一件事是,
db.Entry(person).State=EntityState.Modified
不会将嵌套对象标记为
Modified
,只标记为基本属性。因此,我的建议是:在MVC的VC部分使用PersonDto/ColorDto,而不使用
[Bind]
,并将它们映射回控制器或(最好)服务中的真实内容。谢谢@gert arnold,我已经用ViewModel更新了我的初始问题,效果很好。