1. ホーム
  2. ef-code-first

[解決済み] EntityType 'IdentityUserLogin' にはキーが定義されていません。この EntityType のキーを定義してください。

2022-02-12 07:14:30

質問

私はEntity Framework Code FirstとMVC 5で作業しています。私は私のアプリケーションを 個別ユーザーアカウント認証 Account コントローラと、Indiv User Accounts 認証を動作させるために必要なすべてのクラスとコードを一緒に渡されました。

すでに用意されているコードの中には、こんなものがありました。

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つあるからでしょうか?

アップデイト

Augustoさんの返信を読んで、私は次のようにしました。 オプション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はpasswordプロパティと他のフィールドをたくさん持っているので、ユーザーにpasswordプロパティが必要かどうかわかりませんでした。

とにかく、上記の変更でうまくビルドできましたが、アプリケーションを実行すると、またしてもこのエラーが発生します。

無効なカラム名 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 そうすれば、すべてが理解できるはずです。

私は少し前にこの状況に遭遇し、3つの可能な解決策を見つけました(もっとあるかもしれません)。

  1. 2つの異なるデータベース、または同じデータベースで異なるテーブルに対して別々のDbContextを使用する。
  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();
    }
}

ここで、Userは

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とCode First Migrationsのためのマイグレーションを生成する必要があります。 次に Shailendra Chauhan の素晴らしい投稿に倣って、DXContext を作成しました。 複数のデータコンテキストを使用したコードファーストマイグレーション

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はすでに本番環境で使っています。

UPDATE: オプション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から派生したコンテキスト)にAutomatic Migrationsを使用しています。

public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
    public IdentityMigrationConfiguration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = false;
    }

    protected override void Seed(IdentityContext context)
    {
    }
}

この方法は、AspNetIdentity クラスを継承した n 層の横断的なエンティティを持つことを避けることができるという利点もあります。