ASP.NET Identity DbContext 混淆

Posted

技术标签:

【中文标题】ASP.NET Identity DbContext 混淆【英文标题】:ASP.NET Identity DbContext confusion 【发布时间】:2013-11-23 01:01:03 【问题描述】:

IdentityModels.cs 中的这段代码带有默认 MVC 5 应用程序 - 这段代码适用于默认模板的所有 ASP.NET Identity 操作:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>

    public ApplicationDbContext()
        : base("DefaultConnection")
    
    

如果我使用带有实体框架的视图构建一个新控制器并在对话框中创建一个“新数据上下文...”,我会为我生成这个:

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; 

    
 

如果我使用 EF 搭建另一个控制器 + 视图,例如对于 Animal 模型,这条新行将在 public System.Data.Entity.DbSet&lt;WebApplication1.Models.Movie&gt; Movies get; set; 下自动生成 - 如下所示:

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(对于所有 ASP.NET 身份的东西)继承自 IdentityDbContext,而 IdentityDbContext 又继承自 DbContextAllOtherStuffDbContext(我自己的东西)继承自 DbContext

所以我的问题是:

我应该将这两个(ApplicationDbContextAllOtherStuffDbContext)中的哪一个用于我自己的所有其他模型?或者我应该只使用默认的自动生成的ApplicationDbContext,因为它派生自基类DbContext,使用它应该没有问题,还是会有一些开销?对于所有模型,您应该在您的应用程序中只使用一个 DbContext 对象(我在某处读过此内容),所以我什至不应该考虑在单个应用程序中同时使用 ApplicationDbContextAllOtherStuffDbContext?或者在 MVC 5 中使用 ASP.NET Identity 的最佳实践是什么?

【问题讨论】:

顺便说一句;这是超级丑陋的,在扫描文档时对我的眼睛来说是不必要的: public System.Data.Entity.DbSet Movies get;放; - System.Data.Entity 和 WebApplication1.Models 部分。不能从声明中删除,而是在 using 语句部分添加命名空间吗? Puss - 是的,你的评论。这应该可以正常工作。 这是一个很好且有效的示例 (MVC 6) 和 ASP.NET 5 Identity (>= v3) 框架的实现库,没有 MongoDB.Driver 的实体框架 (>= v2.1.0) github.com/saan800/SaanSoft.AspNet.Identity3.MongoDB 【参考方案1】:

我将使用从 IdentityDbContext 继承的单个 Context 类。 这样,您可以让上下文了解您的类与 IdentityDbContext 的 IdentityUser 和 Roles 之间的任何关系。 IdentityDbContext 的开销很小,它基本上是一个带有两个 DbSet 的常规 DbContext。一个用于用户,一个用于角色。

【讨论】:

这适用于单个 MVC5 项目,但当派生的 DbContext 在多个项目之间共享时是不可取的,其中一些不是 MVC5,其中一些不需要身份支持。 投票支持相同的数据库,以便于维护和更好的关系完整性。因为用户实体和角色实体很容易关联到其他应用程序对象中。 @Dave - 它通过使用两个不同的上下文使用户数据的分区复杂化。您的 MVC 应用程序是否按用户分区数据,但其他应用程序没有。共享同一个数据层很常见,但我认为有些项目需要用户分配的数据,有些不需要。 有人知道如何从 MVC 项目中提取 ApplicationDBContext 并将其包含在现有的 EF 数据层中吗?如上所述,合并两者似乎是正确的方法,但我正在从事一个时间有限的项目。我想在第一次就做对,但希望能对摆在我面前的所有问题有所了解...... 在寻找了大约一个小时后,这个答案为我指明了正确的方向 - 但我不确定如何实施它(对于一个非常字面的人来说)。因此,如果它对其他人有帮助,我发现最简单的方法是打开 IdentityModels.cs,然后在 ApplicationDbContext 类中添加新的 DbSet。【参考方案2】:

