ASP.NET Identity DbContext confusion

asp.netasp.net MvcEntity Frameworkasp.net Mvc-5asp.net Identity

asp.net Problem Overview


A default MVC 5 App comes with this piece of code in IdentityModels.cs - this piece of code is for all the ASP.NET Identity operations for the default templates:

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

If I scaffold a new controller using views with Entity Framework and create a "New data context..." in the dialog, I get this generated for me:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace WebApplication1.Models
{
    public class AllTheOtherStuffDbContext : DbContext
    {
        // You can add custom code to this file. Changes will not be overwritten.
        // 
        // If you want Entity Framework to drop and regenerate your database
        // automatically whenever you change your model schema, please use data migrations.
        // For more information refer to the documentation:
        // http://msdn.microsoft.com/en-us/data/jj591621.aspx
    
        public AllTheOtherStuffDbContext() : base("name=AllTheOtherStuffDbContext")
        {
        }

        public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }
    
    }
} 

If I scaffold another controller + view using EF, say for instance for an Animal model, this new line would get autogenerated right under public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; } - like this:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace WebApplication1.Models
{
    public class AllTheOtherStuffDbContext : DbContext
    {
        // You can add custom code to this file. Changes will not be overwritten.
        // 
        // If you want Entity Framework to drop and regenerate your database
        // automatically whenever you change your model schema, please use data migrations.
        // For more information refer to the documentation:
        // http://msdn.microsoft.com/en-us/data/jj591621.aspx

        public AllTheOtherStuffDbContext() : base("name=AllTheOtherStuffDbContext")
        {
        }

        public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }
        public System.Data.Entity.DbSet<WebApplication1.Models.Animal> Animals { get; set; }

    }
} 

ApplicationDbContext (for all the ASP.NET Identity stuff) inherits from IdentityDbContext which in turn inherits from DbContext. AllOtherStuffDbContext (for my own stuff) inherits from DbContext.

So my question is:

Which of these two (ApplicationDbContext and AllOtherStuffDbContext) should I use for all my other own models? Or should I just use the default autogenerated ApplicationDbContext since it shouldn't be a problem using it since it derives from the base class DbContext, or will there be some overhead? You should use only one DbContext object in your app for all your models (I've read this somewhere) so I should not even consider using both ApplicationDbContext and AllOtherStuffDbContext in a single app? Or what is best practice in MVC 5 with ASP.NET Identity?

asp.net Solutions


Solution 1 - asp.net

I would use a single Context class inheriting from IdentityDbContext. This way you can have the context be aware of any relations between your classes and the IdentityUser and Roles of the IdentityDbContext. There is very little overhead in the IdentityDbContext, it is basically a regular DbContext with two DbSets. One for the users and one for the roles.

Solution 2 - asp.net

There is a lot of confusion about IdentityDbContext, a quick search in Stackoverflow and you'll find these questions:
" Why is Asp.Net Identity IdentityDbContext a Black-Box?
How can I change the table names when using Visual Studio 2013 AspNet Identity?
Merge MyDbContext with IdentityDbContext"

To answer to all of these questions we need to understand that IdentityDbContext is just a class inherited from DbContext.
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 if we want to merge IdentityDbContext with our DbContext we have two options:

First Option:
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
}


Extra Notes:

  1. We can also change asp.net Identity default table names with the following solution:

     public class ApplicationDbContext : IdentityDbContext
     {    
         public ApplicationDbContext(): base("DefaultConnection")
         {
         }
     
         protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
         {
             base.OnModelCreating(modelBuilder);
             modelBuilder.Entity<IdentityUser>().ToTable("user");
             modelBuilder.Entity<ApplicationUser>().ToTable("user");
     
             modelBuilder.Entity<IdentityRole>().ToTable("role");
             modelBuilder.Entity<IdentityUserRole>().ToTable("userrole");
             modelBuilder.Entity<IdentityUserClaim>().ToTable("userclaim");
             modelBuilder.Entity<IdentityUserLogin>().ToTable("userlogin");
         }
     }
    
  2. Furthermore we can extend each class and add any property to classes like 'IdentityUser', 'IdentityRole', ...

     public class ApplicationRole : IdentityRole<string, ApplicationUserRole>
    

    { public ApplicationRole() { this.Id = Guid.NewGuid().ToString(); }

     public ApplicationRole(string name)
         : this()
     {
         this.Name = name;
     }
    
     // Add any custom Role properties/code here
    

    }

    // Must be expressed in terms of our custom types: 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
    

    } To save time we can use AspNet Identity 2.0 Extensible Project Template to extend all the classes.

