5

This is MVC 5/ EF6. So I have the following classes:

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

and

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

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

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

In addition to regular MVC5 membership tables it creates 2 more: Levels and UserLevels (with User_Id and Level_Id columns). Levels table has a static data (i.e. 1 - Excellent, 2 - Good, etc) and is kind of a library, I don't want to insert in this table.

What I'm trying to do is when user registers on the site and chooses the level it would go ahead and retrieve it from DB so that UserLevels table is populated with new UserID and selected LevelID. Here is my code:

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

It throws an exception on this line: An entity object cannot be referenced by multiple instances of IEntityChangeTracker..

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

I'm guessing it has something to do with it trying to insert into Levels table the level that already exists in there? Of course it might be something else... Any advice? Thanks in advance!

Drazen Bjelovuk
  • 5,201
  • 5
  • 37
  • 64
dima
  • 1,181
  • 1
  • 9
  • 20
  • Where does `DBContext` come from? – haim770 Mar 26 '14 at 07:53
  • It's a global var in Account controller `MyDBContext DBContext = new MyDBContext();` whereas the actual context class is this: `public class MyDBContext : IdentityDbContext{ public MyDBContext() : base("DefaultConnection") { } public DbSet Levels {get; set;} }` – dima Mar 26 '14 at 15:05
  • The exception simply means that your `Level` is being tracked by multiple `DbContexts`. The one you're using to fetch the Level from, and the one instantiated by the `UserManager`. How is your `UserManager` configured? – haim770 Mar 26 '14 at 15:25
  • Thank you! I didn't configure it in any way, just whatever came out of the box is what I'm using. Is there a workaround on how to make sure I'm using single DbContext for both? Weird thing is that I'm only using `DBContext` to retrieve the Level from the DB that the user chose, I don't really use it to save the changes to DB or anything, at least not in this controller. – dima Mar 26 '14 at 18:05

1 Answers1

15

If you're using UserManager the way 'it came out of the box' then it's probably instantiated like this:

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

It means that if you don't provide a UserManager instance to the AccountController constructor (and it's probably the case here), a new MyDbContext is created for the UserManager store.

Yet, you have another instance of MyDbContext, as i can infer from this line in your code:

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

All it means is that you have to make your UserManager use the same context:

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;

This way, you're creating the (only) MyDbContext instance first, then passing it to the UserManager constructor (as the IUserStore<User>).

Also, you can definitely make a good use of Dependency Injection here and have the MyDbContext instance injected for you by a DI container and keeping your current code almost unchanged:

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

See Tutorial (for Microsoft Unity).

haim770
  • 48,394
  • 7
  • 105
  • 133