EntityType 'IdentityUserLogin' tanımlı bir anahtar içermiyor. Bu EntityType için anahtarı tanımlayın


105

Entity Framework Code First ve MVC 5 ile çalışıyorum. Uygulamamı Individual User Accounts Authentication ile oluşturduğumda , bir Hesap denetleyicisi ve bununla birlikte Bireysel Kullanıcı Hesapları kimlik doğrulamasının çalışması için gerekli tüm gerekli sınıflar ve kodlar verildi .

Zaten mevcut olan kodlar arasında şunlar vardı:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false)
    {

    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}

Ama sonra devam ettim ve önce kodu kullanarak kendi bağlamımı oluşturdum, bu yüzden şimdi aşağıdakilere de sahibim:

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; }        
}

Son olarak, geliştirme sırasında üzerinde çalışmam için bazı veriler eklemek için aşağıdaki tohum yöntemine sahibim:

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);
            }
        }
    }
    
}

Çözümüm iyi inşa ediliyor, ancak veritabanına erişim gerektiren bir denetleyiciye erişmeyi denediğimde aşağıdaki hatayı alıyorum:

DX.DOMAIN.Context.IdentityUserLogin:: EntityType 'IdentityUserLogin' tanımlı anahtar içermiyor. Bu EntityType için anahtarı tanımlayın.

DX.DOMAIN.Context.IdentityUserRole:: EntityType 'IdentityUserRole' tanımlı anahtar içermiyor. Bu EntityType için anahtarı tanımlayın.

Neyi yanlış yapıyorum? İki bağlamım olduğu için mi?

GÜNCELLEME

Augusto'nun cevabını okuduktan sonra Seçenek 3'e geçtim . DXContext sınıfım şu anda şöyle görünüyor:

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();
    }
}

Ayrıca bir User.csve bir Role.cssınıf ekledim , şöyle görünüyorlar:

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; }
}

Varsayılan ApplicationUser'da bu ve bir sürü başka alan olduğundan, kullanıcı için bir şifre özelliğine ihtiyacım olup olmayacağından emin değildim!

Her neyse, yukarıdaki değişiklik iyi yapılıyor, ancak uygulama çalıştırıldığında yine bu hatayı alıyorum:

Geçersiz Sütun adı UserId

UserId benim tam sayı özelliğidir Artist.cs

Yanıtlar:


116

Sorun, ApplicationUser'ınızın şu şekilde tanımlanan IdentityUser'dan miras almasıdır :

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; }

ve birincil anahtarları IdentityDbContext sınıfının OnModelCreating yönteminde eşlenir :

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

ve DXContext'iniz ondan türetilmediğinden, bu anahtarlar tanımlanmaz.

Eğer içine kazmak ise kaynaklardan arasında Microsoft.AspNet.Identity.EntityFramework, her şeyi anlayacaksınız.

Bu durumla bir süre önce karşılaştım ve üç olası çözüm buldum (belki daha fazlası vardır):

  1. İki farklı veritabanında veya aynı veritabanında ancak farklı tablolarda ayrı DbContexts kullanın.
  2. DXContext'inizi ApplicationDbContext ile birleştirin ve bir veritabanı kullanın.
  3. Aynı tablo için ayrı DbContexts kullanın ve geçişlerini buna göre yönetin.

1. Seçenek: Alt kısmı güncellemeye bakın.

Seçenek 2: Bunun gibi bir DbContext elde edeceksiniz:

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);
    }
}

Seçenek 3: Seçenek 2'ye eşit bir DbContext'e sahip olacaksınız. Bunu IdentityContext olarak adlandıralım. Ve DXContext adında başka bir DbContext'e sahip olacaksınız:

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();
    }
}

Kullanıcı nerede:

public class User
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; }

    [Required, StringLength(128)]
    public string SomeOtherColumn { get; set; }
}

Bu çözümle, User varlığını ApplicationUser varlığı ile aynı tabloya eşleştiriyorum.

Ardından, Code First Migrations'ı kullanarak , Shailendra Chauhan'ın şu harika gönderisini izleyerek IdentityContext ve THEN için DXContext için geçişleri oluşturmanız gerekir : Birden Çok Veri Bağlamıyla Kod İlk Geçişler

