繁体   English   中英

告诉 DbContext 不要添加整个 object 图?

[英]Tell DbContext to not add whole object graph?

我的 model 中有一个 class 可以引用来自两个不同 FK 关联/字段的同一个子 class。 在创建父项时,这两个引用都填充了子 object 的相同实例,然后可以更新或更改两个子中的一个(这并不总是发生),并且保留原始值,因为另一个的孩子们从未被触及。 我希望这是有道理的。

当从数据库中创建或提取具有相同子项两次引用的父项时,当您尝试将父项添加到 DbContext 时,我们会看到可怕的错误: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. 这是因为 DbContext 试图将整个 object 图添加到其更改跟踪器中,即指向同一个子 object 的两个子引用。

我们不需要更改跟踪。 我们不介意在数据库中抛出一个完全填充的 UPDATE 语句。 有没有办法强制 DbContext 不添加整个 object 图,只添加我们告诉它添加的单个实例? 如果是这样,如果我们全局禁用它,我们将失去什么功能?

编辑:更新的代码示例。

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

丑陋的解决方案:

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;

在将子对象设置为null之后,我不再需要具有子对象的parent对象当然就足够了。

另一个更好的解决方案:再次从数据库中提取原始父级 - 没有子级,因为您知道您只想保存更改的父级属性:

var originalParent = context.Set<Parent>()
    .Where(p => p.ID == parent.ID)
    .FirstOrDefault();

context.Entry(originalParent).CurrentValues.SetValues(parent);
context.SaveChanges();

您必须首先从数据库加载父项(使用主动更改跟踪。),但另一方面,此处发出的 UPDATE 命令将仅包含更改的属性。 既然您说您不介意发送完整的 UPDATE 命令(通过将 state 设置为Modified ),我想您没有性能问题。 因此,加载原始文件然后发送仅包含更改的属性的小型 UPDATE 可能不会比发送完整的 UPDATE 命令更糟糕或更糟糕的性能。

暂无
暂无

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

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