ASP.NET Identity DbContext confusion

196

Una aplicación MVC 5 predeterminada viene con este código en IdentityModels.cs: este código es para todas las operaciones de identidad ASP.NET para las plantillas predeterminadas:

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

Si andamio un nuevo controlador usando vistas con Entity Framework y creo un "Nuevo contexto de datos ..." en el diálogo, obtengo esto generado por mí:

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 andamio otra vista de controlador + usando EF, por ejemplo, para un modelo Animal, esta nueva línea se generaría automáticamente justo debajo public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }, como esta:

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(para todas las cosas de identidad de ASP.NET) hereda de la IdentityDbContextque a su vez hereda DbContext. AllOtherStuffDbContext(para mis propias cosas) hereda de DbContext.

Entonces mi pregunta es:

¿Cuál de estos dos ( ApplicationDbContexty AllOtherStuffDbContext) debo usar para todos mis otros modelos propios? ¿O debería usar el autogenerado predeterminado ApplicationDbContextya que no debería ser un problema usarlo ya que deriva de la clase base DbContext, o habrá alguna sobrecarga? Debería usar solo un DbContextobjeto en su aplicación para todos sus modelos (lo he leído en alguna parte), ¿así que ni siquiera debería considerar usar ambos ApplicationDbContexty AllOtherStuffDbContexten una sola aplicación? ¿O cuál es la mejor práctica en MVC 5 con ASP.NET Identity?

El gato con botas
fuente
1
Por cierto; esto es superfluo y es innecesario para mis ojos mientras escaneo el documento: public System.Data.Entity.DbSet <WebApplication1.Models.Movie> Movies {get; conjunto; }: la parte System.Data.Entity y WebApplication1.Models. ¿No se puede eliminar de la declaración y, en su lugar, agregar los espacios de nombres en la sección de declaraciones usando?
PussInBoots
Gato - sí a tu comentario. Eso debería funcionar bien.
SB2055
Este es un ejemplo bueno y funcional (MVC 6) y lib de implementación con el marco de identidad ASP.NET 5 (> = v3) sin Entity Framework para MongoDB.Driver (> = v2.1.0) github.com/saan800/SaanSoft. AspNet.Identity3.MongoDB
Stanislav Prusac

Respuestas:

178

Usaría una sola clase de contexto que hereda de IdentityDbContext. De esta manera, puede tener el contexto al tanto de cualquier relación entre sus clases y el IdentityUser y Roles del IdentityDbContext. Hay muy poca sobrecarga en el IdentityDbContext, es básicamente un DbContext normal con dos DbSets. Uno para los usuarios y otro para los roles.

Olav Nybø
fuente
52
Eso es para un solo proyecto MVC5, pero no es deseable cuando el DbContext derivado se comparte entre varios proyectos, algunos no MVC5, donde algunos no necesitan el soporte de Identidad.
Dave
Voté por la misma base de datos para facilitar el mantenimiento y mejorar la integridad relacional. Porque la entidad de usuario y la entidad de rol se relacionarán fácilmente con otros objetos de aplicación.
anIBMer
66
@Dave: complica la partición de los datos del usuario al usar dos contextos diferentes. ¿Su aplicación MVC particiona los datos por usuario pero las otras aplicaciones no? Compartir la misma capa de datos es común, pero no creo que sea común que algunos proyectos necesiten los datos divididos por el usuario, y otros no.
RickAndMSFT
1
¿Alguien sabe de un procedimiento para extraer ApplicationDBContext de un proyecto MVC e incluirlo en una capa de datos EF existente? La fusión de los dos, como se describió anteriormente, parece ser el enfoque correcto, pero estoy trabajando en un proyecto de tiempo limitado. Quiero hacerlo bien la primera vez, pero me encantaría estar al tanto de todas las trampas que se encuentran frente a mí ...
Mike Devenney
77
Después de buscar durante aproximadamente una hora, esta respuesta me indicó la dirección correcta, pero no estaba seguro de cómo implementarla (para una persona muy literal, yo). Entonces, si ayuda a alguien más, descubrí que la forma más simple es abrir IdentityModels.cs y agregar su nuevo DbSet en la clase ApplicationDbContext.
SeanOB
45

Existe mucha confusión sobre IdentityDbContext , una búsqueda rápida en Stackoverflow y encontrará estas preguntas:
" ¿Por qué Asp.Net Identity IdentityDbContext es un recuadro negro?
¿Cómo puedo cambiar los nombres de las tablas cuando uso Visual Studio 2013 AspNet Identity?
Fusionar MyDbContext con IdentityDbContext "

Para responder a todas estas preguntas, debemos entender que IdentityDbContext es solo una clase heredada de DbContext.
Echemos un vistazo a la fuente 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");
        });
    }
}


Según el código fuente, si queremos fusionar IdentityDbContext con nuestro DbContext, tenemos dos opciones:

Primera opción:
crear un DbContext que herede de IdentityDbContext y tener acceso a las clases.

   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
}


Notas adicionales:

1) También podemos cambiar los nombres de tabla predeterminados de asp.net Identity con la siguiente solución:

    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) Además, podemos extender cada clase y agregar cualquier propiedad a clases como '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
}

Para ahorrar tiempo, podemos usar la plantilla de proyecto extensible AspNet Identity 2.0 para extender todas las clases.

Segunda opción:(No recomendado)
En realidad, no tenemos que heredar de IdentityDbContext si escribimos todo el código nosotros mismos.
Básicamente, podemos simplemente heredar de DbContext e implementar nuestra versión personalizada de "OnModelCreating (ModelBuilder Builder)" del código fuente IdentityDbContext

Arvand
fuente
2
@ Mike-Devenney Aquí está su respuesta sobre la fusión de las dos capas de contexto, espero que ayude.
Arvand
1
Gracias Arvand, me perdí esto y, curiosamente, tropecé con él 1.5 años más tarde mientras volvía a examinar el tema. :)
Mike Devenney
9

Esta es una entrada tardía para la gente, pero a continuación está mi implementación. También notará que eliminé la capacidad de cambiar el tipo predeterminado de CLAVES: los detalles sobre los cuales se pueden encontrar en los siguientes artículos:

NOTAS:
Debe tenerse en cuenta que no puede usar Guid'ssus llaves. Esto se debe a que bajo el capó son un Struct, y como tal, no tienen un unboxing que permita su conversión de un <TKey>parámetro genérico .

Las clases se ven como:

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
    }
Prisionero CERO
fuente
8

Si profundiza en las abstracciones de IdentityDbContext, encontrará que se parece a su DbContext derivado. La ruta más fácil es la respuesta de Olav, pero si desea más control sobre lo que se está creando y un poco menos de dependencia de los paquetes de Identidad, eche un vistazo a mi pregunta y responda aquí . Hay un ejemplo de código si sigue el enlace, pero en resumen solo agrega los DbSets requeridos a su propia subclase DbContext.

joelmdev
fuente