IEntityChangeTracker的多个实例不能引用实体

时间:2012-11-18 16:12:15

标签: c# asp.net entity-framework asp.net-mvc-4

每当我尝试将用户附加到组(或创建组)时,我都会收到此错误。

这是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);
        }
}

变量Session [“user”]设置为logon,这是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");
            }
     }
 }

这是我的背景:

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;
    }
}

这是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;}

}

这是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; }

}

我试图在我的Context中做一个“singleton”(总是在每个控制器中重用相同的上下文)但是当我这样做时,我得到“Db closed”错误信息......

4 个答案:

答案 0 :(得分:2)

初步答案

你所描述的对我来说似乎是预期的行为。

会发生什么?

  1. 当您登录时,会加载用户实体并保存在会话中。但是,由于跟踪用户实体的上下文具有对用户实体的引用,更重要的是,用户实体具有对上下文的引用,因此不会丢弃上下文,即使您的控制器将被垃圾收集,上下文也是不

  2. 组控制器是实例化的,它有一个单独的上下文。然后,您创建一个组实体,此时组实体尚未引用上下文(或相反)。因此,将用户实体连接到组实体是可以的。但是,当您将组实体附加到新上下文时,上下文将开始跟踪此实体以及组实体引用的所有其他实体。这包括用户实体。由于原始上下文也跟踪用户实体,因此会导致异常。

  3. 我可以看到一个简单的解决方案,那就是当您想要将用户实体连接到组实体时,使用用户实体的id而不是对象本身。这样,组实体和用户实体之间就不会创建任何引用,因此应该可以正常工作。

    更新

    第二次阅读时,我发现您在组实体中没有user-id属性,因此我的解决方案无效。

答案 1 :(得分:2)

问题是由实体导航属性的virtual关键字引起的。 EF将创建一个延迟加载代理,然后能够动态加载导航属性,但要做到这一点,它需要保持对上下文的引用。当您稍后将实体附加到另一个上下文时,您会收到此异常,因为代理仍然被它指向的旧上下文跟踪。

您可以通过在加载用户时禁用代理创建和跟踪来修复错误,以确保加载的纯(非代理)POCO实体不再引用上下文:

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");
        }
    }
}

它将禁用整个控制器的代理和延迟加载,但是当您需要加载导航属性时,可以用急切加载(Include)替换它。

即使这样可以解决您的问题,但我认为将实体存储在会话状态仍然不是最佳做法。更好地使用专门的UserForSessionModel类(不是数据库实体)或仅存储Id并在需要时从数据库重新加载用户。

修改

您也可以在Create操作中使用“存根实体”,以避免附加其他上下文引用的已加载用户:

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

用户建立关系时唯一需要的是它的主键,因此仅填充键的空用户就足够了。

答案 2 :(得分:0)

不需要在会话状态下保存用户对象的db.Users.Attach。要保存的对象是新组,而不是用户。我的猜测是,当你调用db.Users.Attach((User)Session [&#34; user&#34;])时会抛出异常,这可能表明当前用户的实体已经在EF的当前背景。

答案 3 :(得分:0)

当我有一个缓存查找实体并从缓存中检索它们的缓存层时,我遇到了同样的错误。缓存的实体是代理实体,并且仍然引用了最初检索它们的DBContext。

这样做的主要症状是带有查找引用的Domain实体的第一次保存工作正常,但后续保存失败。

我希望这对某人有所帮助,因为问题花了我两天的时间。