ASP.NET DbContext Identity สับสน


196

แอป MVC 5 เริ่มต้นมาพร้อมกับรหัสชิ้นนี้ใน IdentityModels.cs - รหัสนี้สำหรับการดำเนินการ ASP.NET Identity ทั้งหมดสำหรับแม่แบบเริ่มต้น:

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

ถ้าฉันนั่งร้านตัวควบคุมใหม่โดยใช้มุมมองกับ Entity Framework และสร้าง "บริบทข้อมูลใหม่ ... " ในกล่องโต้ตอบฉันจะได้รับสิ่งนี้ที่สร้างขึ้นสำหรับฉัน:

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 พูดเช่นสำหรับโมเดลสัตว์บรรทัดใหม่นี้จะได้รับการสร้างอัตโนมัติภายใต้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 เอกลักษณ์) ที่สืบทอดจากซึ่งสืบทอดหันกลับจาก IdentityDbContext (สำหรับสิ่งที่ฉันเอง) ที่สืบทอดจากDbContextAllOtherStuffDbContextDbContext

ดังนั้นคำถามของฉันคือ:

ฉันควรใช้สองแบบนี้ ( ApplicationDbContextและAllOtherStuffDbContext) สำหรับรุ่นอื่น ๆ ของตัวเองทั้งหมดอย่างไร หรือฉันควรใช้ค่าเริ่มต้นอัตโนมัติApplicationDbContextเนื่องจากไม่น่าจะมีปัญหาในการใช้มันเนื่องจากมันมาจากคลาสพื้นฐานDbContextหรือจะมีค่าใช้จ่ายบ้างไหม คุณควรใช้เพียงDbContextวัตถุเดียวในแอปของคุณสำหรับทุกรุ่นของคุณ (ฉันได้อ่านสิ่งนี้แล้ว) ดังนั้นฉันจึงไม่ควรใช้ทั้งสองApplicationDbContextและAllOtherStuffDbContextในแอพเดียว? หรือวิธีปฏิบัติที่ดีที่สุดใน MVC 5 ด้วย ASP.NET Identity คืออะไร?


1
ยังไงซะ; นี่คือสุดยอดและไม่จำเป็นสำหรับดวงตาของฉันในขณะที่สแกนเอกสาร: สาธารณะ System.Data.Entity.DbSet <WebApplication1.Models.Movie> ภาพยนตร์ {รับ; ตั้ง; } - ส่วน System.Data.Entity และ WebApplication1.Models ไม่สามารถลบออกจากการประกาศและเพิ่มเนมสเปซในส่วนการใช้ข้อความแทนได้หรือไม่
PussInBoots

Puss - ใช่ความคิดเห็นของคุณ ที่ควรจะทำงานได้ดี
SB2055

นี่เป็นตัวอย่างที่ดีและใช้งานได้ (MVC 6) และ lib ของการนำไปใช้กับ ASP.NET 5 Identity (> = v3) โดยไม่มี Entity Framework สำหรับ MongoDB.Driver (> = v2.1.0) github.com/saan800/SaanSoft AspNet.Identity3.MongoDB
Stanislav Prusac

คำตอบ:


178

ฉันจะใช้คลาส Context คลาสเดียวที่สืบทอดจาก IdentityDbContext วิธีนี้คุณสามารถให้บริบทรับรู้ถึงความสัมพันธ์ระหว่างคลาสของคุณกับ IdentityUser และบทบาทของ IdentityDbContext มีค่าใช้จ่ายน้อยมากใน IdentityDbContext เป็นพื้น DbContext ปกติที่มี DbSets สองชุด หนึ่งสำหรับผู้ใช้และอีกหนึ่งสำหรับบทบาท


52
นั่นคือสำหรับโครงการ MVC5 เดียว แต่ไม่ต้องการเมื่อ DbContext ที่ได้รับนั้นถูกใช้ร่วมกันระหว่างโครงการหลายโครงการบางโครงการไม่ใช่ MVC5 ซึ่งบางโครงการไม่ต้องการการสนับสนุนข้อมูลประจำตัว
เดฟ

