Идентификация ASP.NET DbContext путаница
По умолчанию приложение 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, скажем, например, для модели 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
Я бы использовал один класс Context, наследующий от IdentityDbContext.
Таким образом, вы можете иметь контекст, который должен знать любые отношения между вашими классами и IdentityUser и Роли IdentityDbContext.
В IdentityDbContext очень мало накладных расходов, в основном это обычный DbContext с двумя DbSets. Один для пользователей и один для ролей.
Ответ 2
Существует много путаницы в 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
Ответ 3
Если вы развернете абстракции IdentityDbContext, вы обнаружите, что он выглядит так же, как ваш производный DbContext. Самый простой маршрут - ответ Олава, но если вы хотите получить больше контроля над тем, что создается и немного меньше зависит от пакетов Identity посмотрите мой вопрос и ответ здесь. Там пример кода, если вы следуете ссылке, но в итоге вы просто добавляете необходимые DbSets в свой собственный подкласс DbContext.
Ответ 4
Это поздняя запись для людей, но ниже моя реализация. Вы также заметите, что я удалил возможность изменить тип по умолчанию 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
}