web-dev-qa-db-fra.com

Pourquoi Asp.Net Identity IdentityDbContext est-il une boîte noire?

Il y a beaucoup de confusion autour d'IdentityDbContext.

Si nous créons deux contextes de base de données dans notre application, un pour l'identité et un pour nos données d'entreprise personnalisées, le contexte de base de données d'identité hérite d'IdentityDbContext tandis que nos données d'entreprise personnalisées héritent de DbContext.

Ajoutons donc ce qui suit à un contrôleur:

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

Et ce qui suit à une méthode Index dans le contrôleur:

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

Vous remarquerez également que les tables de la base de données des identités ne sont pas disponibles. Pourquoi est-ce?

Actuellement, à partir de la version 2.0.0-beta1, il existe des éléments Utilisateurs et rôles, mais je m'attendais à ce que les tables soient disponibles. Et pourquoi pas? Et si je voulais accéder à AspNetUserRoles

Il semble certain que beaucoup de confusion et de problèmes avec Asp.Net Identity disparaîtraient s'il était traité comme n'importe quel contexte de base de données dans Entity Framework.

34
Sean Newcome

Les propriétés ApplicationDbContext et Users de Roles sont mappées aux tables AspNetUsers et AspNetRoles, et au reste des entités (Claims, Logins, UserRoles) sont mappés automatiquement via les propriétés de navigation. Pour autant que je sache, le préfixe des noms de table avec "AspNet" sont les seuls mappages personnalisés dans ApplicationDbContext, tout le reste n'est que les conventions Entity Framework Code First.

Si vous avez besoin d'un accès direct aux tables via ApplicationDbContext, vous pouvez le faire comme ceci ...

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();
}

Vous pouvez accéder aux rôles, revendications et connexions d'un utilisateur via les propriétés de navigation sur l'entité IdentityUser (à partir de UsersDbSet). Si vous souhaitez les interroger directement, ajoutez-les explicitement en tant que DbSets dans le contexte ...

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; }

}

Et interrogez-les comme ça ...

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

ASP.NET Identity 2.0 expose Users et RolesIQueryables sur les classes Manager pour plus de commodité, mais il ne fournit aucune fonctionnalité supplémentaire par rapport à ce qui était disponible à partir de DbContext.

31
Anthony Chu

Il y a un malentendu fondamental sur le fonctionnement de DbContext. Les noms de propriété de vos DbSet dans votre contexte ne pas correspondent aux noms de table. Si quoi que ce soit, le nom de la table est basé sur le nom de classe de l'entité réelle, mais même cela peut être remplacé. Un exemple parfait est bien sûr votre classe d'utilisateurs, qui est par défaut ApplicationUser, mais qui résidera dans une table appelée AspNetUsers.

Toutes les propriétés DbSet de votre contexte déterminent l'API que vous utilisez pour accéder aux données via Entity Framework. IdentityDbContext implémente DbSet propriétés nom Users, Roles, etc. Voilà donc comment vous accédez à ces données, pas via le nom de la table (c'est-à-dire context.Users).

De plus, si vous n'êtes pas satisfait d'avoir deux contextes, vous n'avez pas à les garder comme deux. Faites simplement hériter votre contexte principal de IdentityDbContext<ApplicationUser> au lieu de DbContext et tuez la version échafaudée.

9
Chris Pratt

Il y a certainement beaucoup de confusion autour d'IdentityDbContext, une recherche rapide autour de SO et vous trouverez beaucoup de questions sur ce sujet.
confusion ASP.NET Identity DbContext
Comment puis-je changer les noms de table lors de l'utilisation de Visual Studio 2013 AspNet Identity?
Fusionner MyDbContext avec IdentityDbContext

La réponse à toutes ces questions, nous devons d'abord comprendre comment fonctionne IdentityDbContext. Pour clarifier les choses, nous devons prendre en considération que IdentityDbContext est juste une classe héritée de DbContext et non une boîte noire!
Jetons un coup d'œil à source 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");
        });
    }
}

Sur la base du code source, tout ce que vous avez à faire est de créer un DbContext qui hérite d'IdentityDbContext et d'avoir accès aux 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
}

Si vous souhaitez étendre davantage les classes, jetez un œil à AspNet Identity 2.0 Extensible Project Template

1
Arvand

Même si les tables d'identité de la base de données sont nommées avec le préfixe aspnet, vous pouvez toujours les changer . Mais pas toujours le nom de la table dans la base de données ne sera pas celui que vous verrez lors de l'accès à partir de DbContext. Vous devrez travailler avec des noms générés par le framework. Mais cela peut aussi être changé. Voir Modèle de données d'identité avec Entity Framework Fluent .

1
Nipuna