2013-03-09 6 views
62

Я использую Entity Framework 5 (DBContext), и я пытаюсь найти лучший способ глубоко скопировать объект (то есть скопировать объект и все связанные объекты), а затем сохранить новые объекты в базе данных. Как я могу это сделать? Я изучил методы расширения, такие как CloneHelper, но я не уверен, применим ли он к DBContext.Entity Framework 5 глубокая копия/клонирование объекта

+0

Я попытался Deep клона/дублировать объекты сущностей, используя отражение, описанное в следующей [ссылка] (http://code.msdn.microsoft. com/CSEFDeepCloneObject-12a5cb95), но, как я понимаю, производные типы EntityObject не поддерживаются API-интерфейсом DbContext. – kypk

ответ

116

Один дешевые простой способ клонировать объект должны сделать что-то вроде этого:

var originalEntity = Context.MySet.AsNoTracking() 
          .FirstOrDefault(e => e.Id == 1); 
Context.MySet.Add(originalEntity); 
Context.SaveChanges(); 

трюка здесь является AsNoTracking() - при загрузке объекта, как это, ваш контекст не знает о он и когда вы вызываете SaveChanges, он будет рассматривать его как новый объект.

Если MySet имеет ссылку на MyProperty и вы хотите копию этого тоже, просто использовать Include:

var originalEntity = Context.MySet.Include("MyProperty") 
          .AsNoTracking() 
          .FirstOrDefault(e => e.Id == 1); 
+0

Этот трюк просто защитил меня довольно долго :-). Но с моей конфигурацией DbContext было исключение из-за невозможности автоматического добавления Entity. Мне пришлось пройти через ObjectContext, как этот 'DirectCast (DbContext, IObjectContextAdapter) .ObjectContext.AddObject (entitySetName, entity)' – Patrick

+0

У меня есть проекция из ef-контекста, подобная этому 'dbContext, Select (x => {a = x, ab = x.MyCollection.Where (g => g.Id> 2)}) ToList() 'Если я добавляю' AsNoTracking() 'Потеря данных из запроса. – Eldho

+1

Это отлично поработало для меня с использованием EF Core. Однако мне пришлось установить первичные ключи родителя и вложенные объекты в Guid.Empty, чтобы предотвратить попытку EF вставить повторяющиеся строки в базу данных. Если вы используете целые ключи, я подозреваю, что их установка в 0 будет иметь тот же эффект. – agileMike

20

Вот еще один вариант.

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

//Get entity to be cloned 
var source = Context.ExampleRows.FirstOrDefault(); 

//Create and add clone object to context before setting its values 
var clone = new ExampleRow(); 
Context.ExampleRows.Add(clone); 

//Copy values from source to clone 
var sourceValues = Context.Entry(source).CurrentValues; 
Context.Entry(clone).CurrentValues.SetValues(sourceValues); 

//Change values of the copied entity 
clone.ExampleProperty = "New Value"; 

//Insert clone with changes into database 
Context.SaveChanges(); 

Этот метод копирует текущие значения из источника в новую добавленную строку.

+0

Это отлично работает, если вы хотите, чтобы клон был вставлен вместе с обновлениями оригинала в одном SaveChanges – Dacker

+0

'SetValues' не работает, если новый объект не подключен к контекст. Он генерирует исключение InvalidOperationException. Если все, что вы хотите сделать, - клонировать объект в отдельном состоянии, вы можете добавить объект в контекст, установить его текущие значения, а затем отсоединить. – Suncat2000

1

Это универсальный метод расширения, который позволяет использовать общее клонирование.

Вы должны получить System.Linq.Dynamic от nuget.

public TEntity Clone<TEntity>(this DbContext context, TEntity entity) where TEntity : class 
    { 

     var keyName = GetKeyName<TEntity>(); 
     var keyValue = context.Entry(entity).Property(keyName).CurrentValue; 
     var keyType = typeof(TEntity).GetProperty(keyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).PropertyType; 

     var dbSet = context.Set<TEntity>(); 
     var newEntity = dbSet 
      .Where(keyName + " = @0", keyValue) 
      .AsNoTracking() 
      .Single(); 

     context.Entry(newEntity).Property(keyName).CurrentValue = keyType.GetDefault(); 

     context.Add(newEntity); 

     return newEntity; 
    } 

Единственное, что вам нужно реализовать, это метод GetKeyName. Это может быть что угодно: от до return the first guid property или вернуть первое имущество, помеченное DatabaseGenerated(DatabaseGeneratedOption.Identity)].

В моем случае я уже отмечал свои классы с [DataServiceKeyAttribute("EntityId")]

private string GetKeyName<TEntity>() where TEntity : class 
    { 
     return ((DataServiceKeyAttribute)typeof(TEntity) 
      .GetCustomAttributes(typeof(DataServiceKeyAttribute), true).First()) 
      .KeyNames.Single(); 
    } 
Смежные вопросы