Путаница в ASP.NET Identity DbContext

Приложение 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 Identity?


person PussInBoots    schedule 11.11.2013    source источник
comment
Кстати; это сверхъестественно и не нужно для моих глаз при сканировании документа: public System.Data.Entity.DbSet ‹WebApplication1.Models.Movie› Movies {get; установленный; } - часть System.Data.Entity и WebApplication1.Models. Нельзя ли удалить его из объявления и вместо этого добавить пространства имен в раздел using?   -  person PussInBoots    schedule 11.11.2013
comment
Кот - да на ваш комментарий. Это должно работать нормально.   -  person SB2055    schedule 09.01.2014
comment
Это хороший и рабочий пример (MVC 6) и библиотека реализации с фреймворком ASP.NET 5 Identity (›= v3) без Entity Framework для MongoDB.Driver (› = v2.1.0) github.com/saan800/SaanSoft.AspNet.Identity3.MongoDB   -  person Stanislav Prusac    schedule 20.09.2015


Ответы (4)


Я бы использовал один класс Context, унаследованный от IdentityDbContext. Таким образом, вы можете получить информацию о контексте любых отношений между вашими классами и IdentityUser и ролями IdentityDbContext. В IdentityDbContext очень мало накладных расходов, в основном это обычный DbContext с двумя DbSet. Один для пользователей и один для ролей.

person Olav Nybø    schedule 11.11.2013
comment
Это для одного проекта MVC5, но нежелательно, когда производный DbContext используется совместно несколькими проектами, некоторые из которых не являются MVC5, а некоторым не требуется поддержка удостоверений. - person Dave; 19.02.2014
comment
Проголосовал за ту же базу данных для упрощения обслуживания и лучшей реляционной целостности. Потому что сущность пользователя и сущность роли будут легко связаны с другими объектами приложения. - person anIBMer; 11.03.2014
comment
@Dave - усложняет разделение пользовательских данных за счет использования двух разных контекстов. Разделяет ли ваше приложение MVC данные по пользователям, а другие - нет. Совместное использование одного и того же уровня данных является обычным явлением, но я не думаю, что это обычное явление, когда некоторые проекты нуждаются в данных, разделенных пользователем, а некоторые нет. - person RickAndMSFT; 22.02.2015
comment
Кто-нибудь знает, как извлечь ApplicationDBContext из проекта MVC и включить его в существующий уровень данных EF? Слияние двух, как описано выше, кажется правильным подходом, но я работаю над проектом с ограниченным временем. Я хочу сделать это правильно с первого раза, но хотел бы знать обо всех подводных камнях, которые лежат передо мной ... - person Mike Devenney; 20.03.2015
comment
После часа поиска этот ответ указал мне в правильном направлении, но я не знал, как его реализовать (для очень буквального человека). Итак, если это поможет кому-то другому, я обнаружил, что самый простой способ - открыть IdentityModels.cs и добавить новый DbSet в класс ApplicationDbContext. - person SeanOB; 25.04.2015
comment
Обратите внимание, это будет означать, что вы не можете использовать EFHooks или что-то еще, что использует ссылку наследования. - person George Mauer; 27.04.2015

Существует много путаницы по поводу 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 (ModelBuilder builder)» из Исходный код IdentityDbContext

person Arvand    schedule 01.03.2016
comment
@ mike-devenney Вот ваш ответ о слиянии двух слоев контекста, надеюсь, это поможет. - person Arvand; 01.03.2016
comment
Спасибо, Арванд, я пропустил это и, как ни странно, наткнулся на это через 1,5 года, снова заглядывая в эту тему. :) - person Mike Devenney; 10.11.2016

Это поздний ввод для людей, но ниже моя реализация. Вы также заметите, что я отключил возможность изменения типа 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
    }
person Prisoner ZERO    schedule 18.06.2015

Если вы углубитесь в абстракции IdentityDbContext, вы обнаружите, что он выглядит так же, как ваш производный DbContext. Самый простой путь - это ответ Олафа, но если вам нужен больший контроль над тем, что создается, и меньшая зависимость от пакетов Identity взгляните на мой вопрос и ответьте здесь. Если вы перейдете по ссылке, есть пример кода, но в итоге вы просто добавляете необходимые DbSets в свой собственный подкласс DbContext.

person joelmdev    schedule 18.11.2013