Почему Asp.Net Identity IdentityDbContext является черным ящиком?

В IdentityDbContext есть много путаницы.

Если мы создаем два контекста базы данных в нашем приложении, один для Identity и один для наших пользовательских бизнес-данных, Контекст базы данных Identity наследует от 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

Вы также заметите, что таблицы в базе данных indentity недоступны. Почему это?

В настоящее время с 2.0.0-beta1 есть элементы "Пользователи и роли", но я ожидал, что фактические таблицы будут доступны. А почему бы и нет? Что делать, если я хочу попасть в AspNetUserRoles

Несомненно, что много путаницы и проблем с Asp.Net Identity исчезнет, ​​если рассматривать ее как любой контекст базы данных в Entity Framework.

Ответ 1

Свойства ApplicationDbContext Users и Roles отображаются в таблицы AspNetUsers и AspNetRoles, а остальные объекты (Claims, Logins, UserRoles) отображаются автоматически через свойства навигации. Насколько мне известно, префиксы имен таблиц с помощью "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 (из 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 для классов Manager для удобства, но не предоставляет каких-либо дополнительных функций над тем, что было доступно в DbContext.

Ответ 2

Здесь фундаментальное недоразумение о том, как работает DbContext. Имена свойств вашего DbSet в вашем контексте не соответствуют именам таблиц. Во всяком случае, имя таблицы основано на имени класса фактического объекта, но даже это можно переопределить. Прекрасным примером является, конечно, ваш пользовательский класс, который по умолчанию ApplicationUser, но будет находиться в таблице под названием AspNetUsers.

Все свойства DbSet в вашем контексте определяют API, который вы используете для доступа к данным через Entity Framework. IdentityDbContext реализует DbSet имя свойства Users, Roles и т.д. Таким образом, вы получаете доступ к этим данным, а не через имя таблицы (т.е. context.Users).

Кроме того, если вы недовольны наличием двух контекстов, вам не нужно держать их как два. Просто сделайте свой основной контекст наследуемым от IdentityDbContext<ApplicationUser> вместо DbContext и убейте версию подкласса.

Ответ 3

Даже несмотря на то, что идентификационные таблицы в базе данных имеют имя с префиксом aspnet, вы всегда можете изменить их. Но не всегда имя таблицы в базе данных не будет тем, которое вы увидите при доступе от DbContext. Вам нужно будет работать с именами, которые генерируются каркасом. Но это тоже можно изменить. См. Модель данных идентичности с Fluent Entity Framework.

Ответ 4

В IdentityDbContext есть много путаницы, быстрый поиск по SO, и вы найдете много вопросов по этой теме.
Идентификация ASP.NET DbContext путаница
Как изменить имена таблиц при использовании AspNet Identity для Visual Studio 2013? Объединить 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");
        });
    }
}

На основе исходного кода все, что вам нужно сделать, это создать DbContext, который наследует от IdentityDbContext и имеет доступ к классам.

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