web-dev-qa-db-fra.com

Entity Framework 6: objet clone sauf ID

Dans mon programme MVVM, j'ai une classe Model (disons MyModel) à partir de laquelle j'ai une instance de lecture de la base de données (à l'aide de Entity Framework). Lors de la récupération de l'objet, je présente toutes les données à l'utilisateur. Plus tard, l'utilisateur modifiera certains champs.
Ce que je veux, c'est créer le même objet, à l'exception de ID (puisque ID est le clé primaire et incrémenté automatiquement)).
Alors, comment pourrais-je aborder cela? Je ne veux pas copier tous les champs un par un, ce n'est pas une approche robuste. Parce que peut-être que le modèle sera peut-être modifié à l'avenir, il faudra donc que cela soit pris en compte dans la méthode de clonage.

Existe-t-il donc un moyen élégant de copier l'objet et lors de l'enregistrement dans la base de données, son ID est auto incrémenté à nouveau? (Définir l'ID sur null me donne une erreur de compilation, car il s'agit du type int).

46
QuantumHive

J'ai remarqué qu'il n'y avait pas besoin de copier. Apparemment, lors de l'ajout d'une instance d'un modèle à la base de données (même si l'ID est déjà défini dans la base de données), Entity Framework insère une nouvelle ligne dans la base de données et incrémente automatiquement sa clé primaire. Donc, cette fonctionnalité est déjà intégrée à EF. Je ne savais pas ça, désolé.
Par souci de clarté, voici un exemple:

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 a suggéré d'utiliser .AsNoTracking () pour effectuer le clonage dans EF6. J'utilise cette méthode et peux confirmer que cela fonctionne. Vous pouvez même inclure des objets enfants.

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

entity.SomeProperty = DateTime.Now;

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

Lorsque vous récupérez une ou plusieurs entités d'un ensemble de données, vous pouvez indiquer à Entity Framework de ne suivre aucune des modifications apportées à cet objet, puis d'ajouter cette entité en tant que nouvelle entité à l'ensemble de données. Avec .AsNoTracking, le contexte ne sait rien de l’entité existante.

38
jaycer

Lorsque vous utilisez ObjectContext, la réponse fournie par QuantumHive ne fonctionne pas.

L'erreur renvoyée dans cette situation est la suivante:

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)

Pour cloner correctement un objet cadre d'entité (au moins dans EF6.0), procédez comme suit:

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

J'ai trouvé ceci en cherchant s'il y avait un meilleur moyen de cloner un objet que celui que j'utilisais actuellement et j'ai remarqué qu'il y avait un problème potentiel avec la réponse acceptée si vous essayez de créer plusieurs clones ... du moins si vous voulez. évitez de créer votre contexte plusieurs fois ...

Je ne sais pas si c'est la meilleure approche du clonage, c'est pourquoi je cherchais un autre moyen. Mais ça marche. Si vous devez cloner une entité plusieurs fois, vous pouvez utiliser la sérialisation JSON pour cloner ... quelque chose comme ceci (en utilisant 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

J'utilise la base de données postgres:

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

Aucune des méthodes mentionnées ne fonctionne dans mon cas. J'utilise ensuite:

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