web-dev-qa-db-de.com

Entity Framework 6: Objekt außer ID klonen

In meinem MVVM-Programm habe ich eine Model-Klasse (sagen wir MyModel), aus der ich eine Instanz des Lesens aus der Datenbank (mit Entity Framework) habe. Beim Abrufen des Objekts präsentiere ich dem Benutzer alle Daten. Später werden vom Benutzer einige Felder geändert.
Ich möchte dasselbe Objekt erstellen, außer dass es ID ist (da ID der Primärschlüssel und automatisch inkrementiert)).
Wie könnte ich das angehen? Ich möchte nicht alle Felder einzeln kopieren, dies ist kein robuster Ansatz. Da das Modell möglicherweise in Zukunft geändert wird, muss dies bei der Klonmethode berücksichtigt werden.

Gibt es also eine elegante Möglichkeit, das Objekt zu kopieren, und beim Speichern in der Datenbank wird die ID automatisch erneut inkrementiert? (Wenn ich die ID auf null setze, erhalte ich einen Compilerfehler, da er vom Typ int ist.).

46
QuantumHive

Mir ist aufgefallen, dass keine Notwendigkeit zum Kopieren besteht. Anscheinend fügt Entity Framework beim Hinzufügen einer Instanz eines Modells zur Datenbank (auch wenn die ID auf eine bereits in der Datenbank vorhandene ID festgelegt ist) eine neue Zeile in die Datenbank ein und inkrementiert den Primärschlüssel automatisch. Diese Funktionalität ist also bereits in EF integriert. Ich wusste das nicht, sorry.
Nur der Klarheit halber hier ein Beispiel:

using(var database = new MyDbContext()) {
    MyModel myModel = database.FirstOrDefault(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();
}
59
QuantumHive

Lori Peterson hat vorgeschlagen, .AsNoTracking () zu verwenden, um das Klonen in EF6 durchzuführen. Ich verwende diese Methode und kann bestätigen, dass es funktioniert. Sie können sogar untergeordnete Objekte einschließen.

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

entity.SomeProperty = DateTime.Now;

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

Wenn Sie eine Entität oder Entitäten aus einem Dataset abrufen, können Sie Entity Framework anweisen, keine der Änderungen zu verfolgen, die Sie an diesem Objekt vornehmen, und diese Entität dann als neue Entität zum Dataset hinzufügen. Bei Verwendung von .AsNoTracking weiß der Kontext nichts über die vorhandene Entität.

38
jaycer

Bei Verwendung von ObjectContext funktioniert die von QuantumHive bereitgestellte Antwort nicht.

Der in dieser Situation zurückgegebene Fehler ist:

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)

So klonen Sie ein Entity Framework-Objekt korrekt (mindestens 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;
    }
}
4
sweetfa

Ich fand dies, um herauszufinden, ob es einen besseren Weg gibt, ein Objekt zu klonen, als ich es derzeit verwende, und bemerkte, dass es ein potenzielles Problem mit der akzeptierten Antwort gibt, wenn Sie versuchen, mehrere Klone zu erstellen ... zumindest, wenn Sie möchten vermeide es, deinen Kontext oft zu erstellen ...

Ich weiß nicht, ob dies der beste Ansatz zum Klonen ist, weshalb ich nach einem anderen Weg gesucht habe. Aber es funktioniert. Wenn Sie eine Entität mehrmals klonen müssen, können Sie mit der JSON-Serialisierung ... so etwas klonen (mit Newtonsoft 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();
}
4
Kevin Nelson

Ich benutze die Postgres-Datenbank:

CREATE TABLE public."Table" ( 
    "Id" integer NOT NULL DEFAULT nextval('"Table_Id_seq"'::regclass),
    ...

Keine der genannten Methoden funktioniert in meinem Fall nicht. Ich benutze zweitens:

Table table = _context.Table.AsNoTracking().Select(s => new Table {
 // some properties, exept id
    }).FirstOrDefault();
_context.Table.Add(table);
await _context.SaveChangesAsync();
0
user1855805