Entity Framework 5 Deep Copy / Klon einer Entität

74

Ich verwende Entity Framework 5 ( DBContext) und versuche, den besten Weg zu finden, um eine Entität tief zu kopieren (dh die Entität und alle zugehörigen Objekte zu kopieren) und dann die neuen Entitäten in der Datenbank zu speichern. Wie kann ich das machen? Ich habe versucht, Erweiterungsmethoden wie zu verwenden CloneHelper, bin mir aber nicht sicher, ob dies zutrifft DBContext.

kypk
quelle
Ich habe versucht, die Entity-Objekte mithilfe der unter dem folgenden Link beschriebenen Reflexion tief zu klonen / zu duplizieren , aber nach meinem Verständnis werden von EntityObject abgeleitete Typen von der DbContext-API nicht unterstützt
kypk

Antworten:

129

Eine billige und einfache Möglichkeit, eine Entität zu klonen, besteht darin, Folgendes zu tun:

var originalEntity = Context.MySet.AsNoTracking()
                             .FirstOrDefault(e => e.Id == 1);
Context.MySet.Add(originalEntity);
Context.SaveChanges();

Der Trick hier ist AsNoTracking (). Wenn Sie eine Entität wie diese laden, weiß Ihr Kontext nichts davon und wenn Sie SaveChanges aufrufen, wird sie wie eine neue Entität behandelt.

Wenn Sie MySeteinen Verweis auf haben MyPropertyund auch eine Kopie davon möchten, verwenden Sie einfach Folgendes Include:

var originalEntity = Context.MySet.Include("MyProperty")
                            .AsNoTracking()
                            .FirstOrDefault(e => e.Id == 1);
Löwe
quelle
Dieser Trick hat mich nur einige Zeit gerettet :-). Bei meiner Konfiguration von DbContext gab es jedoch eine Ausnahme, dass die Entität nicht automatisch hinzugefügt werden konnte. Ich musste den ObjectContext so durchgehenDirectCast(DbContext, IObjectContextAdapter).ObjectContext.AddObject(entitySetName, entity)
Patrick
Ich habe eine Projektion aus einem solchen Kontext. dbContext,Select(x=> { a = x, ab = x.MyCollection.Where(g=>g.Id>2)}).ToList()Wenn ich hinzufüge, AsNoTracking()gibt es einen Datenverlust durch die Abfrage.
Eldho
4
Dies hat bei der Verwendung von EF Core hervorragend funktioniert. Ich musste jedoch meine Primärschlüssel für das übergeordnete und die verschachtelten Objekte auf Guid.Empty setzen, um zu verhindern, dass EF versucht, doppelte Zeilen in die Datenbank einzufügen. Wenn Sie Ganzzahlschlüssel verwenden, würde das Setzen auf 0 vermutlich den gleichen Effekt haben.
agileMike
1
Gut zu wissen: Sie können verschachtelte Elemente in den Punktoperator aufnehmen, wie z. B .: .Include("MyProperty.MyNestedObjet")Siehe msdn.microsoft.com/query/…
Malick
1
Muss ich beim Kopieren der verschachtelten Entitäten diese durchlaufen und ihnen alle neuen GuidIDs geben?
Zug
22

Hier ist eine weitere Option.

Ich bevorzuge es in einigen Fällen, weil Sie keine spezielle Abfrage ausführen müssen, um Daten zum Klonen zu erhalten. Mit dieser Methode können Sie Klone von Entitäten erstellen, die Sie bereits aus der Datenbank erhalten haben.

//Get entity to be cloned
var source = Context.ExampleRows.FirstOrDefault();

//Create and add clone object to context before setting its values
var clone = new ExampleRow();
Context.ExampleRows.Add(clone);

//Copy values from source to clone
var sourceValues = Context.Entry(source).CurrentValues;
Context.Entry(clone).CurrentValues.SetValues(sourceValues);

//Change values of the copied entity
clone.ExampleProperty = "New Value";

