C# 实体框架6-ObjectStateManager中已存在对具有相同键的对象的多个查找插入

C# 实体框架6-ObjectStateManager中已存在对具有相同键的对象的多个查找插入,c#,asp.net-mvc,entity-framework,asp.net-mvc-5,entity-framework-6,C#,Asp.net Mvc,Entity Framework,Asp.net Mvc 5,Entity Framework 6,显然,我在理解实体框架6时遇到了一个真正的麻烦,我正在使用ASP.NET MVC 5 问题的核心是,我有一个非常简单的数据模型,这是任何现实世界中的典型情况,其中我有各种业务对象,这些业务对象具有其他业务对象作为属性(当然,它们的子对象也可能具有其他子业务对象),以及各种类型的查找/类型数据(国家、州/省、语言类型、状态类型等),我不知道如何正确保存/更新它 我一直在两种错误状态之间来回切换: 1) 我可能会遇到这样的情况:保存父业务对象会导致将不需要的重复值插入到我的查找/类型表中(例如,保存

显然,我在理解实体框架6时遇到了一个真正的麻烦,我正在使用ASP.NET MVC 5

问题的核心是,我有一个非常简单的数据模型,这是任何现实世界中的典型情况,其中我有各种业务对象,这些业务对象具有其他业务对象作为属性(当然,它们的子对象也可能具有其他子业务对象),以及各种类型的查找/类型数据(国家、州/省、语言类型、状态类型等),我不知道如何正确保存/更新它

我一直在两种错误状态之间来回切换:

1) 我可能会遇到这样的情况:保存父业务对象会导致将不需要的重复值插入到我的查找/类型表中(例如,保存已分配“English”的现有LanguageType的业务对象将导致在LanguageType表中插入另一个“English”的LanguageType),或

2) 我使用我在这里和网上其他地方看到的一些建议(例如)来解决问题1,然后发现自己在与这个问题作斗争:

现在,我将提供一些代码片段,以帮助构建我正在尝试做什么以及我正在使用什么来做这件事的图片。首先,有关实体的一个例子:



    public class Customer : BaseEntity
    {
        public string Name { get; set; }
        [LocalizedDisplayName("Contacts")]

        public virtual List Contacts { get; set; }
    }

    public class Contact : BaseEntity
    {
        [Required]
        public string FirstName { get; set; }

        [Required]
        public string LastName { get; set; }

        public int? LanguageTypeID { get; set; }

        [Required]
        [ForeignKey("LanguageTypeID")]
        public virtual LanguageType Language { get; set; }
    }

    public class LanguageType : Lookup
    {
        [LocalizedDisplayName("CultureName")]
        public string CultureName { get; set; }
    }

    public class Lookup : BaseEntity
    {
        public string DisplayName { get; set; }

        public int DisplayOrder { get; set; }

        public string Name { get; set; }

        public string Description { get; set; }
    }

    public class BaseEntity
    {
        public int ID { get; set; }
        public DateTime? CreatedOn { get; set; }
        public DateTime? UpdatedOn { get; set; }
        public DateTime? DeletedOn { get; set; }
        public bool Deleted { get; set; }
        public bool Active { get; set; }
        public ApplicationUser CreatedByUser { get; set; }
        public ApplicationUser UpdatedByUser { get; set; }
    }

在我的控制器中,我有如下代码:



    foreach(Contact contact in lstContacts)
    {
        customer.Contacts.Add(contact);
    }
    if (ModelState.IsValid)
    {
        repository.Add(customer);
    }

假设每个联系人都分配了相同的语言类型“English”(在本例中,我试图保存多个具有相同语言类型的联系人,这会触发ObjectStateManager错误)。最初,repository.Add()代码只是执行了一个context.SaveChanges(),但它没有按预期工作,所以现在看起来像这样(实体变量是客户):

期望这一点起作用从根本上说是错误的吗?我应该如何像这样保存和保存示例?我在保存类似的对象图时也遇到类似的问题。当我看EF的教程和示例时,它们都很简单,在做了与我在这里所做的非常类似的事情之后,它们都只调用SaveChanges()

我最近刚刚使用了ColdFusion的ORM功能(它是隐藏的hibernate),有很多功能可以简单地加载LanguageType实体,将其分配给Contact实体,保存Contact实体,将其分配给Customer,然后保存Customer

在我看来,这是最基本的情况,我不敢相信它给我带来了这么多的痛苦——我讨厌这么说,但使用普通的旧ADO.NET(或者说老天保佑,我真的不喜欢的ColdFusion)会简单得多。所以我错过了一些东西。显然,我对EF的理解/方法有一个关键缺陷,如果有人能帮助我按预期完成这项工作,并帮助我找出我的误解所在,我将不胜感激。我在这方面花费了太多的时间,这是在浪费时间——我正在构建的代码中有/将有无数类似的例子,因此我现在需要调整我对EF的想法,以便我能够高效地以预期的方式处理事情


你的帮助意义重大,我感谢你

> P> >考虑下面的<强>对象图< /强>,其中教师实例是<强>根<强>对象,

教师--[有许多]-->课程

教师--[有一个]-->系

在实体框架的
DbContext
中,对象的每个实例都有一个
状态
,指示对象是
添加的
修改的
删除的
还是
未更改的
。显然发生了以下情况:

