ASP.NET Identity DbContext confusion

196

Une application MVC 5 par défaut est livrée avec ce morceau de code dans IdentityModels.cs - ce morceau de code est pour toutes les opérations d'identité ASP.NET pour les modèles par défaut:

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

Si j'échafaude un nouveau contrôleur en utilisant des vues avec Entity Framework et crée un "Nouveau contexte de données ..." dans la boîte de dialogue, je reçois ceci généré pour moi:

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

    }
} 

Si j'échafaudais un autre contrôleur + vue en utilisant EF, par exemple pour un modèle Animal, cette nouvelle ligne serait automatiquement générée sous public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }- comme ceci:

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(pour tous les trucs d'identité ASP.NET) hérite de IdentityDbContextqui à son tour hérite de DbContext. AllOtherStuffDbContext(pour mes propres affaires) hérite de DbContext.

Ma question est donc:

Lequel de ces deux ( ApplicationDbContextet AllOtherStuffDbContext) devrais-je utiliser pour tous mes autres modèles? Ou devrais-je simplement utiliser la génération automatique par défaut ApplicationDbContextcar cela ne devrait pas être un problème de l'utiliser car elle dérive de la classe de base DbContext, ou y aura-t-il des frais généraux? Vous ne devez utiliser qu'un seul DbContextobjet dans votre application pour tous vos modèles (je l'ai lu quelque part), donc je ne devrais même pas envisager d'utiliser les deux ApplicationDbContextet AllOtherStuffDbContextdans une seule application? Ou quelle est la meilleure pratique dans MVC 5 avec ASP.NET Identity?

Chat Botté
la source
1
Au fait; ceci est extrêmement superflu et inutile pour mes yeux lors de la numérisation du document: public System.Data.Entity.DbSet <WebApplication1.Models.Movie> Movies {get; ensemble; } - la partie System.Data.Entity et WebApplication1.Models. Ne peut-il pas être supprimé de la déclaration et ajouter à la place les espaces de noms dans la section des instructions using?
PussInBoots
Puss - oui à votre commentaire. Cela devrait très bien fonctionner.
SB2055
Ceci est un bon exemple de bibliothèque d'implémentation (MVC 6) et de travail avec ASP.NET 5 Framework (> = v3) sans Entity Framework pour MongoDB.Driver (> = v2.1.0) github.com/saan800/SaanSoft. AspNet.Identity3.MongoDB
Stanislav Prusac

Réponses:

178

J'utiliserais une seule classe Context héritant d'IdentityDbContext. De cette façon, vous pouvez faire en sorte que le contexte soit au courant des relations entre vos classes et IdentityUser et les rôles d'IdentityDbContext. Il y a très peu de frais généraux dans IdentityDbContext, il s'agit essentiellement d'un DbContext normal avec deux DbSets. Un pour les utilisateurs et un pour les rôles.

Olav Nybø
la source
52
C'est pour un seul projet MVC5 mais pas souhaitable lorsque le DbContext dérivé est partagé entre plusieurs projets, certains pas MVC5, où certains n'ont pas besoin de la prise en charge de l'identité.
Dave
A voté pour la même base de données pour une maintenabilité plus facile et une meilleure intégrité relationnelle. Parce que l'entité utilisateur et l'entité de rôle seront facilement liées à d'autres objets d'application.
anIBMer
6
@Dave - Il complique le partitionnement des données utilisateur en utilisant deux contextes différents. Est-ce que votre application MVC partitionne les données par utilisateur, mais pas les autres applications. Le partage de la même couche de données est courant, mais je ne pense pas qu'il soit courant que certains projets aient besoin des données fractionnées par utilisateur, et d'autres non.
RickAndMSFT
1
Quelqu'un sait-il comment extraire ApplicationDBContext d'un projet MVC et l'inclure dans une couche de données EF existante? La fusion des deux, comme décrit ci-dessus, semble être la bonne approche, mais je travaille sur un projet limité dans le temps. Je veux le faire correctement la première fois, mais j'aimerais avoir un coup de
œil
7
Après avoir cherché pendant environ une heure, cette réponse m'a orienté dans la bonne direction - mais je ne savais pas comment la mettre en œuvre (pour une personne très littérale, moi). Donc, si cela aide quelqu'un d'autre, j'ai trouvé que le moyen le plus simple consiste à ouvrir IdentityModels.cs et à ajouter votre nouveau DbSet dans la classe ApplicationDbContext.
SeanOB
45

Il y a beaucoup de confusion à propos d' IdentityDbContext , une recherche rapide dans Stackoverflow et vous trouverez ces questions:
« Pourquoi l'identité Asp.Net IdentityDbContext est-elle une boîte noire?
Comment puis-je changer les noms de table lors de l'utilisation de Visual Studio 2013 AspNet Identity?
Fusionner MyDbContext avec IdentityDbContext "

Pour répondre à toutes ces questions, nous devons comprendre que IdentityDbContext n'est qu'une classe héritée de DbContext.
Jetons un coup d'œil à la 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 si nous voulons fusionner IdentityDbContext avec notre DbContext, nous avons deux options:

Première option:
créer un DbContext qui hérite d'IdentityDbContext et 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
}


Notes supplémentaires:

1) Nous pouvons également modifier les noms de table par défaut de asp.net Identity avec la solution suivante:

    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) De plus, nous pouvons étendre chaque classe et ajouter n'importe quelle propriété à des classes comme '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<ApplicationUser, ApplicationRole, 
    string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }

    static ApplicationDbContext()
    {
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }

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

    // Add additional items here as needed
}

Pour gagner du temps, nous pouvons utiliser le modèle de projet extensible AspNet Identity 2.0 pour étendre toutes les classes.

Deuxième option:(Non recommandé)
En fait, nous n'avons pas à hériter d'IdentityDbContext si nous écrivons nous-mêmes tout le code.
Donc, fondamentalement, nous pouvons simplement hériter de DbContext et implémenter notre version personnalisée de "OnModelCreating (ModelBuilder builder)" du code source IdentityDbContext

Arvand
la source
2
@ mike-devenney Voici votre réponse sur la fusion des deux couches de contexte, j'espère que cela vous aidera.
Arvand
1
Merci Arvand, j'ai raté cela et assez bizarrement je suis tombé dessus 1,5 an plus tard en réexaminant le sujet. :)
Mike Devenney
9

Ceci est une entrée tardive pour les gens, mais voici ma mise en œuvre. Vous remarquerez également que j'ai supprimé la possibilité de modifier le type par défaut des touches: les détails à ce sujet peuvent être trouvés dans les articles suivants:

REMARQUES:
Il convient de noter que vous ne pouvez pas utiliser Guid'svos clés. En effet, sous le capot, ils sont un Struct, et en tant que tels, n'ont pas de déballage qui permettrait leur conversion à partir d'un <TKey>paramètre générique .

LES COURS SONT COMME:

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
    }
Prisonnier ZERO
la source
8

Si vous explorez les abstractions de IdentityDbContext, vous constaterez qu'il ressemble à votre DbContext dérivé. Le chemin le plus simple est la réponse d'Olav, mais si vous voulez plus de contrôle sur ce qui est créé et un peu moins de dépendance sur les packages d'identité, jetez un œil à ma question et réponse ici . Il y a un exemple de code si vous suivez le lien, mais en résumé, vous ajoutez simplement les DbSets requis à votre propre sous-classe DbContext.

joelmdev
la source