//Insert clone with changes into database
Context.SaveChanges();

Diese Methode kopiert die aktuellen Werte aus der Quelle in eine neue Zeile, die hinzugefügt wurde.

Jas Laferriere
quelle
2
Dies funktioniert hervorragend, wenn Sie möchten, dass der Klon zusammen mit Aktualisierungen des Originals in einem SaveChanges
Dacker
1
SetValuesfunktioniert nicht, wenn das neue Objekt nicht an den Kontext angehängt ist. Es wird eine InvalidOperationExceptionAusnahme ausgelöst . Wenn Sie die Entität nur in einem getrennten Zustand klonen möchten, können Sie die Entität zum Kontext hinzufügen, ihre aktuellen Werte festlegen und sie dann trennen.
Suncat2000
4
Dies ist kein tiefer Klon. Der Fragentitel und der Text fragen nach einem tiefen Klon, um "die Entität und alle verwandten Objekte zu kopieren".
Zach Mierzejewski
2

Dies ist eine generische Erweiterungsmethode, die das generische Klonen ermöglicht.

Sie müssen System.Linq.Dynamicaus Nuget holen .

    public TEntity Clone<TEntity>(this DbContext context, TEntity entity) where TEntity : class
    {

        var keyName = GetKeyName<TEntity>();
        var keyValue = context.Entry(entity).Property(keyName).CurrentValue;
        var keyType = typeof(TEntity).GetProperty(keyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).PropertyType;

        var dbSet = context.Set<TEntity>();
        var newEntity =  dbSet
            .Where(keyName + " = @0", keyValue)
            .AsNoTracking()
            .Single();

        context.Entry(newEntity).Property(keyName).CurrentValue = keyType.GetDefault();

        context.Add(newEntity);

        return newEntity;
    }

Das einzige, was Sie selbst implementieren müssen, ist die GetKeyName-Methode. Dies kann alles von return typeof(TEntity).Name + "Id"bis sein return the first guid propertyoder die erste mit gekennzeichnete Eigenschaft zurückgeben DatabaseGenerated(DatabaseGeneratedOption.Identity)].

In meinem Fall habe ich meine Klassen bereits mit markiert [DataServiceKeyAttribute("EntityId")]

    private string GetKeyName<TEntity>() where TEntity : class
    {
        return ((DataServiceKeyAttribute)typeof(TEntity)
           .GetCustomAttributes(typeof(DataServiceKeyAttribute), true).First())
           .KeyNames.Single();
    }
Jürgen Steinblock
quelle
1

Ich hatte das gleiche Problem in Entity Framework Core, wo Deep Clone mehrere Schritte umfasst, wenn untergeordnete Entitäten faul geladen werden. Eine Möglichkeit, die gesamte Struktur zu klonen, ist die folgende:

   var clonedItem = Context.Parent.AsNoTracking()
        .Include(u => u.Child1)
        .Include(u => u.Child2)
        // deep includes might go here (see ThenInclude)
        .FirstOrDefault(u => u.ParentId == parentId);

    // remove old id from parent
    clonedItem.ParentId = 0;

    // remove old ids from children
    clonedItem.Parent1.ForEach(x =>
    {
        x.Child1Id = 0;
        x.ParentId= 0;
    });
    clonedItem.Parent2.ForEach(x =>
    {
        x.Child2Id = 0;
        x.ParentId= 0;
    });

    // customize entities before inserting it

    // mark everything for insert
    Context.Parent.Add(clonedItem);

    // save everything in one single transaction
    Context.SaveChanges();

Natürlich gibt es Möglichkeiten, generische Funktionen zu erstellen, um alles eifrig zu laden und / oder Werte für alle Tasten zurückzusetzen, aber dies sollte alle Schritte klar und anpassbar machen (z. B. alle, damit einige Kinder überhaupt nicht geklont werden, indem sie übersprungen werden Einschließen).

Alexei
quelle