2010-02-02 3 views
48

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

Теперь мне нужно сделать операции копирования/клонирования с помощью Entity Framework. Сначала я начал писать следующим образом:

 
foreach(sourcedata1 in table1) 
{ 
    ... create new table 
    ... copy data 
    ... create Guid 
    ... add 
    foreach(sourcedata2 in table2) 
    { 
     ... create new table 
     ... copy data 
     ... create Guid 
     ... add  

     ... and so on 
    } 
} 

Проблема в том, что это не очень хороший способ сделать это. Есть ли какая-либо легкая информация о клонировании (кроме Guid, которая должна быть сгенерирована для новых строк) или мне нужно вручную копировать все?

Другое решение

Вы также можете использовать EmitMapper или AutoMapper сделать копирование свойств.

ответ

15

Используя прямую сериализации, вы можете сделать это:

http://social.msdn.microsoft.com/forums/en-US/adodotnetentityframework/thread/a967b44b-c85c-4afd-a499-f6ff604e2139/

с помощью отражения, но много больше кода вы можете сделать это: http://msmvps.com/blogs/matthieu/archive/2008/05/31/entity-cloner.aspx

+0

кажется, что там не лучше решение, поэтому я принимаю это как ответ :) – Tx3

+0

@ TX3 Если какой-либо из ответов добавлены позже предоставить больше любезность, которую вы искали, не стесняйтесь для обновления принятого ответа. :-) –

+0

Решение, предлагаемое [здесь] (http://stackoverflow.com/a/15322430/159341), похоже на гораздо более чистый. Использование 'AsNoTracking()' для возврата отдельного объекта, который можно повторно добавить, который создает новый объект при сохранении контекста. – Tr1stan

61

Для клонирования объекта в Entity Framework вы могли бы просто отсоедините объект от DataContext, а затем снова добавьте его в EntityCollection.

context.Detach(entity); 
entityCollection.Add(entity); 

Update для EF6 + (с комментариями)

context.Entry(entity).State = EntityState.Detached; 
entity.id = 0; 
entity.property = value; 
context.Entry(entity).State = EntityState.Added; 
context.SaveChanges(); 
+3

Для тех, кто пытается этот метод. Помните, что вы не получите новый идентификатор (значение ключа), пока не назовете context.SaveChanges() – Rabbi

+0

Хороший материал, спасибо :-) –

+24

Единственная проблема с этим - отсоединить объект, вы потеряете все ссылки. – Martin

9
public static EntityObject Clone(this EntityObject Entity) 
{ 
    var Type = Entity.GetType(); 
    var Clone = Activator.CreateInstance(Type); 

    foreach (var Property in Type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.SetProperty)) 
    { 
     if (Property.PropertyType.IsGenericType && Property.PropertyType.GetGenericTypeDefinition() == typeof(EntityReference<>)) continue; 
     if (Property.PropertyType.IsGenericType && Property.PropertyType.GetGenericTypeDefinition() == typeof(EntityCollection<>)) continue; 
     if (Property.PropertyType.IsSubclassOf(typeof(EntityObject))) continue; 

     if (Property.CanWrite) 
     { 
      Property.SetValue(Clone, Property.GetValue(Entity, null), null); 
     } 
    } 

    return (EntityObject)Clone; 
} 

Это простой метод, который я написал. Он должен работать для большинства людей.

+0

Это хорошо работает для модальных диалогов. Вы клонируете объект, задаете его как контекст для модального диалога, а затем, когда они нажимают ok, установите значения обратно в исходный объект. Если они отменяют вас, вы ничего не делаете. Очень просто. Раньше я отменял изменения, но это было беспорядочно, если у объекта уже были изменения, поэтому он был бы отменен с самого начала. Это также решает проблему, что привязка в главном окне будет обновляться, когда пользователь вводит диалоговое окно. – MikeKulls

+5

