每当我尝试将用户附加到组(或创建组)时,我都会收到此错误。
这是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”错误信息......
答案 0 :(得分:2)
你所描述的对我来说似乎是预期的行为。
会发生什么?
当您登录时,会加载用户实体并保存在会话中。但是,由于跟踪用户实体的上下文具有对用户实体的引用,更重要的是,用户实体具有对上下文的引用,因此不会丢弃上下文,即使您的控制器将被垃圾收集,上下文也是不
组控制器是实例化的,它有一个单独的上下文。然后,您创建一个组实体,此时组实体尚未引用上下文(或相反)。因此,将用户实体连接到组实体是可以的。但是,当您将组实体附加到新上下文时,上下文将开始跟踪此实体以及组实体引用的所有其他实体。这包括用户实体。由于原始上下文也跟踪用户实体,因此会导致异常。
我可以看到一个简单的解决方案,那就是当您想要将用户实体连接到组实体时,使用用户实体的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实体的第一次保存工作正常,但后续保存失败。
我希望这对某人有所帮助,因为问题花了我两天的时间。