[英]Entity Framework: An object with the same key already exists in the 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
指示对象是否被Added
, Modified
, Removed
或Unchanged
。 显然发生了以下情况:
在这种情况下,除了新创建的根对象Teacher
, 都在图中的子对象将有State
Added
以及即使他们已经建立。 解决此问题的方法是为每个子元素包括外键属性 ,并改为使用它,例如Teacher.DepartmentId = 3
。
假设您从数据库中获取了一个教师对象,并且更改了Teacher.Name
属性以及Teacher.Department.Name
属性; 在这种情况下, 只有教师根对象的State
标记为Modified
,而部门的State
则保持Unchanged
,并且修改不会持久化到DB中; 默默地没有任何警告。
我按如下方式使用了您的类,并且持久化对象没有问题:
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();
}
一切顺利,没有任何问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.