web-dev-qa-db-fra.com

Framework d'entité, problèmes de mise à jour des objets associés

Je travaille actuellement sur un projet utilisant la dernière version d'Entity Framework et j'ai rencontré un problème que je n'arrive pas à résoudre.

Quand il s'agit de mettre à jour des objets existants, je peux assez facilement mettre à jour les propriétés de l'objet ok, jusqu'à ce qu'il s'agisse d'une propriété qui est une référence à une autre classe.

Dans l'exemple ci-dessous, j'ai une classe appelée Foo, qui stocke diverses propriétés, dont 2 sont des instances d'autres classes

public class Foo
{
     public int Id {get; set;}
     public string Name {get; set;}
     public SubFoo SubFoo {get; set}
     public AnotherSubFoo AnotherSubFoo {get; set}
}

Lorsque j'utilise la méthode Edit() ci-dessous, je passe l'objet que je souhaite mettre à jour et je parviens à obtenir la mise à jour de Name, mais je n'ai pas réussi à trouver un moyen de pour obtenir les propriétés du SubFoo à modifier. Par exemple, si la classe SubFoo a une propriété Name, et que celle-ci a été modifiée et est différente entre ma base de données et newFoo, elle n'est pas mise à jour.

public Foo Edit(Foo newFoo)
{
    var dbFoo = context.Foo
        .Include(x => x.SubFoo)
        .Include(x => x.AnotherSubFoo)
        .Single(c => c.Id == newFoo.Id);

    var entry = context.Entry<Foo>(dbFoo);
    entry.OriginalValues.SetValues(dbFoo);
    entry.CurrentValues.SetValues(newFoo);

    context.SaveChanges();

    return newFoo;
}

Toute aide ou pointeur serait grandement apprécié.

MISE À JOUR: Sur la base du commentaire de Slauma, j'ai modifié ma méthode pour

public Foo Edit(Foo newFoo)
{
    var dbFoo = context.Foo
        .Include(x => x.SubFoo)
        .Include(x => x.AnotherSubFoo)
        .Single(c => c.Id == newFoo.Id);

    context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
    context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo);

    context.SaveChanges();

    return newFoo;
}

En exécutant cela maintenant, j'obtiens l'erreur:

Le type d'entité Collection`1 ne fait pas partie du modèle pour le contexte actuel.

Pour essayer de contourner cela, j'ai ajouté du code pour essayer d'attacher les sous-classes newFoo au contexte, mais cela par le biais d'une erreur disant que ObjectManager avait déjà une entité identique:

Un objet avec la même clé existe déjà dans ObjectStateManager. ObjectStateManager ne peut pas suivre plusieurs objets avec la même clé

57
Thewads

CurrentValues.SetValues ne met à jour que les propriétés scalaires mais aucune entité liée, vous devez donc faire de même pour chaque entité liée:

public Foo Edit(Foo newFoo)
{
    var dbFoo = context.Foo
                       .Include(x => x.SubFoo)
                       .Include(x => x.AnotherSubFoo)
                       .Single(c => c.Id == newFoo.Id);

    context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
    context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo);
    context.Entry(dbFoo.AnotherSubFoo).CurrentValues.SetValues(newFoo.AnotherSubFoo);

    context.SaveChanges();

    return newFoo;
}

Si la relation aurait pu être supprimée ou créée, vous devez également gérer ces cas de manière explicite:

public Foo Edit(Foo newFoo)
{
    var dbFoo = context.Foo
                       .Include(x => x.SubFoo)
                       .Include(x => x.AnotherSubFoo)
                       .Single(c => c.Id == newFoo.Id);

    context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
    if (dbFoo.SubFoo != null)
    {
        if (newFoo.SubFoo != null)
        {
            if (dbFoo.SubFoo.Id == newFoo.SubFoo.Id)
                // no relationship change, only scalar prop.
                context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo);
            else
            {
                // Relationship change
                // Attach assumes that newFoo.SubFoo is an existing entity
                context.SubFoos.Attach(newFoo.SubFoo);
                dbFoo.SubFoo = newFoo.SubFoo;
            }
        }
        else // relationship has been removed
            dbFoo.SubFoo = null;
    }
    else
    {
        if (newFoo.SubFoo != null) // relationship has been added
        {
            // Attach assumes that newFoo.SubFoo is an existing entity
            context.SubFoos.Attach(newFoo.SubFoo);
            dbFoo.SubFoo = newFoo.SubFoo;
        }
        // else -> old and new SubFoo is null -> nothing to do
    }

    // the same logic for AnotherSubFoo ...

    context.SaveChanges();

    return newFoo;
}

Vous devez également éventuellement définir l'état des entités attachées sur Modified si la relation a été modifiée et les propriétés scalaires également.

Modifier

Si - selon votre commentaire - Foo.SubFoo est en fait une collection et pas seulement une référence, vous aurez besoin de quelque chose comme ça pour mettre à jour les entités liées:

public Foo Edit(Foo newFoo)
{
    var dbFoo = context.Foo
                       .Include(x => x.SubFoo)
                       .Include(x => x.AnotherSubFoo)
                       .Single(c => c.Id == newFoo.Id);

    // Update foo (works only for scalar properties)
    context.Entry(dbFoo).CurrentValues.SetValues(newFoo);

    // Delete subFoos from database that are not in the newFoo.SubFoo collection
    foreach (var dbSubFoo in dbFoo.SubFoo.ToList())
        if (!newFoo.SubFoo.Any(s => s.Id == dbSubFoo.Id))
            context.SubFoos.Remove(dbSubFoo);

    foreach (var newSubFoo in newFoo.SubFoo)
    {
        var dbSubFoo = dbFoo.SubFoo.SingleOrDefault(s => s.Id == newSubFoo.Id);
        if (dbSubFoo != null)
            // Update subFoos that are in the newFoo.SubFoo collection
            context.Entry(dbSubFoo).CurrentValues.SetValues(newSubFoo);
        else
            // Insert subFoos into the database that are not
            // in the dbFoo.subFoo collection
            dbFoo.SubFoo.Add(newSubFoo);
    }

    // and the same for AnotherSubFoo...

    db.SaveChanges();

    return newFoo;
}
92
Slauma

Je pensais simplement publier le lien ci-dessous car cela m'a vraiment aidé à comprendre comment mettre à jour les entités liées.

Mise à jour des données associées avec le framework d'entité dans une application asp net mvc

REMARQUE: j'ai modifié légèrement la logique affichée dans la fonction UpdateInstructorCourses pour répondre à mes besoins.

3
craigvl

Vous pouvez également appeler les tables indépendamment

MyContext db = new MyContext
// I like using asynchronous calls in my API methods 
var OldFoo = await db.Foo.FindAsync(id);
var OldAssociateFoo = db.AssociatedFoo;  
var NewFoo = OldFoo;
var NewAssociatedFoo = OldAssociatedFoo;

NewFoo.SomeValue = "The Value";
NewAssociatedFoo.OtherValue = 20;

db.Entry(OldFoo).CurrentValues.SetValues(NewFoo);
db.Entry(OldAssociatedFoo).CurrentValues.SetValues(NewAssociatedFoo);

await db.SaveChangesAsync();
0
iuppiter