我的模型中有一个类可以从两个不同的FK关联/字段引用相同的子类。在创建父对象时,这两个引用都填充了子对象的相同实例,然后,可以更新或更改这两个子对象中的一个(这不会 总是发生),并且保留原件是因为其他孩子从未接触过。我希望这是有道理的。
当从具有相同Child的数据库创建或提取Parent两次时,当您尝试将Parent添加到DbContext时,我们看到了可怕的错误:An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
这是因为DbContext尝试将整个对象图添加到其更改跟踪器,即两个Child引用指向同一个Child对象。
我们不需要更改跟踪。我们不介意在数据库中抛出完全填充的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();
}
}
模仿Web服务交互的序列化方法:
private void SaveToStorage(Parent parent)
{
string savedFilePath = String.Format("{0}\\Parent{1}.xml", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), parent.ID);
using (FileStream fileStream = new FileStream(savedFilePath, FileMode.Create, FileAccess.Write))
{
using (XmlWriter writer = XmlWriter.Create(fileStream))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(Parent));
serializer.WriteObject(writer, parent);
}
}
}
private Parent GetSavedItem(int parentID)
{
string savedFilePath = String.Format("{0}\\Parent{1}.xml", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), parentID);
using (FileStream fileStream = new FileStream(savedFilePath, FileMode.Open, FileAccess.Read))
{
using (XmlDictionaryReader xmlReader = XmlDictionaryReader.CreateTextReader(fileStream, new XmlDictionaryReaderQuotas()))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(Parent));
Parent savedItem = (Parent)serializer.ReadObject(xmlReader, true);
return savedItem;
}
}
}
使用的类(为序列化更新):
[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>();
}
}
答案 0 :(得分:3)
丑陋的解决方案:
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;
保存将子项设置为parent
后,您不再需要null
子对象了。
另一个更好的解决方案:再次从数据库中拉出原始父级 - 没有子级,因为您知道您只想保存更改的父级属性:
var originalParent = context.Set<Parent>()
.Where(p => p.ID == parent.ID)
.FirstOrDefault();
context.Entry(originalParent).CurrentValues.SetValues(parent);
context.SaveChanges();
您必须先从数据库加载父级(使用主动更改跟踪!),但另一方面,此处发出的UPDATE命令仅包含已更改的属性。既然你说你不介意发送完整的UPDATE命令(通过将状态设置为Modified
),我猜你没有性能问题。因此,加载原始文件然后发送只有更改后的属性的小UPDATE可能不会比发送完整的UPDATE命令更差或更差。