ASP.NET Identity DbContext kebingungan

196

Aplikasi MVC 5 default dilengkapi dengan kode ini di IdentityModels.cs - kode ini untuk semua operasi ASP.NET Identity untuk templat default:

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

Jika saya merancah pengontrol baru menggunakan tampilan dengan Entity Framework dan membuat "konteks data baru ..." dalam dialog, saya mendapatkan ini dihasilkan untuk saya:

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; }

    }
} 

Jika saya membuat scaffold pengontrol + tampilan lain menggunakan EF, misalnya untuk model Animal, baris baru ini akan di-autogenerasi tepat di bawah public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }- seperti ini:

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(untuk semua hal ASP.NET Identity) mewarisi dari IdentityDbContextmana pada gilirannya mewarisi dari DbContext. AllOtherStuffDbContext(untuk barang-barang saya sendiri) mewarisi dari DbContext.

Jadi pertanyaan saya adalah:

Manakah dari dua ini ( ApplicationDbContextdan AllOtherStuffDbContext) yang harus saya gunakan untuk semua model saya yang lain? Atau haruskah saya hanya menggunakan autogenerated default ApplicationDbContextkarena seharusnya tidak menjadi masalah menggunakannya karena berasal dari kelas dasar DbContext, atau akankah ada beberapa overhead? Anda harus menggunakan hanya satu DbContextobjek di aplikasi Anda untuk semua model Anda (saya pernah membaca ini di suatu tempat) jadi saya bahkan tidak perlu mempertimbangkan untuk menggunakan keduanya ApplicationDbContextdan AllOtherStuffDbContextdalam satu aplikasi? Atau apa praktik terbaik di MVC 5 dengan ASP.NET Identity?

Kucing dalam sepatu
sumber
1
Ngomong-ngomong; ini luar biasa dan tidak perlu untuk mata saya saat memindai dokumen: public System.Data.Entity.DbSet <WebApplication1.Models.Movie> Movies {get; set; } - bagian System.Data.Entity dan WebApplication1.Models. Tidak bisakah itu dihapus dari deklarasi dan sebagai gantinya menambahkan namespaces di bagian menggunakan pernyataan?
PussInBoots
Kucing - ya komentar Anda. Itu seharusnya bekerja dengan baik.
SB2055
Ini adalah contoh yang baik dan berfungsi, (MVC 6) dan lib implementasi dengan kerangka kerja ASP.NET 5 Identity (> = v3) tanpa Kerangka Entitas untuk MongoDB.Driver (> = v2.1.0) github.com/saan800/SaanSoft. AspNet.Identity3.MongoDB
Stanislav Prusac

Jawaban:

178

Saya akan menggunakan kelas konteks tunggal mewarisi dari IdentityDbContext. Dengan cara ini Anda dapat membuat konteks menyadari setiap hubungan antara kelas Anda dan IdentityUser dan Peran dari IdentityDbContext. Ada sedikit sekali overhead dalam IdentityDbContext, ini pada dasarnya adalah DbContext biasa dengan dua DbSet. Satu untuk pengguna dan satu untuk peran.

Olav Nybø
sumber
52
Itu untuk proyek MVC5 tunggal tetapi tidak diinginkan ketika DbContext turunan dibagi di antara beberapa proyek, beberapa bukan MVC5, di mana beberapa tidak memerlukan dukungan Identity.
Dave
Dipilih untuk database yang sama untuk kemudahan pemeliharaan dan integritas hubungan yang lebih baik. Karena entitas pengguna dan entitas peran akan terkait dengan objek aplikasi lain dengan mudah.
anIBMer
6
@Dave - Ini menyulitkan pemartisian data pengguna dengan menggunakan dua konteks yang berbeda. Apakah data partisi aplikasi MVC Anda oleh pengguna tetapi aplikasi lain tidak. Berbagi lapisan data yang sama adalah hal biasa, tetapi saya tidak berpikir itu umum bahwa beberapa proyek memerlukan data yang dibagi oleh pengguna, dan beberapa tidak.
RickAndMSFT
1
Adakah yang tahu tentang cara mengekstraksi ApplicationDBContext dari proyek MVC dan memasukkannya ke dalam lapisan data EF yang ada? Menggabungkan keduanya, seperti dijelaskan di atas tampaknya menjadi pendekatan yang tepat, tetapi saya sedang mengerjakan proyek yang dibatasi waktu. Saya ingin melakukannya dengan benar pertama kali melalui tetapi akan senang dengan semua gotcha yang ada di depan saya ...
Mike Devenney
7
Setelah mencari sekitar satu jam, jawaban ini mengarahkan saya ke arah yang benar - tetapi saya tidak yakin bagaimana cara mengimplementasikannya (untuk orang yang sangat literal saya). Jadi jika itu membantu orang lain, saya menemukan cara paling sederhana adalah buka IdentityModels.cs, dan tambahkan DbSet baru Anda di kelas ApplicationDbContext.
SeanOB
45