DXContext için oluşturulan geçişi değiştirmeniz gerekecektir. ApplicationUser ve User arasında hangi özelliklerin paylaşıldığına bağlı olarak bunun gibi bir şey:

        //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));

ve sonra bu özel sınıfı kullanarak global.asax veya uygulamanızın başka herhangi bir yerinden sırayla (ilk olarak Kimlik geçişleri) geçişleri çalıştırın:

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);
    }
}

Bu şekilde, n katmanlı kesişen varlıklarım AspNetIdentity sınıflarından miras almak zorunda kalmaz ve bu nedenle bu çerçeveyi kullandığım her projede içe aktarmak zorunda kalmam.

Kapsamlı gönderi için özür dilerim. Umarım bu konuda biraz yol gösterici olabilir. Üretim ortamlarında 2. ve 3. seçenekleri zaten kullandım.

GÜNCELLEME: 1. Seçeneği Genişletin

Son iki proje için 1. seçeneği kullandım: IdentityUser'dan türetilen bir AspNetUser sınıfına ve AppUser adlı ayrı bir özel sınıfa sahip olmak. Benim durumumda, DbContexts sırasıyla IdentityContext ve DomainContext'dir. Ve AppUser'ın kimliğini şu şekilde tanımladım:

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 bağlamımın geçersiz kılınan SaveChanges yönteminde kullandığım özel soyut temel sınıftır)

Önce AspNetUser'ı ve ardından AppUser'ı oluşturuyorum. Bu yaklaşımın dezavantajı, "CreateUser" işlevselliğinizin işlemsel olduğundan emin olmanızdır (SaveChanges'ı ayrı ayrı çağıran iki DbContexts olacağını unutmayın). TransactionScope'u kullanmak bazı nedenlerden dolayı benim için işe yaramadı, bu yüzden çirkin bir şey yaptım ama bu benim için çalışıyor:

        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;
        }

(Lütfen, birisi bu bölümü yapmanın daha iyi bir yolunu bulursa, bu cevaba yorum yapmaktan veya bir düzeltme önermekten memnuniyet duyarım)

Avantajları, geçişleri değiştirmek zorunda olmamanız ve AspNetUser ile uğraşmadan AppUser üzerinde herhangi bir çılgın miras hiyerarşisini kullanabilmenizdir . Ve aslında IdentityContext (IdentityDbContext'ten türetilen bağlam) için Otomatik Geçişler kullanıyorum:

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

    protected override void Seed(IdentityContext context)
    {
    }
}

Bu yaklaşım aynı zamanda, n katmanlı çapraz kesim varlıklarınızın AspNetIdentity sınıflarından miras almasını engelleme avantajına da sahiptir.


Kapsamlı gönderi için @ Augusto'ya teşekkürler. Biri mu zorunda işe Seçenek 3 almak için Göçler kullanabilir? Bildiğim kadarıyla, EF Geçişleri değişiklikleri geri almak için mi? Veritabanımı bırakıp ardından yeniden oluşturuyorsam ve her yeni yapıma ekiyorsam, tüm bu geçişleri yapmam gerekir mi?
J86

Geçiş kullanmadan denemedim. Bunları kullanmadan başarabilir misin bilmiyorum. Belki mümkündür. Veritabanına eklenen özel verileri korumak için her zaman geçişleri kullanmak zorunda kaldım.
Augusto Barreto

Dikkat edilmesi gereken bir şey, Taşıma AddOrUpdate(new EntityObject { shoes = green})İşlemleri kullanıyorsanız ... "yükseltme" olarak da bilinenleri kullanmalısınız . sadece bağlama eklemenin aksine, aksi takdirde sadece yinelenen / gereksiz varlık bağlam bilgisi oluşturursunuz.
Chef_Code

3. seçenekle çalışmak istiyorum ama anlamıyorum. birisi bana IdentityContext'in tam olarak nasıl görünmesi gerektiğini söyleyebilir mi? çünkü tam olarak 2. seçenekteki gibi olamaz! @AugustoBarreto bana yardım edebilir misin? Benzer bir şey hakkında bir
başlık

'TrackableEntity'niz neye benziyor?
Ciaran Gallagher

224

