EntityType 'IdentityUserLogin'にはキーが定義されていません。このEntityTypeのキーを定義します


105

私はエンティティフレームワークコードファーストとMVC 5を使用しています。個人ユーザーアカウント認証使用してアプリケーションを作成すると、アカウントコントローラーと、それに加えて、個人ユーザーアカウント認証を機能させるために必要なすべての必要なクラスとコードが与えられました。

すでに配置されているコードには、次のものがあります。

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つのコンテキストがあるからですか?

更新

アウグストの返信を読んだ後、私はオプション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.csRole.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

回答:


116

問題は、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、すべてを理解できます。

私はいつかこの状況に遭遇しました、そして私は3つの可能な解決策を見つけました(多分もっとあります):

  1. 2つの異なるデータベース、または同じデータベースだが異なるテーブルに対して、別々のDbContextsを使用します。
  2. DXContextをApplicationDbContextとマージし、1つのデータベースを使用します。
  3. 同じテーブルに対して個別のDbContextを使用し、それに応じて移行を管理します。

オプション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と同じテーブルにマッピングしています。

次に、コードファーストマイグレーションを使用して、IdentityContextのマイグレーションとDXContextのTHENのマイグレーションを生成する必要があります。これは、Shailendra Chauhanからのこの素晴らしい投稿に従ってください。複数のデータコンテキストを使用したコードファーストマイグレーション

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またはアプリケーションの他の場所から順番に(最初にIdentityの移行)移行を実行します。

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つのプロジェクトでは、最初のオプションを使用しました。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)
    {
    }
}

このアプローチには、AspNetIdentityクラスから継承するn層のクロスカッティングエンティティを回避するという利点もあります。


広範な投稿をありがとう@Augusto。一つはないしなければならない仕事にオプション3を取得するために、移行を使うのか?私の知る限り、EF移行は変更をロールバックするためのものですか?データベースを削除してから再作成し、新しいビルドごとにシードする場合、すべての移行を行う必要がありますか?
J86 2015

マイグレーションを使わずに試してみませんでした。あなたがそれらを使わずにそれを達成できるかどうか私は知りません。多分それは可能です。データベースに挿入されたカスタムデータを保持するには、常に移行を使用する必要がありました。
アウグストバレット2015

AddOrUpdate(new EntityObject { shoes = green})注目すべき点の1つは、Migrationsを使用する場合は、「upsert」としても知られているものを使用する必要があります。単にコンテキストに追加するのではなく、それ以外の場合は、重複/冗長エンティティコンテキスト情報を作成します。
Chef_Code 2016

3番目のオプションを使用したいのですが、うまくいきません。IdentityContextがどのように見えるべきかを誰かに教えてもらえますか?オプション2とまったく同じにすることはできません。@AugustoBarretoを手伝ってくれませんか?私は同様のことについてスレッドを作成しました。多分あなたは私を助けることができます
Arianit '27

「TrackableEntity」はどのように見えますか?
Ciaran Gallagher

224

私の場合、IdentityDbContextから(自分のカスタムタイプとキーを定義して)正しく継承しましたが、基本クラスのOnModelCreatingへの呼び出しを誤って削除しました。

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder); // I had removed this
    /// Rest of on model creating here.
}

次に、IDクラスから欠落しているインデックスを修正し、移行を生成して適切に移行を有効にしました。


同じ問題が「行を削除」した。ソリューションが機能しました。:) ty。
開発者MariusŽilėnas16年

2
これにより、複雑なエンティティの関係に対してFluent APIを使用してカスタムマッピングを含めるためにOnModelCreatingメソッドをオーバーライドする必要があった私の問題が修正されました。Identityと同じコンテキストを使用しているため、マッピングを宣言する前に回答に行を追加するのを忘れていました。乾杯。
ダン・

「override void OnModelCreating」がない場合は機能しますが、オーバーライドする場合は「base.OnModelCreating(modelBuilder);」を追加する必要があります。オーバーライドします。私の問題を修正しました。
ジョー

13

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>
{
    ......
}

マイグレーションを追加したり、データベースを更新しようとすると、「キーが定義されていません」というエラーが表示されます。


また、IDをIntに変更しようとしてこの問題が発生していますが、DbContextを変更して新しいキータイプを指定しています。他に確認すべき場所はありますか?私は指示に非常に注意深く従っていると思いました。
カイル

1
@Kyle:すべてのエンティティのIDをint、つまりAppRole、AppUser、AppUserClaim、AppUserLogin、AppUserRoleに変更しようとしていますか?その場合は、それらのクラスに新しいキータイプを指定したことを確認する必要がある場合もあります。'public class AppUserLogin:IdentityUserLogin <int> {}'のように
David Liang

1
これは、主キーのデータ型のカスタマイズに関する公式ドキュメントです。docs.microsoft.com
us

1
はい、私の問題は、IdentityDbContext <AppUser>ではなく、一般的なDbContextクラスから継承したことでした。おかげで、これは大いに役立った
yibe

13

以下のようにDbContextを変更します。

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
    }

OnModelCreatingbase.OnModelCreating(modelBuilder);へのメソッド呼び出しを追加するだけです。そしてそれは元気になります。EF6を使用しています。

#The Senatorへの特別な感謝


1
 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");

        }
    }

0

私の問題も同様でした-私が作成した新しいテーブルを使用して、IDユーザーに関連付けることができました。上記の回答を読んだ後、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);
            }
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.