Entity Framework Code FirstとMVC 5を使用しています。個人ユーザーアカウント認証を使用してアプリケーションを作成すると、アカウントコントローラーが与えられ、それに伴いIndivユーザーアカウント認証を機能させるために必要なすべての必要なクラスとコード。
すでに配置されているコードには次のものがあります。
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
しかし、その後、先に進み、最初にコードを使用して独自のコンテキストを作成したため、次のものもあります。
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
}
public DbSet<ApplicationUser> Users { get; set; }
public DbSet<IdentityRole> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Paintings> Paintings { get; set; }
}
最後に、開発中に使用するデータを追加する次のシードメソッドがあります。
protected override void Seed(DXContext context)
{
try
{
if (!context.Roles.Any(r => r.Name == "Admin"))
{
var store = new RoleStore<IdentityRole>(context);
var manager = new RoleManager<IdentityRole>(store);
var role = new IdentityRole { Name = "Admin" };
manager.Create(role);
}
context.SaveChanges();
if (!context.Users.Any(u => u.UserName == "James"))
{
var store = new UserStore<ApplicationUser>(context);
var manager = new UserManager<ApplicationUser>(store);
var user = new ApplicationUser { UserName = "James" };
manager.Create(user, "ChangeAsap1@");
manager.AddToRole(user.Id, "Admin");
}
context.SaveChanges();
string userId = "";
userId = context.Users.FirstOrDefault().Id;
var artists = new List<Artist>
{
new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://i62.tinypic.com/ss8txxn.jpg", UrlFriendly = "salvador-dali", Verified = true, ApplicationUserId = userId },
};
artists.ForEach(a => context.Artists.Add(a));
context.SaveChanges();
var paintings = new List<Painting>
{
new Painting { Title = "The Persistence of Memory", ImgUrl = "http://i62.tinypic.com/xx8tssn.jpg", ArtistId = 1, Verified = true, ApplicationUserId = userId }
};
paintings.ForEach(p => context.Paintings.Add(p));
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
私のソリューションはうまく構築されていますが、データベースへのアクセスを必要とするコントローラーにアクセスしようとすると、次のエラーが表示されます。
DX.DOMAIN.Context.IdentityUserLogin::EntityType 'IdentityUserLogin'にはキーが定義されていません。このEntityTypeのキーを定義します。
DX.DOMAIN.Context.IdentityUserRole::EntityType 'IdentityUserRole'にはキーが定義されていません。このEntityTypeのキーを定義します。
何が間違っていますか?コンテキストが2つあるからですか?
UPDATE
Augustoの返事を読んだ後、Option 3を使いました。 DXContextクラスは次のようになります。
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
// remove default initializer
Database.SetInitializer<DXContext>(null);
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Painting> Paintings { get; set; }
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<Role>().ToTable("Roles");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
User.cs
クラスとRole.cs
クラスも追加しました。これらは次のようになります。
public class User
{
public int Id { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
public class Role
{
public int Id { set; get; }
public string Name { set; get; }
}
デフォルトのApplicationUserにはそれと他のフィールドがたくさんあるので、ユーザーのパスワードプロパティが必要かどうかわかりませんでした!
とにかく、上記の変更は問題なくビルドされますが、アプリケーションの実行時に再びこのエラーが発生します。
無効な列名UserId
UserId
はArtist.cs
の整数プロパティです
問題は、ApplicationUserがIdentityUserから継承するであり、これは次のように定義されていることです。
IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser
....
public virtual ICollection<TRole> Roles { get; private set; }
public virtual ICollection<TClaim> Claims { get; private set; }
public virtual ICollection<TLogin> Logins { get; private set; }
そして、それらの主キーはメソッドにマッピングされますクラスIdentityDbContextのOnModelCreating:
modelBuilder.Entity<TUserRole>()
.HasKey(r => new {r.UserId, r.RoleId})
.ToTable("AspNetUserRoles");
modelBuilder.Entity<TUserLogin>()
.HasKey(l => new {l.LoginProvider, l.ProviderKey, l.UserId})
.ToTable("AspNetUserLogins");
dXContextは派生しないため、これらのキーは定義されません。
Microsoft.AspNet.Identity.EntityFramework
の sources を調べると、すべてを理解できます。
しばらく前にこの状況に遭遇しましたが、考えられる解決策は3つありました(おそらく他にもあります)。
オプション1:下部の更新を参照してください。
オプション2:このようなDbContextになります。
public class DXContext : IdentityDbContext<User, Role,
int, UserLogin, UserRole, UserClaim>//: DbContext
{
public DXContext()
: base("name=DXContext")
{
Database.SetInitializer<DXContext>(null);// Remove default initializer
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
public static DXContext Create()
{
return new DXContext();
}
//Identity and Authorization
public DbSet<UserLogin> UserLogins { get; set; }
public DbSet<UserClaim> UserClaims { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
// ... your custom DbSets
public DbSet<RoleOperation> RoleOperations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// Configure Asp Net Identity Tables
modelBuilder.Entity<User>().ToTable("User");
modelBuilder.Entity<User>().Property(u => u.PasswordHash).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.Stamp).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.PhoneNumber).HasMaxLength(50);
modelBuilder.Entity<Role>().ToTable("Role");
modelBuilder.Entity<UserRole>().ToTable("UserRole");
modelBuilder.Entity<UserLogin>().ToTable("UserLogin");
modelBuilder.Entity<UserClaim>().ToTable("UserClaim");
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimType).HasMaxLength(150);
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimValue).HasMaxLength(500);
}
}
オプション3:オプション2と等しいDbContextが1つあります。これをIdentityContextと名付けましょう。そして、DXContextと呼ばれる別のDbContextがあります。
public class DXContext : DbContext
{
public DXContext()
: base("name=DXContext") // connection string in the application configuration file.
{
Database.SetInitializer<DXContext>(null); // Remove default initializer
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
// Domain Model
public DbSet<User> Users { get; set; }
// ... other custom DbSets
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser
modelBuilder.Entity<User>().ToTable("User");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
ユーザーは次のとおりです。
public class User
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
[Required, StringLength(128)]
public string SomeOtherColumn { get; set; }
}
このソリューションでは、エンティティUserをエンティティApplicationUserと同じテーブルにマッピングしています。
次に、Code First Migrationsを使用して、IdentityContextの移行と、DXContextのTHENを生成する必要があります。 Chauhan: 複数のデータコンテキストを使用したCode First Migrations
DXContext用に生成された移行を変更する必要があります。 ApplicationUserとUserの間で共有されるプロパティに応じて、次のようになります。
//CreateTable(
// "dbo.User",
// c => new
// {
// Id = c.Int(nullable: false, identity: true),
// Name = c.String(nullable: false, maxLength: 100),
// SomeOtherColumn = c.String(nullable: false, maxLength: 128),
// })
// .PrimaryKey(t => t.Id);
AddColumn("dbo.User", "SomeOtherColumn", c => c.String(nullable: false, maxLength: 128));
次に、このカスタムクラスを使用してglobal.asaxまたはアプリケーションの他の場所から移行を順番に実行します(最初にID移行)。
public static class DXDatabaseMigrator
{
public static string ExecuteMigrations()
{
return string.Format("Identity migrations: {0}. DX migrations: {1}.", ExecuteIdentityMigrations(),
ExecuteDXMigrations());
}
private static string ExecuteIdentityMigrations()
{
IdentityMigrationConfiguration configuration = new IdentityMigrationConfiguration();
return RunMigrations(configuration);
}
private static string ExecuteDXMigrations()
{
DXMigrationConfiguration configuration = new DXMigrationConfiguration();
return RunMigrations(configuration);
}
private static string RunMigrations(DbMigrationsConfiguration configuration)
{
List<string> pendingMigrations;
try
{
DbMigrator migrator = new DbMigrator(configuration);
pendingMigrations = migrator.GetPendingMigrations().ToList(); // Just to be able to log which migrations were executed
if (pendingMigrations.Any())
migrator.Update();
}
catch (Exception e)
{
ExceptionManager.LogException(e);
return e.Message;
}
return !pendingMigrations.Any() ? "None" : string.Join(", ", pendingMigrations);
}
}
このように、n層のクロスカットエンティティは最終的にAspNetIdentityクラスから継承されないため、使用するすべてのプロジェクトでこのフレームワークをインポートする必要はありません。
広範囲にわたる投稿で申し訳ありません。これに関するガイダンスを提供できるといいのですが。実稼働環境では、オプション2と3をすでに使用しています。
更新:オプション1を展開
最後の2つのプロジェクトでは、1番目のオプションを使用しました。IdentityUserから派生するAspNetUserクラスと、AppUserという別のカスタムクラスを使用します。私の場合、DbContextsはそれぞれIdentityContextとDomainContextです。そして、次のようにAppUserのIDを定義しました。
public class AppUser : TrackableEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
// This Id is equal to the Id in the AspNetUser table and it's manually set.
public override int Id { get; set; }
(TrackableEntityは、DomainContextコンテキストのオーバーライドされたSaveChangesメソッドで使用するカスタム抽象基本クラスです)
最初にAspNetUserを作成してから、AppUserを作成します。このアプローチの欠点は、 "CreateUser"機能がトランザクション対応であることです(SaveChangesを個別に呼び出す2つのDbContextがあることに注意してください)。何らかの理由でTransactionScopeを使用しても機能しなかったので、やっかいなことになりましたが、それは私には機能します。
IdentityResult identityResult = UserManager.Create(aspNetUser, model.Password);
if (!identityResult.Succeeded)
throw new TechnicalException("User creation didn't succeed", new LogObjectException(result));
AppUser appUser;
try
{
appUser = RegisterInAppUserTable(model, aspNetUser);
}
catch (Exception)
{
// Roll back
UserManager.Delete(aspNetUser);
throw;
}
(誰かがこの部分を行うためのより良い方法を持っているなら、私はこの答えにコメントするか編集を提案することに感謝します)
利点は、移行を変更する必要がなく、できることですAspNetUserをいじらずに、AppUserでクレイジーな継承階層を使用する。そして実際には、IdentityContext(IdentityDbContextから派生したコンテキスト)に自動移行を使用します。
public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
public IdentityMigrationConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
}
protected override void Seed(IdentityContext context)
{
}
}
また、このアプローチには、n層のクロスカットエンティティがAspNetIdentityクラスから継承することを回避できるという利点もあります。
私の場合、IdentityDbContextから(自分のカスタムタイプとキーを定義して)正しく継承しましたが、基本クラスのOnModelCreatingの呼び出しを誤って削除しました:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); // I had removed this
/// Rest of on model creating here.
}
これにより、IDクラスから欠落しているインデックスが修正され、移行を生成して、移行を適切に有効化できました。
ASP.NET Identity 2.1を使用しており、主キーをデフォルトのstring
からint
またはGuid
のいずれかに変更した場合、まだ取得している場合
EntityType 'xxxxUserLogin'にはキーが定義されていません。このEntityTypeのキーを定義します。
EntityType 'xxxxUserRole'にはキーが定義されていません。このEntityTypeのキーを定義します。
おそらくIdentityDbContext
に新しいキータイプを指定するのを忘れただけです。
public class AppIdentityDbContext : IdentityDbContext<
AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
public AppIdentityDbContext()
: base("MY_CONNECTION_STRING")
{
}
......
}
持っているだけなら
public class AppIdentityDbContext : IdentityDbContext
{
......
}
あるいは
public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
......
}
移行を追加またはデータベースを更新しようとすると、「キーが定義されていません」というエラーが表示されます。
以下のようにDbContextを変更します。
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
OnModelCreating
メソッド呼び出しをbase.OnModelCreating(modelBuilder);に追加するだけです。そしてそれはうまくなります。 EF6を使用しています。
#The Senatorに感謝
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
// relationship.DeleteBehavior = DeleteBehavior.Restrict;
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<IdentityRole<string>>().ToTable("Roles");
modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("RoleClaims");
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRoles");
}
}
私の問題は似ていました-アイデンティティユーザーに結びつけるために作成した新しいテーブルがありました。上記の回答を読んだ後、IsdentityUserと継承されたプロパティに関係していることに気付きました。 Identityを独自のコンテキストとして設定済みであるため、関連するユーザーテーブルを真のEFプロパティとして使用するのではなく、本質的に2つを結び付けることを避けるために、クエリでマップされていないプロパティを設定して関連するエンティティを取得します。 (DataManagerは、OtherEntityが存在する現在のコンテキストを取得するように設定されています。)
[Table("UserOtherEntity")]
public partial class UserOtherEntity
{
public Guid UserOtherEntityId { get; set; }
[Required]
[StringLength(128)]
public string UserId { get; set; }
[Required]
public Guid OtherEntityId { get; set; }
public virtual OtherEntity OtherEntity { get; set; }
}
public partial class UserOtherEntity : DataManager
{
public static IEnumerable<OtherEntity> GetOtherEntitiesByUserId(string userId)
{
return Connect2Context.UserOtherEntities.Where(ue => ue.UserId == userId).Select(ue => ue.OtherEntity);
}
}
public partial class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> 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;
}
[NotMapped]
public IEnumerable<OtherEntity> OtherEntities
{
get
{
return UserOtherEntities.GetOtherEntitiesByUserId(this.Id);
}
}
}