C# 告诉DbContext不要添加整个对象图?
我的模型中有一个类可以引用来自两个不同FK关联/字段的同一个子类。创建父对象时,这两个引用都使用子对象的同一实例填充,然后,可以更新或更改两个子对象中的一个(这并不总是发生),并保留原始引用,因为从未接触过另一个子对象。我希望这是有道理的 当从具有两次引用的相同子对象的数据库中创建或提取父对象时,当您尝试将父对象添加到DbContext时,我们会看到可怕的错误:C# 告诉DbContext不要添加整个对象图?,c#,entity-framework-4.1,ef-code-first,C#,Entity Framework 4.1,Ef Code First,我的模型中有一个类可以引用来自两个不同FK关联/字段的同一个子类。创建父对象时,这两个引用都使用子对象的同一实例填充,然后,可以更新或更改两个子对象中的一个(这并不总是发生),并保留原始引用,因为从未接触过另一个子对象。我希望这是有道理的 当从具有两次引用的相同子对象的数据库中创建或提取父对象时,当您尝试将父对象添加到DbContext时,我们会看到可怕的错误:ObjectStateManager中已存在具有相同键的对象。ObjectStateManager无法跟踪具有相同键的多个对象。引发此问
ObjectStateManager中已存在具有相同键的对象。ObjectStateManager无法跟踪具有相同键的多个对象。
引发此问题是因为DbContext正在尝试将整个对象图添加到其更改跟踪器,即指向同一子对象的两个子引用
我们不需要变更跟踪。我们不介意在数据库中抛出一个完全填充的UPDATE语句。有没有办法强迫DbContext不添加整个对象图,只添加我们告诉它要添加的单个实例?如果是这样的话,如果我们在全球范围内禁用它,我们将失去哪些功能
编辑:更新的代码示例
编辑2:更新了代码示例,以包含模拟web服务交互的序列化
[TestClass]
public class EntityFrameworkTests
{
[TestMethod]
public void ObjectGraphTest()
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
string connectionString = String.Format("Data Source={0}\\EntityFrameworkTests.sdf", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
MyDbContext context = new MyDbContext(connectionString);
Child child = new Child() { ID = 1, SomeProperty = "test value" };
//context.Entry<Child>(child).State = EntityState.Added;
Parent parent = new Parent()
{
ID = 1,
SomeProperty = "some value",
OriginalChild = child,
ChangeableChild = child
};
context.Entry<Parent>(parent).State = EntityState.Added;
context.SaveChanges();
context = new MyDbContext(connectionString);
//parent = context.Set<Parent>().AsNoTracking().Include(p => p.OriginalChild).Include(p => p.ChangeableChild).FirstOrDefault();
parent = context.Set<Parent>().Include(p => p.OriginalChild).Include(p => p.ChangeableChild).FirstOrDefault();
// mimic receiving object via a web service
SaveToStorage(parent);
parent = GetSavedItem(1);
parent.SomeProperty = "some new value";
context = new MyDbContext(connectionString);
context.Entry<Parent>(parent).State = EntityState.Modified; // error here
context.SaveChanges();
}
}
使用的类(为序列化而更新):
[DataContract]
内部类儿童
{
[数据成员]
公共int ID{get;set;}
[数据成员]
公共字符串SomeProperty{get;set;}
}
[数据合同]
内部类父级
{
[数据成员]
公共int ID{get;set;}
[数据成员]
公共字符串SomeProperty{get;set;}
[数据成员]
public int OriginalChildID{get;set;}
[数据成员]
公共子级原始子级{get;set;}
[数据成员]
public int ChangeableChildID{get;set;}
[数据成员]
公共子级可更改子级{get;set;}
}
内部类MyDbContext:DbContext
{
公共数据库集父项{get;set;}
公共DbSet子项{get;set;}
公共MyDbContext(字符串连接字符串)
:base(connectionString){}
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove();
}
}
丑陋的解决方案:
Child originalChild = parent.OriginalChild;
Child changeableChild = parent.ChangeableChild;
parent.OriginalChild = null;
parent.ChangeableChild = null;
context.Entry<Parent>(parent).State = EntityState.Modified;
context.SaveChanges();
parent.OriginalChild = originalChild;
parent.ChangeableChild = changeableChild;
您必须首先从数据库加载父级(使用活动更改跟踪!),但另一方面,此处发出的更新命令将仅包含更改的属性。既然你说你不介意发送一个完整的更新命令(通过将状态设置为
Modified
),我想你没有性能问题。因此,加载原始文件,然后仅发送带有更改属性的小更新,可能不会比发送完整更新命令更糟糕或更糟糕。是否尝试设置标量属性OriginalChildID=myChild.ID
?显示的代码不会引发异常。在上下文中有两个指向同一对象的引用是合法的(myChild
)。如果上下文中有具有相同键的不同对象,则会出现问题。但在示例代码中并非如此。您的示例中一定缺少一些重要的内容。这似乎是最简单的示例。我应该花点时间编写一个可重复的单元测试,只包含简单的类。真正的情况发生在我们的大型系统中,当父对象从数据库中取出时,包含两个子对象。更新父项上的单个字段,然后将父项添加回DbContext以保存到数据库中。将父对象添加回DbContext时,即发生错误时。我将编写一个可重复的单元测试并更新我的帖子。谢谢。更新了显示错误的新代码。我认为您必须在GetSavedItem
之后创建一个新的上下文,以显示您遇到的实际问题。您的示例现在当然会抛出一个异常,因为旧的父对象仍然在上下文中(父对象现在是重复的,所有子对象也是重复的)。但这并不是你所说的例外。我可能不得不重新设计我的存储库的核心,以使用你在这里概述的第二种方法。无论如何,我已经在脑子里反复思考了几个星期,如果它解决了这些问题……更改这行代码context.Entry(parent.State=EntityState.Modified;//此处的错误
指向此父源Parent=context.Set().FirstOrDefault()代码>context.Entry(originalParent).CurrentValues.SetValues(parent)代码>修复问题中代码示例的问题。所以看起来我将重新设计我的存储库设计。谢谢
[DataContract]
internal class Child
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string SomeProperty { get; set; }
}
[DataContract]
internal class Parent
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string SomeProperty { get; set; }
[DataMember]
public int OriginalChildID { get; set; }
[DataMember]
public Child OriginalChild { get; set; }
[DataMember]
public int ChangeableChildID { get; set; }
[DataMember]
public Child ChangeableChild { get; set; }
}
internal class MyDbContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Children { get; set; }
public MyDbContext(string connectionString)
: base(connectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
}
Child originalChild = parent.OriginalChild;
Child changeableChild = parent.ChangeableChild;
parent.OriginalChild = null;
parent.ChangeableChild = null;
context.Entry<Parent>(parent).State = EntityState.Modified;
context.SaveChanges();
parent.OriginalChild = originalChild;
parent.ChangeableChild = changeableChild;
var originalParent = context.Set<Parent>()
.Where(p => p.ID == parent.ID)
.FirstOrDefault();
context.Entry(originalParent).CurrentValues.SetValues(parent);
context.SaveChanges();