Плутанина ASP.NET Identity DbContext


196

Програма MVC 5 за замовчуванням постачається з цим фрагментом коду в IdentityModels.cs - цей фрагмент коду призначений для всіх операцій з ідентифікацією ASP.NET для шаблонів за замовчуванням:

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, скажімо, наприклад, для моделі 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чого в свою чергу успадковуєDbContext . AllOtherStuffDbContext(для моїх власних речей) успадковує від DbContext.

Отже, моє питання:

Який із цих двох ( ApplicationDbContextі AllOtherStuffDbContext) я повинен використовувати для всіх своїх власних моделей? Або я повинен просто використовувати автогенерований за замовчуванням, ApplicationDbContextоскільки це не повинно бути проблемою з його використанням, оскільки він походить від базового класу DbContext, або будуть якісь накладні витрати? Ви повинні використовувати лише один DbContextоб’єкт у своєму додатку для всіх своїх моделей (я десь це читав), тому я навіть не повинен розглянути можливість використання обох ApplicationDbContextі AllOtherStuffDbContextв одному додатку? Або яка найкраща практика в MVC 5 з ідентифікацією ASP.NET?


1
До речі; це надзвичайно жорстко і не потрібно для моїх очей під час сканування документа: public System.Data.Entity.DbSet <WebApplication1.Models.Movie> Фільми {get; набір; } - частина System.Data.Entity та WebApplication1.Models. Неможливо його видалити з декларації та замість цього додати простори імен у розділ використовуються оператори?
PussInBoots

Puss - так на ваш коментар. Це повинно працювати добре.
SB2055

Це хороший і працездатний приклад (MVC 6) та реалізація рамки з рамкою ASP.NET 5 Identity (> = v3) без Entity Framework для MongoDB.Driver (> = v2.1.0) github.com/saan800/SaanSoft. AspNet.Identity3.MongoDB
Станіслав Прусак

Відповіді:


178

Я використовував би один клас контексту, успадкований від IdentityDbContext. Таким чином, ви можете бути в курсі будь-яких відносин між вашими класами та IdentityUser та ролями IdentityDbContext. У IdentityDbContext дуже мало накладних витрат, це в основному звичайний DbContext з двома DbSets. Один для користувачів та один для ролей.


52
Це стосується одного проекту MVC5, але не бажано, коли похідний DbContext ділиться між декількома проектами, а деякі не MVC5, де деяким не потрібна підтримка ідентичності.
Дейв

Голосували за ту саму базу даних для легшої ремонтопридатності та покращення цілісності реляції. Тому що сутність користувача та роль ролі будуть легко пов'язані з іншими об’єктами програми.
anIBMer

6
@Dave - це ускладнює розподіл даних користувачів за допомогою двох різних контекстів. Чи дані ваших розділів додатків MVC користувачем, але інші програми це не так. Спільний доступ до того ж шару даних є загальним, але я не думаю, що це звичайно, що для деяких проектів потрібні дані, що передаються користувачем, а деякі - ні.
RickAndMSFT

1
Хтось знає про те, як дістати ApplicationDBContext з проекту MVC та включити його у існуючий рівень даних EF? З'єднання двох, як описано вище, здається правильним підходом, але я працюю над обмеженим часом проектом. Я хочу зробити це правильно з першого разу, але я б хотіла підняти голову на всіх гатах, що лежать переді мною ...
Майк Девенні

7
Подивившись близько години, ця відповідь спрямовувала мене в правильному напрямку - але я не знав, як це здійснити (для мене дуже буквальна людина). Тож якщо це допомагає комусь іншому, я знайшов найпростіший спосіб - відкрити IdentityModels.cs і додати новий DbSet у клас ApplicationDbContext.
SeanOB

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, у нас є два варіанти:

Перший варіант:
Створіть 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 Розширюваний шаблон проекту для розширення всіх класів.

Другий варіант:(Не рекомендується)
Нам фактично не потрібно успадковувати IdentityDbContext, якщо ми пишемо весь код самі.
Таким чином, ми можемо просто успадкувати від DbContext і реалізувати нашу індивідуальну версію "OnModelCreating (Builder ModelBuilder)" з вихідного коду IdentityDbContext.


2
@ mike-devenney Ось ваша відповідь про об'єднання двох шарів контексту, сподіваюся, що це допоможе.
Арванд

1
Дякую Арванду, я пропустив це і, як не дивно, наткнувся на нього через 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. Найпростіший маршрут - це відповідь Олава, але якщо ви хочете більше контролювати те, що створюється, і трохи меншу залежність від пакетів ідентичності , перегляньте моє запитання та відповідь тут . Приклад коду є, якщо ви переходите за посиланням, але підсумовуючи, ви просто додаєте потрібні DbSets до власного підкласу DbContext.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.