5 votes

l'entité ne peut pas être référencée par plusieurs instances de IEntityChangeTracker

Je reçois cette erreur chaque fois que j'essaie de rattacher un utilisateur à un groupe (ou de créer un groupe).

Voici le GroupController :

public class GroupsController : Controller
{
       private Context db = new Context();

        [HttpPost]
        public ActionResult Create(Group group)
        {
            if (ModelState.IsValid)
            {
                group.owner = db.Users.Attach((User)Session["user"]);
    //current user stored in session
                db.Groups.Add(group);
                db.SaveChanges();

                return RedirectToAction("Index", "Home");
            }
            return View(group);
        }
}

La variable Session["user"] est définie à l'ouverture de session, voici le code du AccountController :

public class AccountController : Controller
{
    private Context db = new Context();

    [HttpPost]
    public ActionResult LogOn(LoginTemplate login, string returnUrl)
    {

            User result = db.Users.Where(m => m.email == login.email).Where(m => m.password == login.password).SingleOrDefault();

            if (result != null)
            {
                Session["logged"] = true;
                Session["user"] = result;
                return RedirectToAction("Index", "Home");
            }
     }
 }

Voici mon contexte :

public class Context : DbContext
{

    public Context() : base("name=myContext")
    {
    }
    private static Context _instance;

    public DbSet<User> Users { get; set; }
    public DbSet<Group> Groups { get; set; }

//I tried to use this method in order to always get the same context but does not work

    public static Context getContext()
    {
        if (_instance == null)
        {
            _instance = new Context();
        }

        return _instance;
    }
}

Voici User.cs

       public class User
{
    [Key]
    public int userId { get; set; }

    [Display(Name="Firstname")]
    public string firstname { get; set; }

    [Display(Name = "Lastname")]
    public string lastname { get; set; }

    public virtual ICollection<Group> membership { get; set;}

}

Voici le fichier group.cs :

        public class Group
{
    [Key]
    public int idGroup { get; set; }
    public string name { get; set; } 
    public User owner { get; set; }
    public virtual ICollection<User> members { get; set; }

}

J'ai essayé de faire un "singleton" dans mon Contexte (pour toujours réutiliser le même contexte dans chaque contrôleur) mais quand je fais cela, j'obtiens le message d'erreur "Db closed"...

2voto

Maarten Points 8692

RÉPONSE INITIALE

Ce que vous décrivez me semble être un comportement attendu.

Que se passe-t-il ?

  1. Lorsque vous vous connectez, une entité utilisateur est chargée et enregistrée dans la session. Mais comme le contexte qui suit l'entité utilisateur a des références à l'entité utilisateur, et plus important encore, l'entité utilisateur a des références au contexte, le contexte n'est pas éliminé, et même si votre contrôleur sera ramassé, le contexte ne le sera pas.

  2. Le contrôleur de groupe est instancié, et il a un contexte séparé. Ensuite, vous créez une entité de groupe, et à ce moment-là, l'entité de groupe n'a pas encore de références au contexte (ou l'inverse). Ainsi, la connexion de l'entité utilisateur à l'entité groupe est correcte. Mais lorsque vous attachez l'entité groupe au nouveau contexte, le contexte commence à suivre cette entité et toutes les autres entités référencées par l'entité groupe. Cela inclut donc l'entité utilisateur. Comme le contexte d'origine suit également l'entité utilisateur, cela entraîne une exception.

Je vois une solution facile, qui consiste à utiliser l'identifiant de l'entité utilisateur au lieu de l'objet lui-même, lorsque vous voulez connecter l'entité utilisateur à l'entité groupe. De cette façon, aucune référence n'est créée entre l'entité groupe et l'entité utilisateur, donc cela devrait fonctionner correctement.

UPDATE

Après une seconde lecture, je vois que vous n'avez pas de propriété user-id dans l'entité groupe, donc ma solution ne fonctionnerait pas.

2voto

Slauma Points 76561

Le problème est causé par le virtual du mot clé des propriétés de navigation de votre entité. EF va alors créer un proxy de chargement paresseux capable de charger les propriétés de navigation à la volée, mais pour cela il doit détenir une référence au contexte. Lorsque vous attachez ensuite l'entité à un autre contexte, vous obtenez cette exception car le proxy est toujours suivi par l'ancien contexte vers lequel il pointe.

Vous pouvez probablement corriger l'erreur en désactivant la création et le suivi du proxy lorsque vous chargez l'utilisateur afin de vous assurer qu'une entité POCO pure (sans proxy) est chargée et ne fait plus référence au contexte :

public class AccountController : Controller
{
    private Context db = new Context();

    public AccountController()
    {
        db.Configuration.ProxyCreationEnabled = false;
    }

    [HttpPost]
    public ActionResult LogOn(LoginTemplate login, string returnUrl)
    {
        User result = db.Users.AsNoTracking()
            .Where(m => m.email == login.email)
            .Where(m => m.password == login.password)
            .SingleOrDefault();

        if (result != null)
        {
            Session["logged"] = true;
            Session["user"] = result;
            return RedirectToAction("Index", "Home");
        }
    }
}

Cela désactivera les proxies et le chargement paresseux pour l'ensemble du contrôleur mais vous pouvez le remplacer par un chargement rapide ( Include ) lorsque vous souhaitez que les propriétés de navigation soient chargées.

Même si cela résout votre problème, ce n'est toujours pas la meilleure pratique à mon avis pour stocker une entité dans l'état de session. Il vaut mieux utiliser un UserForSessionModel (qui n'est pas une entité de la base de données) ou stocker uniquement la classe Id et recharger l'utilisateur depuis la base de données quand vous en avez besoin.

Editar

Vous pouvez également utiliser un "stub entity" dans votre fichier Create pour éviter d'attacher l'utilisateur chargé qui est référencé par un autre contexte :

var userStub = new User { userId = ((User)Session["user"]).userId };
group.owner = db.Users.Attach(userStub);
db.Groups.Add(group);
db.SaveChanges();

La seule chose dont vous avez besoin de l'utilisateur pour établir une relation est sa clé primaire, donc un tel utilisateur vide avec seulement la clé alimentée est suffisant.

0voto

Paul Taylor Points 1654

Il n'est pas nécessaire d'utiliser db.Users.Attach avec l'objet User sauvegardé dans l'état de la session. L'objet que vous voulez sauvegarder est le nouveau groupe, pas l'utilisateur. Je pense que l'exception est levée lorsque vous appelez db.Users.Attach((User)Session["user"]), et cela peut indiquer que l'entité de l'utilisateur actuel est déjà gérée dans le contexte actuel par EF.

0voto

Daniel Dyson Points 9913

J'ai rencontré la même erreur lorsque j'avais une couche de mise en cache qui mettait en cache des entités de recherche et les récupérait à partir du cache. Les entités mises en cache étaient des entités proxy et avaient toujours une référence au DBContext qui les avait initialement récupérées.

Le principal symptôme de ce problème est que la première sauvegarde d'une entité Domaine avec une référence de consultation a fonctionné correctement, mais que les sauvegardes suivantes ont échoué.

J'espère que cela aidera quelqu'un, car ce problème m'a ôté deux jours de ma vie.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X