Подтвердить что ты не робот

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, "[email protected]");
            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.

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

UPDATE

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

4b9b3361

Ответ 1

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

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

Затем, используя Code First Migrations, вам нужно будет сгенерировать миграции для IdentityContext и THEN для DXContext, следуя этому замечательному сообщению из Shailendra Chauhan: Code First Migrations с несколькими контекстами данных

Вам придется изменить миграцию, созданную для 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));

и затем запускаем миграцию по порядку (сначала миграции Identity) из global.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 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.

Ответ 2

В моем случае я правильно унаследовал от IdentityDbContext (с моими собственными настраиваемыми типами и ключом), но нечаянно удалил вызов базовому классу OnModelCreating:

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

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

Ответ 3

Для тех, кто использует 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>
{
    ......
}

вы получите эту ошибку "без ключа" при попытке добавить миграции или обновить базу данных.

Ответ 4

Изменяя DbContext, как показано ниже;

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

Просто добавив в метод OnModelCreating вызов метода base.OnModelCreating(modelBuilder); и это становится хорошо. Я использую EF6.

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

Ответ 5

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

        }
    }

Ответ 6

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