ASP.NET身份DbContext混淆


196

默认代码MVC 5 App随IdentityModels.cs中的这段代码一起提供-此代码段用于默认模板的所有ASP.NET Identity操作:

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

如果我使用带有Entity Framework的视图来搭建一个新的控制器,并在对话框中创建一个“ New data context ...”,我将为我生成一个:

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<WebApplication1.Models.Movie> 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 Identity东西)继承自IdentityDbContext,而继承自DbContextAllOtherStuffDbContext(用于我自己的东西)继承自DbContext

所以我的问题是:

我所有其他自己的模型都应使用这两个(ApplicationDbContextAllOtherStuffDbContext)中的哪一个?还是我应该只使用默认值autogenerated,ApplicationDbContext因为使用它应该不是问题,因为它是从基类派生的DbContext,否则会产生一些开销吗?你应该只使用一个DbContext对象在你的应用程序为所有的车型(我读过这个地方),所以我应该不会考虑同时使用ApplicationDbContext,并AllOtherStuffDbContext在一个单一的应用程序?或带有ASP.NET Identity的MVC 5中的最佳实践是什么?


1
顺便说说; 这是多余的,并且在扫描文档时对我来说是不必要的:public System.Data.Entity.DbSet <WebApplication1.Models.Movie>电影{get; 组; }-System.Data.Entity和WebApplication1.Models部分。不能从声明中删除它,而是在using语句部分添加名称空间吗?
PussInBoots

猫-是的,您的意见。那应该很好。
SB2055 2014年

这是一个很好且可行的示例(MVC 6),它具有ASP.NET 5 Identity(> = v3)框架的实现,而没有用于MongoDB.Driver的实体框架(> = v2.1.0)的github.com/saan800/SaanSoft。 AspNet.Identity3.MongoDB
Stanislav Prusac

Answers:


178

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


52
那是针对单个MVC5项目的,但是当在多个项目中共享派生的DbContext时,这是不希望的,有些不是MVC5,其中有些不需要身份支持。
戴夫

为更易于维护和更好的关系完整性而投票选择相同的数据库。因为用户实体和角色实体将很容易关联到其他应用程序对象。
an IBMer 2014年

6
@Dave-使用两个不同的上下文使用户数据的分区变得复杂。您的MVC应用程序是否按用户对数据进行分区,而其他应用程序则不。共享相同的数据层是很常见的,但我认为有些项目需要按用户分配数据,而有些则不需要。
RickAndMSFT

1
有谁知道如何从MVC项目中提取ApplicationDBContext并将其包括在现有EF数据层中的方法?如上所述,将两者合并似乎是正确的方法,但是我正在研究时间受限制的项目。我想第一次做就对了,但是很想知道我面前所有的陷阱……
Mike Devenney 2015年

7
在寻找了大约一个小时之后,这个答案为我指明了正确的方向-但是我不确定如何实现它(对于一个非常老实的人来说,我)。因此,如果对其他人有所帮助,我发现最简单的方法是打开IdentityModels.cs,然后在ApplicationDbContext类中添加新的DbSet。
SeanOB 2015年

45

关于IdentityDbContext(在Stackoverflow中进行快速搜索)存在很多困惑,您会发现以下问题:
为什么Asp.Net Identity IdentityDbContext是黑盒?
使用Visual Studio 2013 AspNet Identity时如何更改表名称?
合并MyDbContext与IdentityDbContext

要回答所有这些问题,我们需要了解IdentityDbContext只是从DbContext继承的类。
让我们看一下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");
        });
    }
}


基于源代码,如果我们想将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身份默认表名称:

    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可扩展项目模板扩展所有类。

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


2
@ mike-devenney这是合并两个上下文层的答案,希望对您有所帮助。
2016年

1
谢谢Arvand,我错过了这一点,奇怪的是1.5年后,在再次浏览该主题时偶然发现了它。:)
麦克Devenney

9

这对于人们来说是一个较晚的条目,但是下面是我的实现。您还将注意到,我放弃了更改KEYs默认类型的能力:有关其详细信息,请参见以下文章:

注意:
应该注意,您不能将其Guid's用作钥匙。这是因为在幕后它们是Struct,因此没有取消装箱的操作,这将允许它们从通用<TKey>参数转换。

班级风格:

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

如果深入研究IdentityDbContext的抽象,您会发现它看起来就像派生的DbContext。最简单的方法是Olav的答案,但是如果您想对创建的内容有更多的控制,并且对Identity软件包的依赖性较小,请在此处查看我的问答。如果您单击该链接,则会有一个代码示例,但总而言之,您只需将所需的DbSet添加到您自己的DbContext子类中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.