Дублирование модели с иерархией

Моя модель выглядит примерно так:

Company
-Locations

Locations
-Stores

Stores
-Products

Итак, я хочу сделать копию компании, и все ее ассоциации также должны быть скопированы и сохранены в базе данных.

Как я могу это сделать, если у меня в памяти загружена Компания?

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

Если это сложно, я могу перебрать каждую ассоциацию, а затем вызвать создание нового объекта. Идентификаторы будут другими, но все остальное должно быть одинаковым.

Я использую EF 6.


person cool breeze    schedule 27.04.2016    source источник


Ответы (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 1 и store 2, product A будет вставлено дважды. Если вы хотите предотвратить это, вы должны получать объекты по одному контексту, с отслеживанием (т. е. без AsNoTracking) и Add() в новом контексте. Включив отслеживание, EF отслеживает идентичные объекты и не будет их дублировать. В этом случае создание прокси должно быть отключено, иначе сущности сохранят ссылку на контекст, из которого они пришли.

Подробнее здесь: Объединение идентичных баз данных в одну

person Gert Arnold    schedule 27.04.2016
comment
Я использую UnitOfWork, как правильно отсоединить? asp.net/mvc/overview/older-versions/ - person cool breeze; 27.04.2016
comment
Вам не обязательно его использовать. Не позволяйте собственной архитектуре задушить вас. Этот общий репозиторий очень затрудняет использование функций EF в полной мере. Как вы собираетесь использовать AsNoTracking, если хотите? - person Gert Arnold; 28.04.2016
comment
Но как же сейчас все не рефакторить. Я должен изменить все тогда правильно? - person cool breeze; 28.04.2016
comment
Вы должны как-то попытаться получить граф объектов с AsNoTracking и добавить его, или один с отключенным созданием прокси, который вы можете добавить в новый UoW. Ваши методы репозитория должны позволять это. - person Gert Arnold; 28.04.2016

Я бы добавил метод к каждой модели, которую нужно клонировать таким образом, я бы также рекомендовал интерфейс для него.

Это можно сделать примерно так:

//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;
}

Вы должны повторить этот базовый шаблон для каждого класса и «дочернего» класса, который должен быть копируемым. Таким образом, каждый объект знает, как создать глубокий клон самого себя, и передает ответственность за дочерние объекты дочернему классу, аккуратно инкапсулируя все.

Его можно было бы использовать так:

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

Мои извинения, если в приведенном выше коде есть какие-либо ошибки; У меня сейчас нет Visual Studio, чтобы все проверить, я работаю по памяти.


Еще один действительно простой и эффективный способ глубокого клонирования объекта с помощью сериализации можно найти в этом сообщении Как выполнить глубокое копирование объекта в .Net (в частности, в C#)?

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].

person Bradley Uffner    schedule 27.04.2016