为什么Asp.Net Identity IdentityDbContext是一个黑盒子?

35

似乎有很多关于 IdentityDbContext 的混淆。

如果我们在应用程序中创建了两个数据库上下文,一个用于身份验证,另一个用于自定义业务数据,则身份验证数据库上下文继承自 IdentityDbContext,而自定义业务数据则继承自 DbContext

因此,让我们将以下内容添加到控制器中:

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

将以下内容添加到控制器中的Index方法:

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 Identity像Entity Framework中的任何数据库上下文一样受到处理,似乎许多混淆和问题都可以解决。

4个回答

32

ApplicationDbContext 中的 UsersRoles 属性被映射到 AspNetUsersAspNetRoles 表格,其余实体(ClaimsLoginsUserRoles)通过导航属性自动映射。据我所知,“AspNet”作为表格名称前缀是在 ApplicationDbContext 中唯一的自定义映射,其他所有内容均遵循 Entity Framework Code First 约定。

如果您需要通过 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实体上的导航属性(来自UsersDbSet)来访问用户的角色、声明和登录。如果您想直接查询它们,请将它们显式添加为上下文中的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 将 UsersRolesIQueryable 针对 Manager 类暴露出来,以方便使用,但它并没有提供比 DbContext 更多的功能。


我会查看这个并看看它是否能解决我的问题。最大的问题是访问UserRoles表上的自定义字段。我认为缺失的部分是ApplicationDbContext上的 "public DbSet<IdentityUserRole> UserRoles { get; set; }"。谢谢,如果它有效,我会将其标记为答案。 - Sean Newcome
就像我说的,我会看一下DbSet<ApplicationUserRole> UserRoles { get; set; }是否可以使自定义属性可用。然而,我仍然首先关注Roles正在发生什么。我以相同的方式创建了Roles上的自定义属性,即通过从IdentityRole继承的ApplicationRole。ApplicationDbContext显示了Users和Roles,但没有自定义属性(字段)。你认为我需要做一个DbSet<ApplicationRole> Roles { get; set; }吗? - Sean Newcome
1
我明白了。尝试将 IdentityRole 强制转换为 ApplicationRole。我刚刚尝试将 IdentityUserRole 转换为 ApplicationUserRole,我的自定义属性是可访问的。虽然如果你正在定制超出 ApplicationUser 的类,你可能需要等待 ASP.NET Identity 2.0。它有一个新的 IdentityDbContext<TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim> 基类,可以让你指定所有自定义实体。 - Anthony Chu
我将IdentityRole转换为ApplicationRole。通过RoleManager<ApplicationRole>,我可以访问自定义字段。但我不明白为什么Db Context在用户和角色上不显示自定义字段。 - Sean Newcome
在2.0中,尝试像这样创建您的DbContext... public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int, ApplicationLogin, ApplicationUserRole, ApplicationClaim>int(或string)是用户主键的类型)。 - Anthony Chu
显示剩余4条评论

9
这里有一个关于DbContext的基本误解。你在上下文中的DbSet的属性名称不对应于表名。如果有什么区别,那么表名是基于实际实体类的类名,但即使如此也可以被覆盖。一个完美的例子当然是你的用户类,默认情况下为ApplicationUser,但将驻留在名为AspNetUsers的表中。
在你的上下文中,所有的DbSet属性只是确定了你通过Entity Framework访问数据的API。例如,IdentityDbContext实现了DbSet属性的名称为UsersRoles等等。所以这就是你访问这些数据的方式,而不是通过表名(即context.Users)。
此外,如果你不喜欢有两个上下文,你不必把它们保留为两个。只需让你的主要上下文从DbContext继承到IdentityDbContext<ApplicationUser>,并删除脚手架版本即可。

1
展示两个上下文的代码示例只是说明它们之间的差异的一个例子。我个人喜欢将它们分开。对此并不感到不满。;) - Sean Newcome
是的,var usersInIndentityDb = identityDb.Users.ToList() 可以让您访问记录,但它不会使自定义字段可用。 - Sean Newcome

2

IdentityDbContext是一个令人困惑的概念,在Stack Overflow(SO)上搜索会发现很多涉及该主题的问题。
ASP.NET Identity DbContext confusion
How can I change the table names when using Visual Studio 2013 AspNet Identity?
Merge MyDbContext with 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可扩展项目模板

1
尽管数据库中的身份验证表以“aspnet”前缀命名,您始终可以更改它们。但是,并不总是在从DbContext访问时看到的表名将是数据库中的表名。您需要使用框架生成的名称进行操作。但这也可以更改。请参见使用Entity Framework Fluent的Identity数据模型

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接