IdentityDbContext
周围似乎有很多混乱。
如果我们在应用程序中创建两个数据库上下文,一个用于标识,一个用于自定义业务数据,则身份数据库上下文继承自IdentityDbContext
,而自定义业务数据继承自DbContext
。
因此,让我们将以下内容添加到控制器中:
private MyDbContext db = new MyDbContext();
private ApplicationDbContext identityDb = new ApplicationDbContext();
以及控制器中的索引方法的以下内容:
var thingsInMyBusinessDb = db.Things.ToList();
var usersInIndentityDb = identityDb.AspNetUsers.ToList(); // THIS WILL HAVE AN ERROR
var roles = identityDb.AspNetRoles.ToList(); // ERROR
您还将注意到,标识数据库中的表不可用。这是为什么呢?
目前从 2.0.0-beta1 开始,有一个用户和角色项目,但我本来希望实际的表格可用。 为什么不呢?如果我想访问 AspNetUserRoles 怎么办
当然,如果像实体框架中的任何数据库上下文一样对待 Asp.Net 标识,那么标识的许多混乱和问题似乎都会消失。
ApplicationDbContext
的Users
和Roles
属性映射到AspNetUsers
表和AspNetRoles
表,其余实体(Claims
、Logins
、UserRoles
)通过导航属性自动映射。据我所知,表名的前缀"AspNet"是ApplicationDbContext
中唯一的自定义映射,其他一切都只是实体框架代码优先约定。
如果您需要通过ApplicationDbContext
直接访问表,您可以这样做...
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();
}
可以通过IdentityUser
实体上的导航属性(从Users
DbSet
)访问用户的角色、声明和登录名。如果要直接查询它们,请将它们显式添加为上下文中的DbSet
...
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; }
}
并像这样查询它们...
var claims = context.Claims.ToList();
var userRoles = context.UserRoles.ToList();
var logins = context.Logins.ToList();
ASP.NET Identity 2.0 为方便起见,在管理器类上公开Users
和Roles
IQueryable
,但它不提供比 DbContext 中可用的任何附加功能。
DbContext
如何工作存在根本性的误解。上下文中DbSet
的属性名称与表名称不对应。如果有的话,表名基于实际实体的类名,但即使这样也可以覆盖。一个完美的例子当然是你的用户类,默认情况下它是ApplicationUser
,但将驻留在一个名为 AspNetUsers
的表中。
上下文中确定的所有DbSet
属性都是用于通过实体框架访问数据的 API。 IdentityDbContext
实现DbSet
属性名称Users
、Roles
等。因此,这就是您访问该数据的方式,而不是通过表名(即 context.Users
)。
此外,如果您对拥有两个上下文不满意,则不必将它们保留为两个上下文。只需让你的主上下文继承自IdentityDbContext<ApplicationUser>
而不是DbContext
并杀死脚手架版本。
IdentityDbContext肯定有很多困惑,快速搜索SO,你会发现很多关于这个主题的问题。
ASP.NET 身份数据库上下文混淆
如何在使用 Visual Studio 2013 AspNet Identity 时更改表名?
将 MyDbContext 与 IdentityDbContext 合并
要回答所有这些问题,我们需要首先了解IdentityDbContext是如何工作的。为了澄清事情,我们应该考虑到 IdentityDbContext 只是一个从 DbContext 继承的类,而不是一个黑盒!
让我们来看看 IdentityDbContext 源代码:
/// <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");
});
}
}
基于源代码,您所要做的就是创建一个继承自 IdentityDbContext 并有权访问类的 DbContext。
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
}
如果要进一步扩展类,请查看 AspNet Identity 2.0 可扩展项目模板
即使数据库中的标识表以前缀命名aspnet
您也可以随时更改它们。但并非总是数据库中的表名不是您在从 DbContext
访问时看到的表名。需要使用框架生成的名称。但这也可以改变。请参阅使用实体框架流畅的标识数据模型。