Как скопировать объект из одного контекста платформы Entity Framework в другой?

Как скопировать объект из одного контекста (наследующего от DbContext) на другой?

Все, что я нашел, работает только для ObjectContext, но не для DbContext или использует DbContext, но не работает.

Например, я нашел/попробовал:

  • Использует ObjectContext: CloneHelper в CodeProject
  • Настройка LazyLoadingEnabled на false приводит к не заполнению свойств ICollection < > (внешних ключей)
  • Настройка ProxyCreationEnabled на false приводит к сохранению свойств ICollection < > как нулевых (внешних ключей)
  • Entry < > State = Отдельные результаты не заполняют свойства ICollection < > (внешние ключи), если они установлены перед добавлением свойства, или блокируют очистку идентификатора, если он установлен позже.
  • AsNoTracking() приводит к исключению (одно из следующего: зависит от того, вставляет ли сначала удаленный контекст элемент из родительского свойства ICollection < > или сначала родителя):
    • Родитель: объект в роли "ModelFirstSub_First_Target" не может быть автоматически добавлен в контекст, потому что он был извлечен с помощью параметра слияния NoTracking. Явно привязываю объект к объекту ObjectContext перед определением отношения.
    • Элемент: объект не может быть добавлен или присоединен, поскольку его EntityReference имеет значение свойства EntityKey, которое не соответствует EntityKey для этого объекта.

Я буду использовать его для двух целей:

  • Скопировать все из одной базы данных в другую (целевой будет клонировать оригинал, локальную копию удаленной базы данных). Идентификаторы объектов должны быть сохранены.
  • Добавить или обновить выбранные объекты из одной базы данных в другую (восходящие изменения в локальном кэше удаленного источника). Идентификаторы вновь созданных объектов не должны сохраняться.

Как это сделать?

EF 6.1.1,.NET 4.5.2, С#

Вот тестовый код, который пытается имитировать второе действие (восходящее изменение возвращается к удаленной базе данных):

var addedFirst = localContext.Firsts.AsNoTracking().Single(m => m.Id == 4);
var updatedFirst = localContext.Firsts.AsNoTracking().Single(m => m.Id == 2);

addedFirst.Items.First().Id = 0;
addedFirst.Items.First().FirstId = 0;
addedFirst.Id = 0;

remoteContext.FirstItems.Add(addedFirst.Items.First());
remoteContext.Firsts.Add(addedFirst);

var originalFirst = remoteContext.Firsts.Single(m => m.Id == 2);
var originalItem = originalFirst.Items.First();
originalItem.Title = updatedFirst.Items.First().Title;

Вот модель:

public class ModelFirstBase
{
    public virtual int Id { get; set; }
    public virtual ICollection<ModelFirstSub> Items { get; set; }
    public virtual string Title { get; set; }
}

public class ModelFirstSub
{
    public virtual int Id { get; set; }
    public virtual int FirstId { get; set; }
    public virtual ModelFirstBase First { get; set; }
    public virtual string Title { get; set; }
}

PS: свойства должны поддерживаться виртуальными, поскольку модель разделяется с производственным приложением, которое использует динамические прокси.

Ответ 1

Попробовал ли вы просто перевести модель во вторую модель:

public class ModelFirstBase
{
public virtual int Id { get; set; }
public virtual ICollection<ModelFirstSub> Items { get; set; }
public virtual string Title { get; set; }
}

public class ModelFirstSub
{
public virtual int Id { get; set; }
public virtual int FirstId { get; set; }
public virtual ModelFirstBase First { get; set; }
public virtual string Title { get; set; }
}

public class ModelTranslator
{
public ModelFirstSub TranslateModelFirstBase(ModelFirstBase entity)
{
//do some error handling and null checks.
var second = new ModelFirstSub(){
                  FirstId = entity.Id,
                  .....
             };
return second;
}
}

public void TransferModels(){ //I haven't thought about disposing stuff, you should.
var firstContext = new FirstContext();
var secondContext = new SecondContext();
foreach (var first in firstContext.ModelFirstBases){
     var second = new ModelTranslator().TranslateModelFirstBase(first);
     secondContext.ModelFirstSubs.Add(second);
}
secondContext.SaveChanges();
}

Однако обратите внимание, что EF не предназначен для массового копирования данных, это даже несмотря на то, что работа не является реальным решением. Вы должны подумать о SqlBulkCopy вместо этого, если используете сервер Sql или что-то эквивалентное для любого используемого DB.

SQLBulkCopy: Как работает SqlBulkCopy

Ответ 2

Рассматривали ли вы стороннюю библиотеку, такую ​​как AutoMapper или ValueInjector?

Кажется, что они были созданы для решения этих проблем.

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