Krzysztof Bociurko
Krzysztof Bociurko

Reputation: 4661

Cannot persist relations in Core Identity's ApplicationUser

I cannot find a way to properly extend ASP.NET Core's IdentityUser (after extending: ApplicationUser). I can override it and link it from other models, but cannot link from the user to other models. Simple data like CustomTag works.

Source code of the issue: https://github.com/chanibal/CoreIdentityIssue
Or, to be specific, it's second commit that contains all the changes over the default template

What I did:

  1. Created new project:
    ASP.NET Core Web Application (Model-View-Controller)
    Authentication: Individual User Accounts, store user accounts in-app
    Updated the database
  2. Changed the model overriding the IdentityUser (as in official docs):

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
            {}
    
        public DbSet<Bar> Bars { get; set; }
        public DbSet<Foo> Foos { get; set; }
        public DbSet<ApplicationUser> ApplicationUsers { get; set; } //< doesn't help
    }
    
    public class ApplicationUser : IdentityUser
    {
        public string CustomTag { get; set; }   //< this works
        public Bar Bar { get; set; }            //< THIS DOES NOT WORK
    }
    
    public class Bar
    {
        public int Id { get; set; }
        public int Value { get; set; }
    }
    
    public class Foo
    {
        public int Id { get; set; }
        public ApplicationUser User { get; set; }   //< this works
    }
    

    ER diagram

    And migrated the changes

  3. I'm updating the Bar values this way:

    /// This should ensure a Bar is connected to a ApplicationUser
    /// and increment it's value
    [Authorize]
    public async Task<IActionResult> IncrementBar()
    {
        // This DOES NOT work
        var user = await _userManager.GetUserAsync(HttpContext.User);
        if (user.Bar == null)   //< this is always null
        {
            user.Bar = new Bar() { Value = 0 };
            // _context.Add(user.Bar); //< doesn't help
        }
        user.Bar.Value++;
        // await _userManager.UpdateAsync(user); //<> doesn't help
        // await _signInManager.RefreshSignInAsync(user);  //< doesn't help, starting to get desperate
        await _context.SaveChangesAsync();

        return RedirectToAction(nameof(Index));
    }

The information is there in the database, accessible by SQL. But somehow it doesn't hydrate the ApplicationUser model:

SQL dump

Using Visual Studio 16.3.9

Upvotes: 4

Views: 190

Answers (1)

Chris Pratt
Chris Pratt

Reputation: 239440

EF does not ever automatically load related entities. You must either eagerly or explicitly load the relationships. Eager loading is the preferred way, as it does joins to get all the data in a single query. However, UserManager<TUser> provides no way to eagerly load relationships. As such, you have two choices:

  1. Explicitly load the relationship. This will require an extra query, though.

    var user = await _userManager.GetUserAsync(HttpContext.User);
    await _context.Entry(user).Reference(x => x.Bar).LoadAsync();
    // note: for collection props, you'd use `Collection(x => x.CollectionProp).LoadAsync()` instead.
    
  2. Query the user from the context by the user id, instead of using UserManager<TUser>:

    var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
    var user = await _context.Users.Include(x => x.Bar).SingleOrDefaultAsync(x => x.Id == userId); 
    

Upvotes: 2

Related Questions