2014-09-08 28 views
20

Nel mio programma MVVM ho una classe Model (ad esempio MyModel) da cui ho un'istanza di lettura dal database (utilizzando Entity Framework). Durante il recupero dell'oggetto, sto presentando tutti i dati all'utente. In seguito l'utente modificherà alcuni campi.
Quello che voglio è quello di creare lo stesso oggetto, tranne per la sua ID (dal momento che è l'auto IDchiave primaria e incrementato).
Quindi, come potrei avvicinarmi a questo? Non voglio copiare tutti i campi uno per uno, questo non è un approccio robusto. Perché forse in futuro il modello potrebbe essere modificato, quindi in questo modo dovrò tenerne conto nel metodo di clonazione.Entity Framework 6: oggetto clone tranne ID

Quindi esiste un modo elegante per copiare l'oggetto e al momento del salvataggio nel database, l'ID viene automaticamente incrementato di nuovo? (L'impostazione dell'ID su null mi dà un errore del compilatore, perché è di tipo int).

+0

Che dire di ValueInjecter? Sfortunatamente, fa una copia superficiale, non profonda. http://valueinjecter.codeplex.com –

+1

Puoi mostrarci la tua classe di modello? In caso contrario, è un POCO? Ha proprietà complesse? –

+1

appena finito di leggere, prima di pensare, direi, caricare poi staccare e avrai la tua copia pronta per l'inserimento. Ora dobbiamo parlare di tipo complesso come suggerito da @GeorgeVovos – tschmit007

risposta

26

Ho notato che non è necessario copiare. Apparentemente quando si aggiunge un'istanza di un modello al database (anche se l'ID è impostato su uno che esiste già nel database), Entity Framework inserisce una nuova riga nel database e incrementa automaticamente la sua chiave primaria. Quindi questa funzionalità è già integrata in EF. Non lo sapevo, mi dispiace.
Solo per amor di chiarezza, ecco un esempio:

using(var database = new MyDbContext()) { 
    MyModel myModel = database.Where(m => m.SomeProperty == someValue); 
    myModel.SomeOtherProperty = someOtherValue; //user changed a value 
    database.MyModels.Add(myModel); //even though the ID of myModel exists in the database, it gets added as a new row and the ID gets auto-incremented 
    database.SaveChanges(); 
} 
+1

A volte è un po 'troppo facile. Avevo provato un'ora con MemberwiseClone ... – Niklas

+0

** Dove ** restituisce una collezione però. Inoltre, questo non funzionerà se la tua entità ha un oggetto o una collezione di oggetti. Almeno non per me. –

+3

@THEStephenStanton Il metodo di estensione linq '.Where()' è solo un esempio. Si potrebbe anche usare il metodo '.Find()' su un Entity Framework 'DbSet ' per questo scopo. Ma non è questo il punto, il punto è che EF auto-incrementa l'ID quando aggiungi un modello sporco al tuo DbContext. – QuantumHive

4

Quando si utilizza ObjectContext la risposta fornita da QuantumHive non funziona.

L'errore riscontrato in questa situazione è:

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. 
System.InvalidOperationException: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. 
    at System.Data.Objects.ObjectStateManager.AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, String argumentName, Boolean isAdded) 
    at System.Data.Objects.ObjectContext.AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, String argumentName) 
    at System.Data.Objects.DataClasses.RelatedEnd.AddEntityToObjectStateManager(IEntityWrapper wrappedEntity, Boolean doAttach) 
    at System.Data.Objects.DataClasses.RelatedEnd.AddGraphToObjectStateManager(IEntityWrapper wrappedEntity, Boolean relationshipAlreadyExists, Boolean addRelationshipAsUnchanged, Boolean doAttach) 
    at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges) 
    at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedEntity, Boolean applyConstraints) 
    at System.Data.Objects.DataClasses.EntityReference`1.set_ReferenceValue(IEntityWrapper value) 
    at System.Data.Objects.DataClasses.EntityReference`1.set_Value(TEntity value) 

