Confusione DbContext di identità ASP.NET


196

Un'app MVC 5 predefinita viene fornita con questo pezzo di codice in IdentityModels.cs: questo pezzo di codice è per tutte le operazioni di identità ASP.NET per i modelli predefiniti:

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

Se impalcolo un nuovo controller usando le viste con Entity Framework e creo un "Nuovo contesto di dati ..." nella finestra di dialogo, ottengo questo generato per me:

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

    }
} 

Se impalconessi un altro controller + vista usando EF, ad esempio per un modello Animal, questa nuova linea verrebbe generata automaticamente sotto public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }- in questo modo:

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(per tutte le cose di ASP.NET Identity) eredita da IdentityDbContextcui a sua volta eredita DbContext. AllOtherStuffDbContext(per la mia roba) eredita da DbContext.

Quindi la mia domanda è:

Quale di questi due ( ApplicationDbContexte AllOtherStuffDbContext) dovrei usare per tutti gli altri miei modelli? O dovrei semplicemente usare l'autogenerato predefinito ApplicationDbContextpoiché non dovrebbe essere un problema usarlo poiché deriva dalla classe base DbContext, o ci sarà un overhead? Dovresti usare un solo DbContextoggetto nella tua app per tutti i tuoi modelli (l'ho letto da qualche parte), quindi non dovrei nemmeno considerare di utilizzare entrambi ApplicationDbContexte AllOtherStuffDbContextin una singola app? O quali sono le migliori pratiche in MVC 5 con ASP.NET Identity?


1
A proposito; questo è superbo e non è necessario per i miei occhi durante la scansione del documento: public System.Data.Entity.DbSet <WebApplication1.Models.Movie> Movies {get; impostato; } - la parte System.Data.Entity e WebApplication1.Models. Non può essere rimosso dalla dichiarazione e invece aggiungere gli spazi dei nomi nella sezione dichiarazioni using?
PussInBoots

Il gatto - sì al tuo commento. Dovrebbe funzionare bene.
SB2055,

Questo è un esempio valido e funzionante (MVC 6) e lib di implementazione con ASP.NET 5 Identity (> = v3) framework senza Entity Framework per MongoDB.Driver (> = v2.1.0) github.com/saan800/SaanSoft. AspNet.Identity3.MongoDB
Stanislav Prusac,

Risposte:


178

Vorrei utilizzare una singola classe Context che eredita da IdentityDbContext. In questo modo è possibile far sì che il contesto sia a conoscenza di eventuali relazioni tra le classi e IdentityUser e ruoli di IdentityDbContext. In IdentityDbContext c'è un sovraccarico minimo, in pratica è un DbContext normale con due DbSet. Uno per gli utenti e uno per i ruoli.


52
Questo è per un singolo progetto MVC5 ma non è auspicabile quando il DbContext derivato è condiviso tra più progetti, alcuni non MVC5, in cui alcuni non necessitano del supporto Identity.
Dave,

Votato per lo stesso database per una più facile manutenibilità e una migliore integrità relazionale. Perché l'entità utente e l'entità ruolo saranno facilmente correlate ad altri oggetti applicazione.
anIBMer

6
@Dave: complica il partizionamento dei dati utente utilizzando due contesti diversi. La tua app MVC partiziona i dati per utente ma le altre app no. La condivisione dello stesso livello di dati è comune, ma non credo sia comune che alcuni progetti richiedano i dati suddivisi per utente e altri no.
RickAndMSFT,

1
Qualcuno è a conoscenza di come fare per estrarre ApplicationDBContext da un progetto MVC e includerlo in un livello dati EF esistente? Unire i due, come descritto sopra, sembra essere l'approccio giusto, ma sto lavorando a un progetto a tempo limitato. Voglio farlo bene la prima volta, ma mi piacerebbe un testa a testa su tutti i gotcha che si trovano di fronte a me ...
Mike Devenney

7
Dopo aver cercato per circa un'ora questa risposta mi ha indicato la giusta direzione, ma non ero sicuro di come implementarlo (per una persona molto letterale). Quindi, se aiuta qualcun altro, ho scoperto che il modo più semplice è aprire IdentityModels.cs e aggiungere il nuovo DbSet nella classe ApplicationDbContext.
SeanOB,

45

C'è molta confusione su IdentityDbContext , una rapida ricerca in StackOverflow e troverai queste domande:
" Perché Asp.Net Identity IdentityDbContext è una scatola nera?
Come posso cambiare i nomi delle tabelle quando utilizzo Visual Studio 2013 AspNet Identity?
Unisci MyDbContext con IdentityDbContext "

Per rispondere a tutte queste domande dobbiamo capire che IdentityDbContext è solo una classe ereditata da DbContext.
Diamo un'occhiata alla fonte 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");
        });
    }
}


In base al codice sorgente se vogliamo unire IdentityDbContext con il nostro DbContext abbiamo due opzioni:

Prima opzione:
creare un DbContext che eredita da IdentityDbContext e avere accesso alle classi.

   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
}


Note extra:

1) Possiamo anche cambiare i nomi delle tabelle predefinite di identità asp.net con la seguente soluzione:

    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) Inoltre possiamo estendere ogni classe e aggiungere qualsiasi proprietà a classi come '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
}

Per risparmiare tempo, possiamo utilizzare il modello di progetto estensibile AspNet Identity 2.0 per estendere tutte le classi.

Seconda opzione:(Non consigliato)
In realtà non dobbiamo ereditare da IdentityDbContext se scriviamo tutto il codice da soli.
Quindi in pratica possiamo semplicemente ereditare da DbContext e implementare la nostra versione personalizzata di "OnModelCreating (builder ModelBuilder)" dal codice sorgente di IdentityDbContext


2
@ mike-devenney Ecco la tua risposta sull'unione dei due livelli di contesto, spero che sia di aiuto.
Arvand

1
Grazie Arvand, l'ho perso e stranamente ci siamo imbattuti in esso 1,5 anni dopo, mentre guardavo di nuovo l'argomento. :)
Mike Devenney

9

Questa è una voce in ritardo per la gente, ma di seguito è la mia implementazione. Noterai anche che ho eliminato la possibilità di cambiare il tipo predefinito di KEYs: i dettagli sui quali puoi trovare nei seguenti articoli:

NOTE:
Va notato che non è possibile utilizzare Guid'sper le chiavi. Questo perché sotto il cofano sono a Struct, e come tali, non hanno unboxing che consentirebbe la loro conversione da un <TKey>parametro generico .

LE CLASSI SONO COME:

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
    }

8

Se analizzi le astrazioni di IdentityDbContext, scoprirai che assomiglia al tuo DbContext derivato. Il percorso più semplice è la risposta di Olav, ma se vuoi un maggiore controllo su ciò che viene creato e un po 'meno dipendenza dai pacchetti di Identity dai un'occhiata alla mia domanda e risposta qui . C'è un esempio di codice se segui il link, ma in sintesi aggiungi semplicemente i DbSet richiesti alla tua sottoclasse DbContext.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.