โหวตให้ฐานข้อมูลเดียวกันเพื่อการบำรุงรักษาที่ง่ายขึ้นและความสมบูรณ์ของความสัมพันธ์ที่ดีขึ้น เนื่องจากเอนทิตีผู้ใช้และเอนทิตีบทบาทจะเกี่ยวข้องกับแอปพลิเคชันวัตถุอื่นได้อย่างง่ายดาย
anIBMer

6
@Dave - มันซับซ้อนการแบ่งข้อมูลผู้ใช้โดยใช้บริบทที่แตกต่างกันสอง ข้อมูลพาร์ติชันแอป MVC ของคุณโดยผู้ใช้หรือไม่ แต่แอปอื่นไม่ทำงาน การแชร์เลเยอร์ข้อมูลเดียวกันเป็นเรื่องปกติ แต่ฉันไม่คิดว่ามันเป็นเรื่องธรรมดาที่บางโครงการต้องการข้อมูลที่แบ่งตามผู้ใช้และบางคนไม่ทำ
RickAndMSFT

1
มีใครทราบถึงวิธีการแยก ApplicationDBContext จากโครงการ MVC และรวมไว้ในชั้นข้อมูล EF ที่มีอยู่หรือไม่ การผสานทั้งสองอย่างตามที่อธิบายไว้ข้างต้นดูเหมือนจะเป็นแนวทางที่ถูกต้อง แต่ฉันกำลังทำงานในโครงการที่ จำกัด เวลา ฉันต้องการทำมันให้ถูกต้องตั้งแต่ครั้งแรกที่ผ่านมา แต่จะรักหัวขึ้นบน gotchas ทั้งหมดที่อยู่ข้างหน้าฉัน ...
Mike Devenney

7
หลังจากมองหาคำตอบนี้ประมาณหนึ่งชั่วโมงชี้ให้ฉันไปในทิศทางที่ถูกต้อง - แต่ฉันไม่แน่ใจว่าจะใช้มันอย่างไร (สำหรับคนที่แท้จริงฉัน) ดังนั้นหากช่วยคนอื่นฉันพบวิธีที่ง่ายที่สุดคือเปิด IdentityModels.cs และเพิ่ม DbSet ใหม่ของคุณในคลาส ApplicationDbContext
SeanOB

45

มีความสับสนมากมายเกี่ยวกับIdentityDbContextการค้นหาอย่างรวดเร็วใน Stackoverflow และคุณจะพบคำถามเหล่านี้:
" ทำไม Asp.Net Identity IdentityDbContext a Black-Box
ฉันจะเปลี่ยนชื่อตารางเมื่อใช้ 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 ของเราเรามีสองตัวเลือก: ตัวเลือก

แรก:
สร้าง DbContext ซึ่งสืบทอดจาก IdentityDbContext และเข้าถึงคลาส

   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 และปรับใช้ "OnModelCreating (ModelBuilder builder) รุ่น" ของเราจากซอร์สโค้ด IdentityDbContext


2
@ mike-devenney ต่อไปนี้เป็นคำตอบของคุณเกี่ยวกับการผสานสองบริบทเข้าด้วยกันหวังว่าจะช่วยได้
Arvand

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

หากคุณเจาะลึกลงไปใน abstractions ของ IdentityDbContext คุณจะพบว่ามันดูเหมือน DbContext ที่ได้รับของคุณ เส้นทางที่ง่ายที่สุดคือคำตอบ Olav แต่ถ้าคุณต้องการที่มากขึ้นสามารถควบคุมสิ่งที่ได้รับการสร้างขึ้นและน้อยพึ่งพาแพคเกจบัตรประจำตัวมีลักษณะที่คำถามและคำตอบของฉันที่นี่ มีตัวอย่างโค้ดหากคุณตามลิงก์ แต่โดยสรุปคุณเพียงแค่เพิ่ม DbSets ที่ต้องการลงในคลาสย่อย DbContext ของคุณเอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.