[TestMethod]
public void TestMethod1()
{
    //our context
    var ctx = new Infrastructure.EF.Context();
    
    //our language types
    var languageType1 = new LanguageType { ID = 1, Name = "French" };
    var languageType2 = new LanguageType { ID = 2, Name = "English" };

    ctx.LanguageTypes.AddRange(new LanguageType[] { languageType1, languageType2 });
    
    //persist our language types into db before we continue.
    ctx.SaveChanges();

    //now we're about to start a new unit of work

    var customer = new Customer
    {
        ID = 1,
        Name = "C1",
        Contacts = new List<Contact>() //To avoid null exception
    };

    //notice that we're assigning the id of the language type and not
    //an object.
    var Contacts = new List<Contact>(new Contact[] { 
         new Contact{ID=1, Customer = customer, LanguageTypeID=1},
         new Contact{ID=2, Customer = customer, LanguageTypeID=2}
        });

    customer.Contacts.AddRange(Contacts);
    
    //adding the customer here will mark the whole object graph as 'Added'
    ctx.Customers.Add(customer);

    //The customer & contacts are persisted, and in the DB, the language
    //types are not redundant.
    ctx.SaveChanges();
}
第一次创建根对象 在这种情况下,除了新创建的根对象
Teacher
,图形中的所有子对象也将添加
状态
,即使它们已经创建。此问题的解决方案是为每个子元素包含外键属性,并使用它,例如
Teacher.DepartmentId=3

更新根对象及其一个子元素的属性 假设您从数据库中获取教师对象,并更改
teacher.Name
属性以及
teacher.Department.Name
属性;在这种情况下,教师根对象的
状态
标记为
已修改
,另一方面,部门的
状态
保持
不变
,修改不会持久保存到数据库中无声地而没有任何警告


编辑1 我按如下方式使用了您的类,并且在持久化对象方面没有问题:

public class Customer : BaseEntity
{
    public string Name { get; set; }
    public virtual List<Contact> Contacts { get; set; }
}

public class Contact : BaseEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int? LanguageTypeID { get; set; }
    public Customer Customer { get; set; }
    [ForeignKey("LanguageTypeID")]
    public LanguageType Language { get; set; }
}

public class LanguageType : Lookup
{
    public string CultureName { get; set; }
}

public class Lookup : BaseEntity
{
    public string DisplayName { get; set; }
    public int DisplayOrder { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

public class BaseEntity
{
    public int ID { get; set; }
    public DateTime? CreatedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public DateTime? DeletedOn { get; set; }
    public bool Deleted { get; set; }
    public bool Active { get; set; }
    public ApplicationUser CreatedByUser { get; set; }
    public ApplicationUser UpdatedByUser { get; set; }
}

public class ApplicationUser
{
    public int ID { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
}
公共类客户:BaseEntity
{
公共字符串名称{get;set;}
公共虚拟列表联系人{get;set;}
}
公共类联系人:BaseEntity
{
公共字符串名{get;set;}
公共字符串LastName{get;set;}
public int?LanguageTypeID{get;set;}
公共客户客户{get;set;}
[外键(“LanguageTypeID”)]
公共语言类型语言{get;set;}
}
公共类语言类型:查找
{
公共字符串CultureName{get;set;}
}
公共类查找:BaseEntity
{
公共字符串DisplayName{get;set;}
公共int显示顺序{get;set;}
公共字符串名称{get;set;}
公共字符串说明{get;set;}
}
公共类基实体
{
公共int ID{get;set;}
公共日期时间?CreatedOn{get;set;}
public DateTime?UpdatedOn{get;set;}
公共日期时间?DeletedOn{get;set;}
公共bool已删除{get;set;}
公共bool活动{get;set;}
公共应用程序用户CreatedByUser{get;set;}
public class Context : DbContext
{
    public Context() : base("name=CS") { }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<Contact> Contacts { get; set; }
    public DbSet<LanguageType> LanguageTypes { get; set; }
    public DbSet<ApplicationUser> ApplicationUsers { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //I'm generating the database using those entities you defined;
        //Here we're demanding not add 's' to the end of table names
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}
[TestMethod]
public void TestMethod1()
{
    //our context
    var ctx = new Infrastructure.EF.Context();
    
    //our language types
    var languageType1 = new LanguageType { ID = 1, Name = "French" };
    var languageType2 = new LanguageType { ID = 2, Name = "English" };

    ctx.LanguageTypes.AddRange(new LanguageType[] { languageType1, languageType2 });
    
    //persist our language types into db before we continue.
    ctx.SaveChanges();

    //now we're about to start a new unit of work

    var customer = new Customer
    {
        ID = 1,
        Name = "C1",
        Contacts = new List<Contact>() //To avoid null exception
    };

    //notice that we're assigning the id of the language type and not
    //an object.
    var Contacts = new List<Contact>(new Contact[] { 
         new Contact{ID=1, Customer = customer, LanguageTypeID=1},
         new Contact{ID=2, Customer = customer, LanguageTypeID=2}
        });

    customer.Contacts.AddRange(Contacts);
    
    //adding the customer here will mark the whole object graph as 'Added'
    ctx.Customers.Add(customer);

    //The customer & contacts are persisted, and in the DB, the language
    //types are not redundant.
    ctx.SaveChanges();
}