2010-02-02 6 views
48

Je crée un logiciel dans lequel l'utilisateur peut créer un nouveau produit basé sur un produit plus ancien. Maintenant, je dois faire des opérations de copie/clonage avec Entity Framework. D'abord j'ai commencé à écrire comme ceci:Clonage de données sur 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 
    } 
} 

Le problème est que ce n'est pas une bonne façon de le faire. Existe-t-il des informations de clonage simples (à l'exception de Guid qui doit être généré pour les nouvelles lignes) ou devrais-je copier manuellement tout?

Autre solution

Vous pouvez également utiliser EmitMapper ou AutoMapper pour faire la copie des propriétés.

Répondre

15

En utilisant sérialisation droite, vous pouvez le faire:

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

aide de la réflexion, mais avec beaucoup plus de code que vous pouvez faire ceci: http://msmvps.com/blogs/matthieu/archive/2008/05/31/entity-cloner.aspx

+0

Il semble qu'il n'y a pas plus belle solution, donc je vais accepter cela comme une réponse :) – Tx3

+0

@ Tx3 Si l'une des réponses ajoutées par la suite de fournir plus de niceness que vous recherchez, ne hésitez pas mettre à jour la réponse acceptée. :-) –

+0

La solution fournie [ici] (http://stackoverflow.com/a/15322430/159341) donne l'impression d'être beaucoup plus propre. Utiliser 'AsNoTracking()' pour retourner un objet détaché qui peut être rajouté, ce qui crée une nouvelle entité lorsque le contexte est sauvegardé. – Tr1stan

61

Pour cloner une entité dans Entity Framework vous pourriez Détachez simplement l'entité du DataContext, puis rajoutez-la au EntityCollection.

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

Mise à jour pour EF6 + (des commentaires)

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

Pour ceux qui essayent cette méthode. Rappelez-vous que vous n'obtiendrez pas un nouvel ID (valeur de clé) jusqu'à ce que vous appeliez context.SaveChanges() – Rabbi

+0

Bon, merci :-) –

+24

Le seul problème avec ceci est que si vous détachez une entité, vous perdez toutes les références. – 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; 
} 

C'est une méthode simple je l'ai écrit. Cela devrait marcher pour la plupart des gens.

+0

Cela fonctionne très bien pour les boîtes de dialogue modales. Vous clonez l'objet, le définissez en tant que contexte de la boîte de dialogue modale, puis, lorsqu'ils cliquent sur OK, redéfinissez les valeurs sur l'objet d'origine. S'ils annulent, vous ne faites rien. Très facile. Auparavant, j'étais en train d'annuler les modifications, mais cela devenait désordonné si l'objet avait déjà été modifié, de sorte qu'il serait annulé dès le début. Cela résout également le problème que la liaison sur la fenêtre principale serait mise à jour lorsque l'utilisateur tapait dans la boîte de dialogue. – MikeKulls

+5

Cela nécessite un certain nombre de corrections. Le premier est que je pense que vous vouliez utiliser continuer au lieu de pause pour les trois premières lignes dans l'instruction foreach. En fonction de l'ordre des propriétés renvoyées, ce comportement sera incorrect. La seconde est que vous devez supprimer BindingFlags.DeclaredOnly car cela provoque son échec avec les objets entité hérités d'autres objets entité. Par exemple, dans mon cas, le personnel est hérité de la personne, mais je recevais juste les propriétés sur le personnel cloné. – MikeKulls

+0

Cela perd également les références? –

8

Pour ajouter une nouvelle ligne dont le contenu est basé sur une ligne existante, procédez comme suit:

  1. Obtenez une entité basée sur la ligne de départ.
  2. Définissez l'état d'entrée de l'entité sur Ajouté.
  3. Modifier l'entité.
  4. Enregistrer les modifications.

Voici un exemple:

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

Lorsque j'ai essayé ceci, j'ai reçu un message d'erreur disant "Les modifications de la base de données ont été validées, mais une erreur est survenue lors de la mise à jour du contexte de l'objet." – Rocklan

0

Si vous souhaitez créer une copie d'une entité pour une comparaison ultérieure dans l'exécution de votre code, vous pouvez sélectionner l'entité dans un nouveau contexte db.

Si par exemple vous mettez à jour une entité, puis plus tard dans le code que vous voulez comparer l'entité mise à jour et originale:

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

À ce stade, myEntity.FirstName contiendra "Gary" tout myEntityOrig.FirstName contiendra la valeur d'origine . Utile si vous avez une fonction pour consigner les modifications dans lesquelles vous pouvez transmettre l'entité mise à jour et l'entité d'origine.

0

Une manière très courte de dupliquer des entités en utilisant des génériques (VB, désolé).
Il copie les valeurs de clé étrangère (ID externe) mais ne charge pas leurs objets associés.

<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 

Par exemple:

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

Je ne sais pas comment recharger exactement Grandpa dans ce cas.
Cela ne fonctionne pas:

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

mais vraiment, pas de problème. Je préfère assigner Grandpa à la main si j'en ai besoin.

newDad.Grandpa = oDad.Grandpa 

EDIT: Comme MattW proposes in his comment, détacher et de trouver la nouvelle entité que vous obtenez ses enfants chargés (non collections).

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

Parfait ... Je ne faisais qu'écrire une méthode Web API Put et je voulais vérifier que quelques champs clés n'étaient pas altérés à partir des valeurs existantes - Je pensais charger l'entité de la base de données, comparer les champs immuables à la valeur du Web API, copiez le reste et enregistrez la copie de la base de données. SetValues ​​est exactement ce que je cherchais pour faire l'étape "copier le reste". – MattW

+1

Pour le problème de rechargement, avez-vous essayé un 'Detach' suivi d'un' Find'? Je m'attends à ce que cela donne une nouvelle référence d'objet, je ne sais pas si cela serait un problème pour vous. – MattW

+0

oui! ça a l'air de marcher. Je modifie ma réponse. Merci –