Для этого требуется ряд исправлений. Во-первых, я думаю, вы хотели использовать continue вместо break для первых трех строк в инструкции foreach. В зависимости от порядка возвращаемых свойств это приведет к неправильной работе. Во-вторых, вам нужно удалить BindingFlags.DeclaredOnly, поскольку это приводит к сбою с объектами сущностей, которые наследуются от других объектов объекта. Например, в моем случае Персонал унаследован от Лица, но я только что получил свойства на Клонировании персонала. – MikeKulls

+0

Это также теряет ссылки? –

8

Чтобы добавить новую строку, содержание которого основано на существующей строки, выполните следующие действия:

  1. Получить объект на основе исходного ряда.
  2. Задайте состояние записи для добавляемого объекта.
  3. Изменить сущность.
  4. Сохраните изменения.

Вот пример:

var rabbit = db.Rabbits.First(r => r.Name == "Hopper"); 
db.Entry(rabbit).State = EntityState.Added; 
rabbit.IsFlop = false; 
db.SaveChanges(); 
+0

Когда я попробовал это, у меня появилось сообщение об ошибке «Изменения в базе данных были успешно завершены, но при обновлении контекста объекта произошла ошибка». – Rocklan

0

Если вы хотите создать копию объекта для сравнения позже в вашем исполнении кода, вы можете выбрать объект в новом контексте дб.

Если, например, вы обновляете объект, затем в код, который вы хотите сравнить обновленный и оригинальный объект:

var db = new dbEntityContext(); 
var dbOrig = new dbEntityContext(); 

var myEntity = db.tblData.FirstOrDefault(t => t.Id == 123); 
var myEntityOrig = dbOrig.tblData.FirstOrDefault(t => t.Id == 123); 

//Update the entity with changes 
myEntity.FirstName = "Gary"; 

//Save Changes 
db.SaveChnages(); 

На данный момент, myEntity.FirstName будет содержать "Gary" в то время как myEntityOrig.FirstName будет содержать исходное значение , Полезно, если у вас есть функция для регистрации изменений, где вы можете пройти в обновленном и оригинальном объекте.

0

Действительно короткий способ дублирования объектов с использованием дженериков (VB, извините).
Он копирует значения внешнего ключа (внешние идентификаторы), но не загружает связанные объекты.

<Extension> _ 
Public Function DuplicateEntity(Of T As {New, Class})(ctx As myContext, ent As T) As T 
    Dim other As New T 'T is a proxy type, but New T creates a non proxy instance 
    ctx.Entry(other).State = EntityState.Added 'attaches it to ctx 
    ctx.Entry(other).CurrentValues.SetValues(ent) 'copies primitive properties 
    Return other 
End Function 

Например:

newDad = ctx.DuplicateEntity(oDad) 
newDad.RIDGrandpa ' int value copied 
newDad.Grandpa ' object for RIDGrandpa above, equals Nothing(null) 
newDad.Children ' nothing, empty 

Я не знаю точно, как перезагрузить Grandpa в этом случае.
Это не работает:

ctx.SaveChanges() 
ctx.Entry(newDad).Reload() 

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

newDad.Grandpa = oDad.Grandpa 

EDIT: Как MattW proposes in his comment, отсоединение и найти новый объект вы получите его дети загружены (не коллекции).

ctx.Entry(newDad).State = EntityState.Detached 
ctx.Find(newDad.RowId) 'you have to know the key name 
+0

Perfect ... Я просто написал метод Web API Put и хотел проверить, что несколько ключевых полей не были изменены из существующих значений - я решил загрузить объект из базы данных, сравнить неизменяемые поля со значением из Web API, скопируйте остальные и сохраните копию базы данных. SetValues ​​- это именно то, что я искал, чтобы сделать шаг «скопировать остаток». – MattW

+1

Для проблемы перезагрузки вы попробовали 'Detach', а затем' Find'? Я ожидаю, что это даст новую ссылку на объект, не знаю, будет ли это проблемой для вас. – MattW

+0

да! он работает. Я редактирую свой ответ. благодаря –