Confusão de DbContext de identidade do ASP.NET

196

Um aplicativo MVC 5 padrão vem com esse trecho de código no IdentityModels.cs - esse trecho de código é para todas as operações de identidade do ASP.NET para os modelos padrão:

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

Se eu estruturar um novo controlador usando visualizações com o Entity Framework e criar um "Novo contexto de dados ..." na caixa de diálogo, eu gero isso para mim:

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 eu montar um outro controller + view usando EF, digamos, por exemplo, para um modelo Animal, essa nova linha seria gerada automaticamente logo abaixo public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }- assim:

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 as coisas de identidade do ASP.NET) herda da IdentityDbContextqual, por sua vez, herda DbContext. AllOtherStuffDbContext(para minhas próprias coisas) herda DbContext.

Então, minha pergunta é:

Qual desses dois ( ApplicationDbContexte AllOtherStuffDbContext) devo usar para todos os meus outros modelos? Ou devo apenas usar o padrão gerado automaticamente, ApplicationDbContextpois não deve ser um problema usá-lo, pois ele deriva da classe base DbContextou haverá alguma sobrecarga? Você deve usar apenas um DbContextobjeto no seu aplicativo para todos os seus modelos (já li isso em algum lugar), para que eu nem considere usar ambos ApplicationDbContexte AllOtherStuffDbContextem um único aplicativo? Ou qual é a melhor prática no MVC 5 com o ASP.NET Identity?

O Gato de Botas
fonte
1
A propósito; isso é excessivo e desnecessário para meus olhos ao digitalizar o documento: public System.Data.Entity.DbSet <WebApplication1.Models.Movie> Movies {get; conjunto; } - a parte System.Data.Entity e WebApplication1.Models. Não pode ser removido da declaração e, em vez disso, adicionar os espaços para nome na seção using statement?
PussInBoots 11/11
Gato - sim ao seu comentário. Isso deve funcionar muito bem.
SB2055
Este é um exemplo bom e funcional de implementação (MVC 6) e lib com a estrutura do ASP.NET 5 Identity (> = v3) sem o Entity Framework para MongoDB.Driver (> = v2.1.0) github.com/saan800/SaanSoft. AspNet.Identity3.MongoDB
Stanislav Prusac

Respostas:

178

Eu usaria uma única classe Context herdada de IdentityDbContext. Dessa maneira, você pode ter o contexto ciente de quaisquer relações entre suas classes e o IdentityUser e as Funções do IdentityDbContext. Há muito pouca sobrecarga no IdentityDbContext, é basicamente um DbContext regular com dois DbSets. Um para os usuários e outro para as funções.

Olav Nybø
fonte
52
Isso é para um único projeto MVC5, mas não é desejável quando o DbContext derivado é compartilhado entre vários projetos, alguns não o MVC5, onde alguns não precisam do suporte de Identidade.
19414 Dave
Votou no mesmo banco de dados para facilitar a manutenção e melhorar a integridade relacional. Porque a entidade do usuário e a entidade da função serão relacionadas a outros objetos de aplicativos facilmente.
anIBMer
6
@Dave - complica o particionamento de dados do usuário usando dois contextos diferentes. Seu aplicativo MVC particiona dados por usuário, mas os outros aplicativos não. Compartilhar a mesma camada de dados é comum, mas não acho que seja comum que alguns projetos precisem dos dados repartidos pelo usuário e outros não.
RickAndMSFT
1
Alguém sabe de como extrair o ApplicationDBContext de um projeto MVC e incluí-lo em uma camada de dados EF existente? Mesclar os dois, como descrito acima, parece ser a abordagem correta, mas estou trabalhando em um projeto com tempo limitado. Eu quero fazê-lo direito na primeira vez, mas adoraria um heads-up em todas as armadilhas que se encontram na frente de mim ...
Mike Devenney
7
Depois de procurar por cerca de uma hora, essa resposta me indicou a direção certa - mas eu não tinha certeza de como implementá-la (para uma pessoa muito literal comigo). Portanto, se ajudar alguém, achei que a maneira mais simples é abrir o IdentityModels.cs e adicionar seu novo DbSet na classe ApplicationDbContext.
SeanOB
45

Há muita confusão sobre o IdentityDbContext , uma pesquisa rápida no Stackoverflow e você encontrará as seguintes perguntas:
" Por que o Asp.Net Identity IdentityDbContext é uma caixa preta?
Como posso alterar os nomes das tabelas ao usar o Visual Studio 2013 AspNet Identity?
Mesclar MyDbContext com IdentityDbContext "

Para responder a todas essas perguntas, precisamos entender que o IdentityDbContext é apenas uma classe herdada do DbContext.
Vamos dar uma olhada na 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");
        });
    }
}


Com base no código fonte, se queremos mesclar IdentityDbContext com nosso DbContext, temos duas opções:

Primeira opção:
Crie um DbContext que herda de IdentityDbContext e tenha acesso às 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
}


Notas Extra:

1) Também podemos alterar os nomes de tabela padrão da identidade do asp.net com a seguinte solução:

    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) Além disso, podemos estender cada classe e adicionar qualquer propriedade a classes 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 economizar tempo, podemos usar o modelo de projeto extensível do AspNet Identity 2.0 para estender todas as classes.

Segunda opçao:(Não recomendado)
Na verdade, não precisamos herdar o IdentityDbContext se escrevermos todo o código.
Então, basicamente, podemos herdar do DbContext e implementar nossa versão personalizada do "OnModelCreating (construtor ModelBuilder)" do código-fonte IdentityDbContext

Arvand
fonte
2
@ mike-devenney Aqui está sua resposta sobre a fusão das duas camadas de contexto, espero que ajude.
Arvand
1
Graças a Arvand, eu perdi isso e, por incrível que pareça, tropecei novamente 1,5 anos depois enquanto olhava para o tópico novamente. :)
Mike Devenney
9

Esta é uma entrada tardia para as pessoas, mas abaixo está minha implementação. Você também notará que eu eliminei a capacidade de alterar o tipo padrão das KEYs: os detalhes sobre os quais podem ser encontrados nos seguintes artigos:

NOTAS:
Observe que você não pode usar Guid'sas chaves. Isso ocorre porque, sob o capô, eles são Structe, como tal, não têm unboxing, o que permitiria a conversão de um <TKey>parâmetro genérico .

AS CLASSES PARECEM 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
    }
Prisioneiro ZERO
fonte
8

Se você pesquisar detalhadamente as abstrações do IdentityDbContext, verá que ele se parece com o seu DbContext derivado. A rota mais fácil é a resposta de Olav, mas se você quiser mais controle sobre o que está sendo criado e um pouco menos de dependência dos pacotes de identidade, dê uma olhada na minha pergunta e resposta aqui . Há um exemplo de código se você seguir o link, mas, em resumo, basta adicionar os DbSets necessários à sua própria subclasse DbContext.

joelmdev
fonte