告诉DbContext不添加整个对象图?

时间:2011-06-08 21:06:34

标签: c# entity-framework-4.1 ef-code-first

我的模型中有一个类可以从两个不同的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>();
  }
}

1 个答案:

答案 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命令更差或更差。