【问题标题】:Entity Framework 6 and Asp.Net Identity UserManager: Multiple DbContextEntity Framework 6 和 Asp.Net Identity UserManager:多个 DbContext
【发布时间】:2014-05-04 11:10:48
【问题描述】:

这是 MVC 5/EF6。所以我有以下课程:

public class User : IdentityUser
{
    public User()
    {
        Levels = new List<Level>();
    }

    [Required, MaxLength(200)]
    public string FirstName { get; set; }

    [Required, MaxLength(200)]
    public string LastName { get; set; }

    public virtual ICollection<Level> Levels { get; set; }
}

public class Level
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    public virtual ICollection<User> Users { get; set; }
}

除了常规的 MVC5 成员表之外,它还创建了 2 个:LevelsUserLevels(带有 User_Id 和 Level_Id 列)。 Levels 表有一个静态数据(即 1 - 优秀,2 - 好等)并且是一种库,我不想在这个表中插入。

我正在尝试做的是当用户在网站上注册并选择它将继续并从数据库中检索它的级别时,以便UserLevels 表填充新的UserID 并选择LevelID。这是我的代码:

Level level = DBContext.Levels.Where(s => s.Name == model.Level.Name).SingleOrDefault();

if (level == null)
    ModelState.AddModelError("", "Invalid Level.");

if (ModelState.IsValid)
{
    var user = new User() { 
        UserName = model.UserName,
        FirstName = model.FirstName,
        LastName = model.LastName
    };

    user.Levels.Add(level);

    var result = await UserManager.CreateAsync(user, model.Password);
    if (result.Succeeded)
    {
        await SignInAsync(user, isPersistent: false);
        return RedirectToAction("Index", "Home");
    }
    else
    {
        AddErrors(result);
    }
}
return View(model);

它在这一行抛出异常:An entity object cannot be referenced by multiple instances of IEntityChangeTracker..

var result = await UserManager.CreateAsync(user, model.Password);

我猜这与试图将其中已经存在的关卡插入 Levels 表有关吗?当然它可能是别的东西......有什么建议吗?提前致谢!

【问题讨论】:

  • DBContext 来自哪里?
  • 这是帐户控制器 MyDBContext DBContext = new MyDBContext(); 中的全局变量,而实际的上下文类是这样的:public class MyDBContext : IdentityDbContext&lt;User&gt;{ public MyDBContext() : base("DefaultConnection") { } public DbSet&lt;Level&gt; Levels {get; set;} }
  • 这个异常仅仅意味着你的Level被多个DbContexts跟踪。您用来从中获取关卡的那个,以及由UserManager 实例化的那个。您的UserManager 是如何配置的?
  • 谢谢!我没有以任何方式配置它,开箱即用的东西就是我正在使用的东西。是否有解决方法来确保我对两者都使用单个 DbContext?奇怪的是我只使用DBContext 从用户选择的数据库中检索关卡,我并没有真正使用它来保存对数据库或任何东西的更改,至少不在这个控制器中。

标签: c# asp.net-mvc-5 entity-framework-6 asp.net-identity-2


【解决方案1】:

如果您以“开箱即用”的方式使用UserManager,那么它可能会像这样实例化:

public AccountController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new MyDbContext())))
{
}

public AccountController(UserManager<ApplicationUser> userManager)
{
    UserManager = userManager;
}

public UserManager<ApplicationUser> UserManager { get; private set; }

这意味着如果您不向AccountController 构造函数提供UserManager 实例(这里可能就是这种情况),则会为UserManager 存储创建一个新的MyDbContext

然而,你有另一个 MyDbContext 实例,我可以从你的代码中的这一行推断:

Level level = DBContext.Levels.Where(s => s.Name == model.Level.Name).SingleOrDefault();

这意味着你必须让你的UserManager 使用相同的上下文:

public AccountController() : this(null)
{
}

public AccountController(UserManager<User> userManager = null)
{
    DBContext = new MyDBContext();

    if (userManager == null)
        userManager = new UserManager<User>(new UserStore<User>(DBContext));

    UserManager = userManager;
}

public UserManager<User> UserManager { get; private set; }
public MyDBContext DBContext;

这样,您首先创建(唯一的)MyDbContext 实例,然后将其传递给UserManager 构造函数(作为IUserStore&lt;User&gt;)。

此外,您绝对可以在此处充分利用依赖注入,并通过 DI 容器为您注入 MyDbContext 实例并保持当前代码几乎不变:

public AccountController(MyDBContext context)
: this(new UserManager<User>(new UserStore<User>(context)))
{
    this.DBContext = context;
}

请参阅Tutorial(适用于 Microsoft Unity)。

【讨论】:

  • 这太完美了!非常感谢!