Per clonare correttamente un oggetto Entity Framework (almeno in EF6.0) è:

/// <summary> 
/// Clone a replica of this item in the database 
/// </summary> 
/// <returns>The cloned item</returns> 
public Item CloneDeep() 
{ 
    using (var context = new EntityObjectContext()) 
    { 
     var item = context.Items 
      .Where(i => i.ItemID == this.ItemID) 
      .Single(); 
     context.Detach(item); 
     item.EntityKey = null; 
     item.ItemID = 0; 
     return item; 
    } 
} 
+0

Dipende dal tuo contesto, se non stai chiamando ['context.SaveChanges()'] (http://www.entityframeworktutorial.net/significance-of-savechanges.aspx) prima di inserire una chiave duplicata, quindi il sottostante allegato le entità potrebbero avere chiavi duplicate e questo è il motivo per cui il messaggio di eccezione si lagna correttamente. Quindi questo dipende totalmente dal tuo caso d'uso e implementazione. Dovresti ** non ** avere entità duplicate con la stessa chiave allegata allo stesso contesto. Nel mio esempio, sto ovviamente interrogando un'entità da un nuovo contesto di database, quindi in tal caso * funziona *. – QuantumHive

0

ho trovato questo cercando di vedere se c'era un modo migliore di clonare un oggetto rispetto a quello che stavo usando e ho notato che c'è un potenziale problema con la risposta accettata se stai provando a fare più cloni ... almeno se vuoi evitare di creare il tuo contesto molte volte. ..

Non so se questo è l'approccio migliore alla clonazione, motivo per cui cercavo un altro modo. Ma funziona. Se hai bisogno di clonare un'entità più volte, puoi usare la serializzazione JSON per clonare ... qualcosa del genere (usando Newton JSON).

using(var context = new Context()) { 
    Link link = context.Links.Where(x => x.Id == someId); 
    bool isFirst = true; 
    foreach(var id in userIds) { 
     if(isFirst) { 
      link.UserId = id; 
      isFirst  = false; 
     } 
     else { 
      string cloneString = JsonConvert.SerializeObject(link); 
      Link clone = JsonConvert.DeserializeObject<Link>(cloneString); 
      clone.UserId = id; 
      context.Links.Add(clone); 
     } 
    } 
    context.SaveChanges(); 
} 
17

Lori Peterson ha suggerito usando .AsNoTracking() per eseguire la clonazione in EF6. Sto usando questo metodo e posso confermare che funziona. Puoi persino includere oggetti figlio.

var entity = context.Entities 
        .AsNoTracking() 
        .Include(x => x.ChildEntities) 
        .FirstOrDefault(x => x.EntityId == entityId); 

entity.SomeProperty = DateTime.Now; 

context.Entities.Add(entity); 
context.SaveChanges(); 

quando si stanno recuperando un ente o gli enti da un insieme di dati, si può dire Entity Framework di non monitorare qualsiasi dei cambiamenti che si stanno facendo a tale oggetto e quindi aggiungere tale entità come nuova entità a il set di dati. Con l'utilizzo di .AsNoTracking, il contesto non sa nulla dell'entità esistente.

+0

Ho usato questo approccio, ma ho dovuto modificare la sezione Include in '.Include (" ChildEntities ")' - da msdn: elenco separato da punti di oggetti correlati da restituire nei risultati della query. –

+0

Sto usando questo metodo, tuttavia, quando aggiungo l'entità al contesto che si lamenta di un vincolo di molteplicità, è come se l'entità figlio non aumentasse la sua chiave primaria. Qualcun altro ha avuto questo problema e sa come risolverlo? – Dalton

+0

@Dalton Suggerirei di inserire una nuova domanda e includere il codice pertinente. Non sono sicuro di cosa causerebbe il problema che hai menzionato. – jaycer

Problemi correlati