繁体   English   中英

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

[英]Entity Framework 6 - Multiple lookup inserts vs An object with the same key already exists in the ObjectStateManager

我显然很费时地了解我与ASP.NET MVC 5一起使用的Entity Framework 6。

问题的核心是,我有一个非常简单的数据模型,该模型在任何实际情况下都是很典型的,在这种情况下,我有各种具有其他业务对象作为属性的业务对象(当然,它们的子对象可能又具有其他子业务)对象)以及各种类型的查询/类型数据(国家/地区,州/省,LanguageType,StatusType等),我无法弄清楚如何正确保存/更新它。

我一直在两个错误状态之间来回移动:

1)我遇到一种情况,即保存父业务对象会导致将不必要的重复值插入到我的查找/类型表中(例如,保存已被分配了现有LanguageType为“ English”的业务对象将导致另一个 LanguageType)用于将“英语”插入LanguageType表中),或

2)我使用一些在网上和网上其他地方看到的建议(例如,“ 保存实体”会导致将重复的内容插入到查找数据中 ,“ 防止实体框架为导航属性插入值” )解决了问题1,然后发现自己在与之抗争相同的问题: ObjectStateManager中已经存在具有相同键的对象。 ObjectStateManager无法使用相同的键跟踪多个对象

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



    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);
    }

让我们假设每个联系人都分配了与“英语”相同的LanguageType(在此示例中,正是我试图保存具有相同LanguageType的多个联系人会触发ObjectStateManager错误的事实)。 最初,repository.Add()代码只是执行了一个context.SaveChanges(),它无法按预期工作,因此现在看起来像这样(实体变量是Customer):



    try
    {
        if(Entity.Contacts != null)
        {
            foreach(Contact contact in Entity.Contacts)
            {
                var entry = this.context.Entry(contact.Language);
                var key = contact.Language.ID;

                if (entry.State == EntityState.Detached)
                {
                    var currentEntry = this.context.LanguageTypes.Local.SingleOrDefault(l => l.ID == key);
                    if (currentEntry != null)
                    {
                        var attachedEntry = this.context.Entry(currentEntry);
                        //attachedEntry.CurrentValues.SetValues(entityToUpdate);
                        attachedEntry.State = EntityState.Unchanged;
                    }
                    else
                    {
                        this.context.LanguageTypes.Attach(contact.Language);
                        entry.State = EntityState.Unchanged;
                    }
                }
            }
        }
        context.Customers.Add(Entity);
        context.SaveChanges();
    }
    catch(Exception ex)
    {
        throw;
    }

期望这种方法行得通根本上是错误的吗? 我应该如何保存和像这样的示例? 保存类似的对象图时,我遇到类似的问题。 当我看一下EF的教程和示例时,它们都很简单,并且都在做与我在这里所做的非常相似的事情之后都调用SaveChanges()。

我最近一直在使用ColdFusion的ORM功能(隐藏在后台),可以简单地加载LanguageType实体,将其分配给Contact实体,保存Contact实体,将其分配给Customer,然后保存客户。

在我看来,这是最基本的情况,我不敢相信它给我造成了那么多痛苦-我讨厌这么说,但是使用普通的旧ADO.NET(或者天堂禁止的ColdFusion,我真的不喜欢)会更简单。 所以我很想念。 我对EF的理解/方法显然存在一个关键缺陷,如果有人可以帮助我按预期进行这项工作,并帮助我弄清误解的根源,我将不胜感激。 我花了太多时间在这个上面,这很浪费时间-我已经/将有无数的示例,就像我正在构建的代码中的这个示例一样,因此我现在需要针对EF调整自己的想法,所以我可以提高工作效率,并以预期的方式来做事。

您的帮助意义非凡,我感谢您!

让我们考虑下面的对象图 ,其中教师实例是对象,

老师-[有很多]->课程

老师-[有一个]->系

在实体框架的DbContext ,每一个对象实例都有一个State指示对象是否被AddedModifiedRemovedUnchanged 显然发生了以下情况:

首次创建根对象

在这种情况下,除了新创建的根对象Teacher在图中的子对象将有State Added以及即使他们已经建立。 解决此问题的方法是为每个子元素包括外键属性 ,并改为使用它,例如Teacher.DepartmentId = 3

更新根对象及其子元素的属性之一

假设您从数据库中获取了一个教师对象,并且更改了Teacher.Name属性以及Teacher.Department.Name属性; 在这种情况下, 只有教师根对象的State标记为Modified ,而部门的State则保持Unchanged ,并且修改不会持久化到DB中; 默默地没有任何警告。


编辑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; }
}

并使用了以下上下文:

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();
}

一切顺利,没有任何问题。

据我所知,没有支持重新连接修改后的图形的构建(例如nHibernate的SaveOrUpdate方法)。 也许还是可以帮助你。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM