35

There is a lot of confusion it seems around IdentityDbContext.

If we create two Database Contexts in our application, one for Identity and one for our custom business data, the Identity Database Context inherits from IdentityDbContext while our custom business data inherits from DbContext.

So let's add the following to a controller:

private MyDbContext db = new MyDbContext();
private ApplicationDbContext identityDb = new ApplicationDbContext();

And the following to an Index method in the controller:

var thingsInMyBusinessDb = db.Things.ToList();
var usersInIndentityDb = identityDb.AspNetUsers.ToList(); // THIS WILL HAVE AN ERROR
var roles = identityDb.AspNetRoles.ToList(); // ERROR

You will also notice that the tables in the Indentity Database are not available. Why is this?

Currently as of 2.0.0-beta1 there is a Users and Roles items, but I would have expected the actual tables to be available. And why not? What if I wanted to get to AspNetUserRoles

Sure seems that a lot of confusion and issues with Asp.Net Identity would go away if it were treated like any database context in Entity Framework.

Vy Do
  • 46,709
  • 59
  • 215
  • 313
Sean Newcome
  • 1,497
  • 2
  • 17
  • 24

4 Answers4

32

The ApplicationDbContext's Users and Roles properties are mapped to the AspNetUsers and AspNetRoles tables, and the rest of the entities (Claims, Logins, UserRoles) are mapped automatically via navigation properties. As far as I know, the prefixing of table names with "AspNet" are the only custom mappings in ApplicationDbContext, everything else is just Entity Framework Code First conventions.

If you need direct access to the tables via the ApplicationDbContext, you can do so like this...

using (var context = new ApplicationDbContext())
{
    var users = context.Users.Include(u => u.Claims)
                             .Include(u => u.Logins)
                             .Include(u => u.Roles)
                             .ToList();

    var roles = context.Roles.ToList();
}

You can access a user's roles, claims, and logins via navigation properties on the IdentityUser entity (from the Users DbSet). If you want to query them directly, add them explicitly as DbSets on the context...

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }

    public DbSet<IdentityUserRole> UserRoles { get; set; }
    public DbSet<IdentityUserClaim> Claims { get; set; }
    public DbSet<IdentityUserLogin> Logins { get; set; }

}

And query them like this...

var claims = context.Claims.ToList();
var userRoles = context.UserRoles.ToList();
var logins = context.Logins.ToList();

ASP.NET Identity 2.0 exposes Users and Roles IQueryables on the Manager classes for convenience, but it doesn't provide any added functionality over what was available from the DbContext.

Anthony Chu
  • 37,170
  • 10
  • 81
  • 71
  • I'll take a look at this and see if it will solve my issues. The biggest issue is getting to custom fields on the UserRoles table. I think the missing piece is "public DbSet UserRoles { get; set; } on the ApplicationDbContext. Thanks, I'll mark it as the answer if it works. – Sean Newcome Feb 28 '14 at 22:09
  • Nope. Your first example of getting access to the tables via the ApplicationDbContext is not accurate. Sure "Users" and "Roles" show up, but only some of the fields. If you've created custom fields, they don't show up. And, yes, custom fields by creating an ApplicationUser inherited from IdentityUser or an ApplicationRole inhertied from IdentityRole and using Code-First migrations. – Sean Newcome Feb 28 '14 at 22:15
  • Can you explain how you added the custom fields? If you added fields to `ApplicationUser`, they should show up. If you added them to the user roles, you would've had to create a class (`ApplicationUserRole`) that inherits from `IdentityUserRole`; in which case the it should be `public DbSet UserRoles { get; set; }` and the custom properties should be available. – Anthony Chu Feb 28 '14 at 22:34
  • Like I said, I'll look at it and see if the DbSet UserRoles { get; set; } makes the custom properties available. However, I'm still concerned first about what is going on with Roles. I created custom properties on Roles the same way, i.e., an ApplicationRole inheriting from IdentityRole. The ApplicationDbContext shows Users and Roles, but not the custom properties (fields). Do you suppose I need to do a DbSet Roles { get; set; }. – Sean Newcome Feb 28 '14 at 22:40
  • 1
    I see. Try casting the `IdentityRole` to an `ApplicationRole`. I just tried casting an `IdentityUserRole` to an `ApplicationUserRole` and my custom property was accessible. Although if you're customizing classes beyond the `ApplicationUser`, you might want to wait for ASP.NET Identity 2.0. It has a new `IdentityDbContext` base class that lets you specify all your custom entities. – Anthony Chu Feb 28 '14 at 23:14
  • I did cast the IdentityRole to an ApplicationRole. I can get to the custom fields through RoleManager. I just don't understand why the Db Context doesn't show custom fields on Users and Roles. – Sean Newcome Feb 28 '14 at 23:16
  • Okay, so ASP.NET Identity 2.0 will expose any custom fields we create on the tables? I have 2.0.0-beta1 but it doesn't seem to do all that yet. I see Roles was added to the Db Context but still doesn't expose custom fields. – Sean Newcome Feb 28 '14 at 23:21
  • In 2.0, try creating your DbContext like this... `public class ApplicationDbContext : IdentityDbContext` (`int` (or `string`) is the type of the User's primary key). – Anthony Chu Feb 28 '14 at 23:35
  • Yes, this is working for 2.0.0.0-beta1. I guess I will have to wait for 2.0. Thanks very much @Anthony Chu. – Sean Newcome Mar 04 '14 at 22:04
9

There's a fundamental misunderstanding here about how DbContext works. The property names of your DbSets in your context do not correspond to table names. If anything, the table name is based on the class name of the actual entity, but even that can be overridden. A perfect example is of course your user class, which is by default ApplicationUser, but will reside in a table called AspNetUsers.

All the DbSet properties in your context determine is the API you use to access data via Entity Framework. IdentityDbContext implements DbSet properties name Users, Roles, etc. So that is how you access that data, not via the table name (i.e. context.Users).

Further, if you're unhappy with having two contexts, you don't have to keep them as two. Just make your main context inherit from IdentityDbContext<ApplicationUser> instead of DbContext and kill the scaffolded version.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    The code examples showing two contexts were just an example of the differences between the two. I personally like keeping them separate. Not unhappy about it at all. ;) – Sean Newcome Feb 28 '14 at 22:26
  • Yes, var usersInIndentityDb = identityDb.Users.ToList() gets you access to the records, but it does not make custom fields available. – Sean Newcome Feb 28 '14 at 23:19
2

There is certainly lots of confusion around IdentityDbContext, a quick search around SO and you will find lots of questions about this topic.
ASP.NET Identity DbContext confusion
How can I change the table names when using Visual Studio 2013 AspNet Identity?
Merge MyDbContext with IdentityDbContext

The answer to all of these questions we need to first understand how IdentityDbContext works. To clarify things, we should take into consideration that IdentityDbContext is just a class inherited from DbContext and not a black box!
Let's take a look at IdentityDbContext source:

/// <summary>
/// Base class for the Entity Framework database context used for identity.
/// </summary>
/// <typeparam name="TUser">The type of user objects.</typeparam>
/// <typeparam name="TRole">The type of role objects.</typeparam>
/// <typeparam name="TKey">The type of the primary key for users and roles.</typeparam>
/// <typeparam name="TUserClaim">The type of the user claim object.</typeparam>
/// <typeparam name="TUserRole">The type of the user role object.</typeparam>
/// <typeparam name="TUserLogin">The type of the user login object.</typeparam>
/// <typeparam name="TRoleClaim">The type of the role claim object.</typeparam>
/// <typeparam name="TUserToken">The type of the user token object.</typeparam>
public abstract class IdentityDbContext<TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken> : DbContext
    where TUser : IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin>
    where TRole : IdentityRole<TKey, TUserRole, TRoleClaim>
    where TKey : IEquatable<TKey>
    where TUserClaim : IdentityUserClaim<TKey>
    where TUserRole : IdentityUserRole<TKey>
    where TUserLogin : IdentityUserLogin<TKey>
    where TRoleClaim : IdentityRoleClaim<TKey>
    where TUserToken : IdentityUserToken<TKey>
{
    /// <summary>
    /// Initializes a new instance of <see cref="IdentityDbContext"/>.
    /// </summary>
    /// <param name="options">The options to be used by a <see cref="DbContext"/>.</param>
    public IdentityDbContext(DbContextOptions options) : base(options)
    { }

    /// <summary>
    /// Initializes a new instance of the <see cref="IdentityDbContext" /> class.
    /// </summary>
    protected IdentityDbContext()
    { }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of Users.
    /// </summary>
    public DbSet<TUser> Users { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User claims.
    /// </summary>
    public DbSet<TUserClaim> UserClaims { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User logins.
    /// </summary>
    public DbSet<TUserLogin> UserLogins { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User roles.
    /// </summary>
    public DbSet<TUserRole> UserRoles { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User tokens.
    /// </summary>
    public DbSet<TUserToken> UserTokens { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of roles.
    /// </summary>
    public DbSet<TRole> Roles { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of role claims.
    /// </summary>
    public DbSet<TRoleClaim> RoleClaims { get; set; }

    /// <summary>
    /// Configures the schema needed for the identity framework.
    /// </summary>
    /// <param name="builder">
    /// The builder being used to construct the model for this context.
    /// </param>
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<TUser>(b =>
        {
            b.HasKey(u => u.Id);
            b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
            b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");
            b.ToTable("AspNetUsers");
            b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();

            b.Property(u => u.UserName).HasMaxLength(256);
            b.Property(u => u.NormalizedUserName).HasMaxLength(256);
            b.Property(u => u.Email).HasMaxLength(256);
            b.Property(u => u.NormalizedEmail).HasMaxLength(256);
            b.HasMany(u => u.Claims).WithOne().HasForeignKey(uc => uc.UserId).IsRequired();
            b.HasMany(u => u.Logins).WithOne().HasForeignKey(ul => ul.UserId).IsRequired();
            b.HasMany(u => u.Roles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
        });

        builder.Entity<TRole>(b =>
        {
            b.HasKey(r => r.Id);
            b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex");
            b.ToTable("AspNetRoles");
            b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();

            b.Property(u => u.Name).HasMaxLength(256);
            b.Property(u => u.NormalizedName).HasMaxLength(256);

            b.HasMany(r => r.Users).WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
            b.HasMany(r => r.Claims).WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
        });

        builder.Entity<TUserClaim>(b => 
        {
            b.HasKey(uc => uc.Id);
            b.ToTable("AspNetUserClaims");
        });

        builder.Entity<TRoleClaim>(b => 
        {
            b.HasKey(rc => rc.Id);
            b.ToTable("AspNetRoleClaims");
        });

        builder.Entity<TUserRole>(b => 
        {
            b.HasKey(r => new { r.UserId, r.RoleId });
            b.ToTable("AspNetUserRoles");
        });

        builder.Entity<TUserLogin>(b =>
        {
            b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
            b.ToTable("AspNetUserLogins");
        });

        builder.Entity<TUserToken>(b => 
        {
            b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name });
            b.ToTable("AspNetUserTokens");
        });
    }
}

Based on the source code all you have to do is create a DbContext which inherits from IdentityDbContext and have access to the classes.

public class ApplicationDbContext 
    : IdentityDbContext
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }

    static ApplicationDbContext()
    {
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    // Add additional items here as needed
}

If you want to further extend the classes have a look at AspNet Identity 2.0 Extensible Project Template

Marcel Gosselin
  • 4,610
  • 2
  • 31
  • 54
Saber
  • 5,150
  • 4
  • 31
  • 43
1

Even though the identity tables in database gets named with aspnet prefix you can always change them. But not always the table name in database will not be the ones you'll see when accessing from DbContext. You'll need to work with names that gets generated by the framework. But this can be changed too. See Identity Data Model with the Entity Framework Fluent.

Community
  • 1
  • 1
Nipuna
  • 6,846
  • 9
  • 64
  • 87