复制具有层次结构

时间:2016-04-27 15:24:24

标签: c# entity-framework

我的模型看起来像这样:

Company
-Locations

Locations
-Stores

Stores
-Products

所以我想制作一份公司的副本,并且还应该复制它的所有关联并保存到数据库中。

如果我将公司加载到内存中,我怎么能这样做?

Company company = DbContext.Companies.Find(123);

如果它很棘手,我可以循环遍历每个关联,然后调用创建一个新对象。 Id会有所不同,但其他一切都应该是相同的。

我正在使用EF 6。

2 个答案:

答案 0 :(得分:2)

用EF克隆对象图是件小事:

var company = DbContext.Companies.AsNoTracking()
                       .Include(c => c.Locations
                           .Select(l => l.Stores
                               .Select(s => s.Products)))
                       .Where(c => c.Id == 123)
                       .FirstOrDefault();
DbContext.Companies.Add(company);
DbContext.SaveChanges();

这里有几点需要注意。

  • AsNoTracking()至关重要,因为您添加到上下文中的对象不应该被跟踪。
  • 现在,如果您Add() company,则其对象图中的所有实体也会被标记为Added
  • 我假设数据库生成新的主键值(标识列)。如果是这样,EF将忽略数据库中现有对象的当前值。如果没有,您将不得不遍历对象图并自己分配新值。

一个警告:只有当关联为1:0..n时,这才有效。如果存在n:m关联,则可以多次插入相同的实体。例如,如果Store-Product为n:m且product A出现在store 1store 2,则product A将被插入两次。如果您想阻止这种情况,您应该通过一个上下文获取对象,跟踪(即没有AsNoTracking),并在新的上下文中Add()。通过启用跟踪,EF可以跟踪相同的实体并且不会复制它们。在这种情况下,应禁用代理创建,否则实体会保留对它们来自的上下文的引用。

此处有更多详情:Merge identical databases into one

答案 1 :(得分:0)

我会为每个需要以这种方式克隆的模型添加方法,我也建议使用它的接口。

可以这样做:

//Company.cs
Company DeepClone()
{
    Company clone = new Company();

    clone.Name = this.name;
    //...more properties (be careful when copying reference types)

    clone.Locations = new List<Location>(this.Locations.Select(l => l.DeepClone()));

    return clone;
}

你应该为每个班级重复这个基本模式,并且#34;孩子&#34;需要可复制的课程。这样每个对象都知道如何创建自己的深层克隆,并将子对象的责任传递给子类,整齐地封装所有内容。

可以这样使用:

Company copyOfCompany123 = DbContext.Companies.Find(123).DeepClone;

如果上述代码中有任何错误,我很抱歉;我目前无法使用Visual Studio来验证所有内容,而是在内存中工作。

另一个使用序列化深度克隆对象的非常简单且代码有效的方法可以在这篇文章中找到How do you do a deep copy an object in .Net (C# specifically)?

public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

请注意,根据您的对象结构,这可能会有一些非常严重的资源和性能问题。您要使用它的每个类也必须使用[Serializable]属性进行标记。