Как сделать EF-Core использовать Guid вместо String для своего ID/Первичного ключа

Когда я смотрю на ASP.NET 3 Identity, он использует string, а не Guid для уникального первичного ключа.

В моем классе Entity Framework code first Пользователи ApplicationUser я наследую класс Identity

public class ApplicationUser : IdentityUser
{
}

что приводит к тому, что при создании миграции Entity Framework создается таблица aspnetusers, созданная с типом ключа nvarchar(450) вместо uniqueidentifier

Когда я сравниваю это с проектом ASP.NET Identity 2 Entity Framework, он создал поле идентификатора uniqueidentifier, а не nvarchar(450)

Я бы предположил, что для производительности базы данных первичные ключи и внешние ключи uniqueidentifier будут лучше, чем nvarchar(450)

Есть ли способ использовать уникальный ключ Guid вместо string и uniqueidentifier вместо nvarchar(450) с помощью ASP.NET Identity 3?

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

Я нашел еще один question и для предыдущей BETA, когда длина была nvarchar (128). Причина в том, что не все базы данных поддерживают гиды, и это было изменено для гибкости.

Должен быть простой способ изменить строку на строку Guid без перезаписи целого identity 3?

nvarchar(450) действительно избыточно и дает всевозможные предупреждения при создании ограничений базы данных SQL Server. Администраторы базы данных будут окончательно не похожи на эти предупреждения.

Ответы

Ответ 1

Вам нужно настроить ApplicationUser наследование с IdentityUser<TKey> и наследование по умолчанию из IdentityRole<TKey>

public class ApplicationUser : IdentityUser<Guid> { }    
public class Role : IdentityRole<Guid> { }

Пользовательский класс контекста наследует от IdentityDbContext<ApplicationUser, Role, TKey> и использует свободно api для автоматического генерации ключей guid.

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, Role, Guid>
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<ApplicationUser>(b =>
        {
            b.Property(u => u.Id).HasDefaultValueSql("newsequentialid()");
        });

        builder.Entity<Role>(b =>
        {
            b.Property(u => u.Id).HasDefaultValueSql("newsequentialid()");
        });
    }
}

затем в Startup добавьте службу Identity в контейнер, подобный этому

services.AddIdentity<ApplicationUser, Role>()
        .AddEntityFrameworkStores<ApplicationDbContext, Guid>()
        .AddDefaultTokenProviders()
        .AddUserStore<UserStore<ApplicationUser, Role, ApplicationDbContext, Guid>> ()
        .AddRoleStore<RoleStore<Role, ApplicationDbContext, Guid>>();

Если вы не создали базу данных, очистите папку миграции и запустите команды ef

Ответ 2

Я еще не испортил миграции для этого примера (им могут потребоваться некоторые настройки), однако ASP.NET Identity v3 гораздо более расширяема, чем v2 в этом отношении.

Следующее должно предоставить вам хранилище Identity на основе основных ключей Guid для пользователей и ролей:

public class ApplicationUser : IdentityUser<Guid>
{
}

public class GuidDataContext :
    IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
{
}

и в вашем классе запуска:

services.AddIdentity<ApplicationUser, IdentityRole<Guid>>(
            identity =>
            {
                // whatever identity options you want
                identity.User.RequireUniqueEmail = true;
                identity.Password.RequiredLength = 8;
            }).
            AddEntityFrameworkStores<GuidDataContext, Guid>().AddDefaultTokenProviders();

Аналогичным образом, если вам не нужно добавлять какие-либо настраиваемые поля в Identity User или настраивать параметры, вы можете просто сделать следующее:

public class GuidDataContext :
    IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>
{ 
}

и при запуске:

services
    .AddIdentity<IdentityUser<Guid>, IdentityRole<Guid>>()
    .AddEntityFrameworkStores<GuidDataContext, Guid>()
    .AddDefaultTokenProviders();

Ответ 3

ApplicationUser наследует IdentityUser базовый класс, который определяется как имеющий строку как id. Чтобы действительно использовать указатель guid/uniqueidentifier, вам не нужно наследовать этот базовый класс.

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

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<ApplicationUser>(b =>
        {
            // you could limit the field size to just long enough for a guid id as string
            b.Property(p => p.Id)
                .HasMaxLength(36);

            // instead, you could define the id as Guid but more work required
            //b.Property(p => p.Id)
            //   .ForSqlServerHasColumnType("uniqueidentifier")
            //   .ForSqlServerHasDefaultValueSql("newid()")
            //   .IsRequired();

        });

    }
}

Можно использовать Guids/uniqueidentifier для ключа, но для этого потребуется больше работы, помимо использования собственного базового класса (или вообще не использовать базовый класс) и использовать собственный DbContext для сопоставления модели. Заимствование и изменение кода EF отсюда должно вас уволить, вам нужно было бы наследовать от UserStore и переопределить виртуальные методы поиска пользователя по id, поскольку интерфейс IUserStore определяет Подпись метода FindById со строкой. Таким образом, переопределяя, вам нужно будет преобразовать строку в guid внутри методов, где вам нужно найти или найти по id.

Я делаю именно это в моем проекте облаков, у которого есть индивидуальная реализация Identity с несколькими арендаторами

EDIT: на самом деле, ближе к коду IdentityUser имеет общую версию, где вы можете определить тип ключа

public class IdentityUser<TKey> where TKey : IEquatable<TKey>

Итак, вы можете просто определить свой собственный базовый класс, например

IdentityUser<Guid>

но я все же думаю, что вам нужно будет переопределить методы UserStore, которые берут строку id и преобразовать строку в guid.