2010-02-02 18 views
48

Tworzę oprogramowanie, w którym użytkownik może stworzyć nowy produkt na podstawie starszego produktu.Klonowanie danych w Entity Framework

Teraz muszę wykonać kopiujące/klonujące operacje z Entity Framework. Najpierw zacząłem pisać tak:

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

Problem polega na tym, że nie jest to dobry sposób na zrobienie tego. Czy istnieje jakaś łatwa informacja o klonie (z wyjątkiem Guid, która musi zostać wygenerowana dla nowych wierszy) lub czy powinienem ręcznie skopiować wszystko?

Inne rozwiązanie

Można również użyć EmitMapper lub AutoMapper zrobić kopiowanie właściwości.

Odpowiedz

15
+0

Wydaje się, że nie ma ładniejsze rozwiązanie, więc ja przyjmuję to jako odpowiedź :) – Tx3

+0

@ TX3 Jeżeli którykolwiek z odpowiedziami dodawanych później dostarczają więcej o uprzejmość, czego szukasz, nie krępuj aby zaktualizować zaakceptowaną odpowiedź. :-) –

+0

Dostarczone rozwiązanie [tutaj] (http://stackoverflow.com/a/15322430/159341) wydaje się być znacznie czystsze. Używanie 'AsNoTracking()' do zwracania odłączonego obiektu, który może zostać ponownie dodany, co tworzy nowy obiekt po zapisaniu kontekstu. – Tr1stan

61

Aby sklonować podmiot w Entity Framework mogłeś Wystarczy odłączyć obiekt od DataContext, a następnie ponownie dodać go do EntityCollection.

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

Aktualizacja dla EF6 + (z komentarzami)

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

Dla osób wypróbowujących tę metodę. Pamiętaj, że nie dostaniesz nowego identyfikatora (wartości klucza), dopóki nie wywołasz context.SaveChanges() – Rabbi

+0

Dobre rzeczy, dziękuję :-) –

+24

Jedynym problemem jest to, że jeśli odłączysz jednostkę, stracisz wszystkie odniesienia. – 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; 
} 

Jest to prosta metoda pisałem. Powinno działać dla większości ludzi.

+0

Działa to bardzo dobrze w przypadku dialogów modalnych. Klonujesz obiekt, ustawiasz go jako kontekst dla modalnego okna dialogowego, a kiedy klikniesz OK, ustaw wartości z powrotem na oryginalny obiekt. Jeśli anulują, nie rób nic. Bardzo łatwe. Poprzednio cofałem zmiany, ale było to nieprzyjemne, gdy obiekt już się zmieniał, więc zostałby cofnięty na samym początku. Rozwiązuje to również problem polegający na tym, że powiązanie w głównym oknie zostanie zaktualizowane, tak jak użytkownik wpisał to okno dialogowe. – MikeKulls

+5

To wymaga wielu poprawek. Po pierwsze myślę, że zamierzałeś użyć kontynuacji zamiast przerwy dla pierwszych trzech linii w oświadczeniu foreach. W zależności od kolejności zwróconych właściwości, zachowanie będzie niepoprawne. Po drugie, musisz usunąć BindingFlags.DeclaredOnly, ponieważ powoduje to niepowodzenie z obiektami encji, które są dziedziczone z innych obiektów encji. Np. W moim przypadku Personel odziedziczył po Osobie, ale właśnie dostałem właściwości sklonowanego personelu. – MikeKulls

+0

To również traci referencje? –

8

Aby dodać nowy wiersz, którego treść jest na podstawie istniejącego wiersza, wykonaj następujące kroki:

  1. Get podmiot na podstawie wiersza początkowego.
  2. Umożliwia ustawienie stanu wpisu dla obiektu do dodania.
  3. Modyfikuj jednostkę.
  4. Zapisz zmiany.

Oto przykład:

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

Po wypróbowaniu tego dostałem komunikat o błędzie, który powiedział: "Zmiany w bazie danych zostały zatwierdzone pomyślnie, ale wystąpił błąd podczas aktualizowania kontekstu obiektu." – Rocklan

0

Jeśli chcesz utworzyć kopię podmiot dla porównania później w swoim wykonanie kodu, można wybrać jednostkę w nowym kontekście db.

Jeśli na przykład aktualizowania podmiot, a później w kodzie chcesz porównać uaktualnioną i oryginalny podmiot:

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(); 

W tym momencie myEntity.FirstName będzie zawierać "Gary" podczas myEntityOrig.FirstName będzie zawierać oryginalną wartość . Przydatne, jeśli masz funkcję rejestrowania zmian, w których możesz przekazać zaktualizowaną i oryginalną encję.

0

Naprawdę krótki sposób powielania podmiotów używających generycznych (VB, przepraszam).
Kopiuje obce wartości kluczy (zewnętrzne identyfikatory), ale nie ładuje ich powiązanych obiektów.

<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 

Na przykład:

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

Nie wiem dokładnie, jak przeładować Grandpa w tym przypadku.
To nie działa:

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

ale tak naprawdę, nie ma problemu. Wolałbym przydzielić ręcznie Grandpa, jeśli jest to potrzebne.

newDad.Grandpa = oDad.Grandpa 

EDIT: Jak MattW proposes in his comment, odłączania i znalezienie nowego podmiotu masz jej dzieci (nie załadowany kolekcji).

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

Idealnie ... Właśnie pisałem metodę Web API Put i chciałem sprawdzić, czy kilka kluczowych pól nie zostało zmienionych z istniejących wartości - pomyślałem, że ładuję encję z bazy danych, porównuję niezmienne pola z wartością z sieci API, skopiuj resztę i zapisz kopię bazy danych. SetValues ​​jest właśnie tym, czego szukałem, aby wykonać krok "skopiuj resztę". – MattW

+1

W przypadku problemu z ponownym ładowaniem, czy wypróbowałeś 'Odłączenie', po którym następuje' Znajdź'? Oczekuję, że da to nowe odniesienie do obiektu, nie wiem, czy byłby to problem dla ciebie. – MattW

+0

tak! wydaje się działać. Edytuję swoją odpowiedź. dzięki –