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


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



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

644   6  

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

С помощью этого решения я сопоставляю сущность Пользователь в той же таблице, что и объект 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 соответственно. И я определил идентификатор пользователя приложения, как это:

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

        }
    }

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

Comments

    Ничего не найдено.