Ada banyak kebingungan tentang IdentityDbContext , pencarian cepat di Stackoverflow dan Anda akan menemukan pertanyaan-pertanyaan ini:
" Mengapa Asp.Net Identity IdentityDbContext Black-Box?
Bagaimana saya bisa mengubah nama tabel saat menggunakan Visual Studio 2013 AspNet Identity?
Gabungkan MyDbContext dengan IdentityDbContext "

Untuk menjawab semua pertanyaan ini, kita perlu memahami bahwa IdentityDbContext hanyalah kelas yang diwarisi dari DbContext.
Mari kita lihat sumber 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");
        });
    }
}


Berdasarkan kode sumber jika kita ingin menggabungkan IdentityDbContext dengan DbContext kita, kita memiliki dua opsi:

Opsi Pertama:
Membuat DbContext yang mewarisi dari IdentityDbContext dan memiliki akses ke kelas.

   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
}


Catatan tambahan:

1) Kami juga dapat mengubah nama tabel asp.net Identity default dengan solusi berikut:

    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) Selanjutnya kami dapat memperluas setiap kelas dan menambahkan properti apa pun ke kelas seperti '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
}

Untuk menghemat waktu, kita dapat menggunakan AspNet Identity 2.0 Extensible Project Template untuk memperluas semua kelas.

Opsi kedua:(Tidak disarankan)
Kita sebenarnya tidak harus mewarisi dari IdentityDbContext jika kita menulis sendiri semua kodenya.
Jadi pada dasarnya kita bisa mewarisi dari DbContext dan mengimplementasikan versi "OnModelCreating (pembangun ModelBuilder)" dari kode sumber IdentityDbContext kami

Arvand
sumber
2
@ mike-devenney Inilah jawaban Anda tentang menggabungkan dua lapisan konteks, semoga membantu.
Arvand
1
Terima kasih Arvand, saya melewatkan ini dan anehnya menemukan kembali 1,5 tahun kemudian sambil melihat ke topik lagi. :)
Mike Devenney
9

Ini adalah entri terlambat untuk orang-orang, tetapi di bawah ini adalah implementasi saya Anda juga akan melihat saya mematikan kemampuan untuk mengubah jenis standar KEYs: detail tentang yang dapat ditemukan di artikel berikut:

CATATAN:
Perlu dicatat bahwa Anda tidak dapat menggunakan Guid'skunci Anda. Ini karena di bawah tenda mereka adalah Struct, dan dengan demikian, tidak memiliki unboxing yang akan memungkinkan konversi mereka dari generik<TKey> parameter .

KELAS TERLIHAT SEPERTI:

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
    }
Tahanan NOL
sumber
8

Jika Anda menelusuri abstraksi dari IdentityDbContext Anda akan menemukan bahwa itu terlihat seperti DbContext Anda. Rute termudah adalah jawaban Olav, tetapi jika Anda ingin lebih mengontrol apa yang sedang dibuat dan sedikit ketergantungan pada paket - paket Identitas, lihat pertanyaan dan jawaban saya di sini . Ada contoh kode jika Anda mengikuti tautan, tetapi dalam ringkasan Anda hanya menambahkan DbSet yang diperlukan ke subkelas DbContext Anda sendiri.

joelmdev
sumber