EntityType 'IdentityUserLogin' не имеет определенного ключа. Определите ключ для этого EntityType

сначала я работаю с кодом Entity Framework и 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.ДОМЕН.Контекст.IdentityUserLogin:: EntityType 'IdentityUserLogin' не имеет определенного ключа. Определите ключ для этого EntityType.

DX.ДОМЕН.Контекст.IdentityUserRole:: EntityType 'IdentityUserRole' не имеет определенного ключа. Определите ключ для этого EntityType.

что я делаю не так? Это потому, что у меня два контекста?

обновление

прочитав ответ Аугусто, я пошел с 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 является целочисленным свойством на my Artist.cs

6 ответов


проблема в том, что ваш 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; }

и их первичные ключи отображаются в методе OnModelCreating класса IdentityDbContext:

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 не является производным от него, эти ключи не определяются.

если вы копаете в источник of Microsoft.AspNet.Identity.EntityFramework, вы поймете всё.

я столкнулся с этой ситуацией некоторое время назад, и я нашел три возможных решения (может быть, есть больше):

  1. используйте отдельные DbContexts для двух разных баз данных или одной базы данных, но разных таблиц.
  2. объедините DXContext с ApplicationDbContext и используйте одну базу данных.
  3. используйте отдельные DbContexts для одной таблицы и управляйте их миграциями соответственно.

Вариант 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: У вас будет один DbContext, равный опции 2. Назовем его IdentityContext. И у вас будет другой DbContext под названием DXContext:

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

С этим решением я сопоставляю сущность Пользователь в той же таблице, что и entity ApplicationUser.

затем, используя первые миграции кода, вам нужно будет создать миграции для IdentityContext и затем для DXContext, после этого Великого поста от Shailendra Chauhan:первые миграции кода с несколькими контекстами данных

вам придется изменить миграцию, созданную для DXContext. Что-то подобное в зависимости от того, какие свойства являются общими между ApplicationUser и Пользователем:

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

а затем запуск миграции по порядку (сначала миграции идентификаторов)из глобального.asax или любое другое место вашего приложения, используя этот пользовательский класс:

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

для последних двух проектов я использовал 1-й вариант: наличие класса AspNetUser, производного от IdentityUser, и отдельного пользовательского класса AppUser. В моем случае DbContexts являются IdentityContext и DomainContext соответственно. И я определил идентификатор AppUser, как это:

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-это пользовательский абстрактный базовый класс, который я использую в переопределенном методе SaveChanges моего контекста DomainContext)

сначала я создаю AspNetUser, а затем AppUser. Недостатком этого подхода является то, что вы гарантируете, что ваша функциональность "CreateUser" является транзакционной (помните, что будет два DbContexts, вызывающих SaveChanges отдельно). По какой-то причине использование 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;
        }

(пожалуйста, если кто-то приходит с лучшим способом сделать эту часть, я ценю комментарий или предложение редактировать этот ответ)

преимущества в том, что вам не нужно изменять миграции, и вы можете используйте любую сумасшедшую иерархию наследования над AppUser, не возясь с AspNetUser. И на самом деле я использую автоматические миграции для моего 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.
}

который затем исправил мои отсутствующие индексы из классов идентификаторов, и я мог бы генерировать миграции и включать миграции соответствующим образом.


для тех, кто использует 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 в базу.OnModelCreating (modelBuilder); и его становятся прекрасными. Я через ef6.

Особая Благодарность #Сенатору


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

        }
    }

моя проблема была похожа - у меня была новая таблица, которую я создавал, чтобы привязать ahd к пользователям identity. Прочитав приведенные выше ответы, понял, что это связано с IsdentityUser и унаследованными properites. У меня уже был Identity, настроенный как собственный контекст, поэтому, чтобы избежать связывания этих двух вместе, а не использовать связанную пользовательскую таблицу в качестве истинного свойства EF, я настроил не сопоставленное свойство с запросом для получения связанных сущностей. (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);
            }
        }
    }