EntityType 'IdentityUserLogin' не имеет определенного ключа. Определить ключевые для этой атрибутом entitytype


Я работаю с 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.ДОМЕН.Контекст.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; }
}

Я не был уверен, что мне понадобится свойство password для пользователя, так как по умолчанию ApplicationUser имеет это и куча других полей!

В любом случае, приведенное выше изменение строится нормально, но снова я получаю эту ошибку, когда приложение запускается:

Недопустимое имя столбца UserId

UserId является целочисленным свойством на моем Artist.cs

6 88

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

Если вы покопаетесь в источниках 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 User с той же таблицей, что и entity ApplicationUser.

Затем, используя миграции Code First, вам нужно будет сгенерировать миграции для 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 в производственных средах.

UPDATE: Expand Option 1

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

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" является транзакционный (помните, что будет два DbContext, вызывающих 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;
        }

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

Преимущества в том, что вам не нужно изменять миграции, и вы можете использовать любую сумасшедшую иерархию наследования над пользователем приложения не связываясь с 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");

        }
    }

Моя проблема была аналогичной - у меня была новая таблица, которую я создавал, чтобы привязать к пользователям identity. Прочитав ответы выше, понял, что это связано с IsdentityUser и унаследованными properites. Я уже настроил Identity как свой собственный контекст, поэтому, чтобы избежать неразрывной связи между ними, вместо использования связанной пользовательской таблицы в качестве истинного свойства EF, я настроил не сопоставленное свойство с запросом для получения связанных сущностей. (DataManager настроен для получения текущего значения контекст, в котором существует инаковость.)

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