Benim durumumda, IdentityDbContext'ten doğru bir şekilde miras aldım (kendi özel türlerim ve anahtar tanımlı olarak) ancak temel sınıfın OnModelCreating'ine yapılan çağrıyı istemeden kaldırdım:

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

Daha sonra kimlik sınıflarındaki eksik dizinlerimi düzelttim ve daha sonra geçişleri oluşturabilir ve uygun şekilde geçişleri etkinleştirebilirdim.


Aynı problem "çizgiyi kaldırdı". Çözümünüz işe yaradı. :) ty.
Geliştirici Marius Žilėnas

2
Bu, karmaşık bir varlık ilişkisi için akıcı api kullanarak özel bir Eşleme eklemek için OnModelCreating yöntemini geçersiz kılmak zorunda kaldığım sorunumu çözdü. Kimlik ile aynı bağlamı kullandığım için eşlememi bildirmeden önce yanıta satırı eklemeyi unuttuğum ortaya çıktı. Şerefe.
Dan

'Geçersiz OnModelCreating' yoksa çalışır, ancak geçersiz kılarsanız 'base.OnModelCreating (modelBuilder);' eklemeniz gerekir; geçersiz kılmak için. Sorunum çözüldü.
Joe

13

ASP.NET Identity 2.1 kullanan ve birincil anahtarı varsayılandan stringikisinden birine değiştirmiş olanlar için intveya Guidhala alıyorsanız

EntityType 'xxxxUserLogin' tanımlı anahtar içermiyor. Bu EntityType için anahtarı tanımlayın.

EntityType 'xxxxUserRole' tanımlı anahtar içermiyor. Bu EntityType için anahtarı tanımlayın.

muhtemelen yeni anahtar türünü belirtmeyi unuttunuz IdentityDbContext:

public class AppIdentityDbContext : IdentityDbContext<
    AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
    public AppIdentityDbContext()
        : base("MY_CONNECTION_STRING")
    {
    }
    ......
}

Eğer sadece sahipsen

public class AppIdentityDbContext : IdentityDbContext
{
    ......
}

ya da

public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
    ......
}

taşıma eklemeye veya veritabanını güncellemeye çalışırken bu 'anahtar tanımlanmamış' hatasını alırsınız.


Ayrıca kimliği bir Int olarak değiştirmeye çalışıyorum ve bu sorunu yaşıyorum, ancak yeni anahtar türünü belirtmek için DbContext'imi değiştirdim. Kontrol etmem gereken başka bir yer var mı? Talimatları çok dikkatli uyguladığımı düşündüm.
Kyle

1
@Kyle: Tüm varlıkların kimliğini int olarak değiştirmeye mi çalışıyorsunuz, yani AppRole, AppUser, AppUserClaim, AppUserLogin ve AppUserRole? Öyleyse, bu sınıflar için yeni anahtar türünü belirttiğinizden de emin olmanız gerekebilir. 'Public class AppUserLogin: IdentityUserLogin <int> {}' gibi
David Liang,

1
Bu, birincil anahtarlar veri türünü özelleştirmeyle ilgili resmi belgedir
AdrienTorris

1
Evet, benim sorunum, IdentityDbContext <AppUser> yerine genel DbContext sınıfından miras aldım. Teşekkürler, bu çok yardımcı oldu
yibe

13

DbContext'i Aşağıdaki Gibi Değiştirerek;

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

Sadece OnModelCreatingbase.OnModelCreating (modelBuilder) 'a yöntem çağrısı eklemek ; ve güzelleşiyor. EF6 kullanıyorum.

# Senatöre Özel Teşekkürler


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

Sorunum benzerdi - kimlik kullanıcılarına bağlanmak için oluşturduğum yeni bir masam vardı. Yukarıdaki cevapları okuduktan sonra, IsdentityUser ve miras alınan özellikler ile ilgisi olduğunu fark etti. Kimlik zaten kendi Bağlamı olarak ayarlanmıştı, bu nedenle ilgili kullanıcı tablosunu gerçek bir EF özelliği olarak kullanmak yerine ikisini doğal olarak birbirine bağlamadan kaçınmak için, ilgili varlıkları almak için sorgu ile eşlenmemiş bir özellik ayarladım. (DataManager, OtherEntity'nin bulunduğu geçerli bağlamı alacak şekilde ayarlanmıştır.)

    [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);
            }
        }
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.