对于 IdentityDbContext 有很多困惑,在 *** 中快速搜索一下,您会发现以下问题: " Why is Asp.Net Identity IdentityDbContext a Black-Box?How can I change the table names when using Visual Studio 2013 AspNet Identity?Merge MyDbContext with IdentityDbContext"

要回答所有这些问题,我们需要了解 IdentityDbContext 只是从 DbContext 继承的一个类。 一起来看看IdentityDbContext source:

/// <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="DbSetTEntity"/> of Users.
    /// </summary>
    public DbSet<TUser> Users  get; set; 

    /// <summary>
    /// Gets or sets the <see cref="DbSetTEntity"/> of User claims.
    /// </summary>
    public DbSet<TUserClaim> UserClaims  get; set; 

    /// <summary>
    /// Gets or sets the <see cref="DbSetTEntity"/> of User logins.
    /// </summary>
    public DbSet<TUserLogin> UserLogins  get; set; 

    /// <summary>
    /// Gets or sets the <see cref="DbSetTEntity"/> of User roles.
    /// </summary>
    public DbSet<TUserRole> UserRoles  get; set; 

    /// <summary>
    /// Gets or sets the <see cref="DbSetTEntity"/> of User tokens.
    /// </summary>
    public DbSet<TUserToken> UserTokens  get; set; 

    /// <summary>
    /// Gets or sets the <see cref="DbSetTEntity"/> of roles.
    /// </summary>
    public DbSet<TRole> Roles  get; set; 

    /// <summary>
    /// Gets or sets the <see cref="DbSetTEntity"/> 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");
        );
    

根据源代码,如果我们想将 IdentityDbContext 与 DbContext 合并,我们有两个选项:第一个选项: 创建一个继承自 IdentityDbContext 并有权访问类的 DbContext。

   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

额外说明:

1) 我们还可以通过以下解决方案更改 asp.net Identity 默认表名:

    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) 此外,我们可以扩展每个类并向类添加任何属性,例如“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

为了节省时间,我们可以使用AspNet Identity 2.0 Extensible Project Template 来扩展所有类。

第二个选项:(不推荐) 如果我们自己编写所有代码,我们实际上不必从 IdentityDbContext 继承。 所以基本上我们可以从 DbContext 继承并从IdentityDbContext source code 实现我们定制版本的“OnModelCreating(ModelBuilder builder)”

【讨论】:

@mike-devenney 这是您关于合并两个上下文层的答案,希望对您有所帮助。 感谢 Arvand,我错过了这一点,奇怪的是 1.5 年后在再次研究该主题时偶然发现了它。 :)【参考方案3】:

这对人们来说是一个较晚的条目,但下面是我的实现。您还会注意到我取消了更改 KEYs 默认类型的功能:有关该功能的详细信息,请参阅以下文章:

Extending Identity Models and Using Integer Keys Instead of Strings Change Primary Key for Users in ASP.NET Identity

注意事项: 请注意,您的密钥不能使用Guid's。这是因为在底层它们是 Struct,因此没有拆箱,这将允许它们从通用 &lt;TKey&gt; 参数转换。

类看起来像:

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
    

【讨论】:

【参考方案4】:

如果您深入了解 IdentityDbContext 的抽象,您会发现它看起来就像您的派生 DbContext。最简单的方法是 Olav 的回答,但如果您想更好地控制所创建的内容并减少对 Identity 包 have a look at my question and answer here 的依赖。如果您点击链接,则会有一个代码示例,但总而言之,您只需将所需的 DbSet 添加到您自己的 DbContext 子类中。

【讨论】:

以上是关于ASP.NET Identity DbContext 混淆的主要内容,如果未能解决你的问题,请参考以下文章

[ASP.NET MVC] ASP.NET Identity登入技术剖析

ASP.NET Identity系列教程Identity高级技术

ASP.NET MVC Identity 添加角色

ASP.NET Identity系列教程(目录)

ASP.NET Identity系列教程

使用 Identity Server 4 和 ASP.NET Identity 添加外部登录