Second Option:(Not recommended)
We actually don't have to inherit from IdentityDbContext if we write all the code ourselves.
So basically we can just inherit from DbContext and implement our customized version of "OnModelCreating(ModelBuilder builder)" from the IdentityDbContext source code

Solution 3 - asp.net

This is a late entry for folks, but below is my implementation. You will also notice I stubbed-out the ability to change the the KEYs default type: the details about which can be found in the following articles:

NOTES:
It should be noted that you cannot use Guid's for your keys. This is because under the hood they are a Struct, and as such, have no unboxing which would allow their conversion from a generic <TKey> parameter.

THE CLASSES LOOK LIKE:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole, string, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
	#region <Constructors>

	public ApplicationDbContext() : base(Settings.ConnectionString.Database.AdministrativeAccess)
	{
	}

	#endregion

	#region <Properties>

	//public DbSet<Case> Case { get; set; }

	#endregion

	#region <Methods>

	#region

	protected override void OnModelCreating(DbModelBuilder modelBuilder)
	{
		base.OnModelCreating(modelBuilder);

		//modelBuilder.Configurations.Add(new ResourceConfiguration());
		//modelBuilder.Configurations.Add(new OperationsToRolesConfiguration());
	}

	#endregion

	#region

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

	#endregion

	#endregion
}

    public class ApplicationUser : IdentityUser<string, CustomUserLogin, CustomUserRole, CustomUserClaim>
    {
    	#region <Constructors>
    
    	public ApplicationUser()
    	{
    		Init();
    	}
    
    	#endregion
    
    	#region <Properties>
    
    	[Required]
    	[StringLength(250)]
    	public string FirstName { get; set; }
    
    	[Required]
    	[StringLength(250)]
    	public string LastName { get; set; }
    
    	#endregion
    
    	#region <Methods>
    
    	#region private
    
    	private void Init()
    	{
    		Id = Guid.Empty.ToString();
    	}
    
    	#endregion
    
    	#region public
    
    	public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
    	{
    		// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
    		var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
    
    		// Add custom user claims here
    
    		return userIdentity;
    	}
    
    	#endregion
    
    	#endregion
    }
    
    public class CustomUserStore : UserStore<ApplicationUser, CustomRole, string, CustomUserLogin, CustomUserRole, CustomUserClaim>
    {
    	#region <Constructors>
    
    	public CustomUserStore(ApplicationDbContext context) : base(context)
    	{
    	}
    
    	#endregion
    }
    
    public class CustomUserRole : IdentityUserRole<string>
    {
    }
    
    public class CustomUserLogin : IdentityUserLogin<string>
    {
    }
    
    public class CustomUserClaim : IdentityUserClaim<string> 
    { 
    }
    
    public class CustomRoleStore : RoleStore<CustomRole, string, CustomUserRole>
    {
    	#region <Constructors>
    
    	public CustomRoleStore(ApplicationDbContext context) : base(context)
    	{
    	} 
    
    	#endregion
    }
    
    public class CustomRole : IdentityRole<string, CustomUserRole>
    {
    	#region <Constructors>
    
    	public CustomRole() { }
    	public CustomRole(string name) 
    	{ 
    		Name = name; 
    	}
    
    	#endregion
    }

Solution 4 - asp.net

If you drill down through the abstractions of the IdentityDbContext you'll find that it looks just like your derived DbContext. The easiest route is Olav's answer, but if you want more control over what's getting created and a little less dependency on the Identity packages have a look at my question and answer here. There's a code example if you follow the link, but in summary you just add the required DbSets to your own DbContext subclass.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPussInBootsView Question on Stackoverflow
Solution 1 - asp.netOlav NybøView Answer on Stackoverflow
Solution 2 - asp.netSaberView Answer on Stackoverflow
Solution 3 - asp.netPrisoner ZEROView Answer on Stackoverflow
Solution 4 - asp.netjoelmdevView Answer on Stackoverflow