How to make EF-Core use a Guid instead of String f

2019-01-13 13:49发布

问题:

When I look at the ASP.NET 3 Identity it uses a string and not a Guid for the unique primary key.

In my Entity Framework code first Users' ApplicationUser class I inherit the Identity class

public class ApplicationUser : IdentityUser
{
}

which results in when I create my Entity Framework migrations a table aspnetusers getting created with a key type ofnvarchar(450) instead of uniqueidentifier

When I compare this to ASP.NET Identity 2 ENtity Framework project it created an ID field of uniqueidentifier and not nvarchar(450)

I would imagine for database performance primary keys and foreign keys of uniqueidentifier would be better than nvarchar(450)

Is there a way to use for the unique key Guid instead of string and uniqueidentifier instead of nvarchar(450) with ASP.NET Identity 3?

There is a previous question how to convert a String to a Guid but I want the database table Id to be a Guid.

I found another question as well for a previous BETA when the length was nvarchar(128). The reason given is that not all databases support Guids and it was changed for flexibility.

Is there must be an easy way to change from string to Guid without rewriting the whole identity 3?

nvarchar(450) is really overkill and gives all kinds of warnings when creating SQL Server database constraints. Database admins will definitively not like these warnings.

回答1:

You need custom ApplicationUser inherit from IdentityUser<TKey> and custom Role inherit from IdentityRole<TKey>

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

Custom context class inherit from IdentityDbContext<ApplicationUser, Role, TKey> and use fluent api for auto generate guid keys.

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

then in Startup add Identity service to container like this

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

If you have not created the database, clear the migrations folder and run ef commands



回答2:

I haven't messed with the migrations for this example yet (they might need some tweaks), however ASP.NET Identity v3 is far more extensible than v2 was in this regard.

The following should give you an Identity store based on Guid primary keys for Users and Roles alike:

public class ApplicationUser : IdentityUser<Guid>
{
}

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

and in your startup class:

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

Likewise, if you didn't need to add any custom fields to the Identity User, or customize your options, you could just do the following:

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

and in your startup:

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


回答3:

ApplicationUser inherits from IdentityUser base class which is defined as having a string as id. So to really use a guid/uniqueidentifier you would need to not inherit from that base class.

Note that internally it is using guid strings for ids. You should at least be able to limit the field size of the key like shown here:

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

        });

    }
}

It is possible to use Guids/uniqueidentifier for the key but that will require more work, in addition to using your own base class (or not use a base class at all) and use a custom DbContext to map the model. Borrowing and modifying the EF code from here should get you going, you would have to inherit from UserStore and override the virtual methods for looking up a user by id, because the interface IUserStore defines the FindById method signature with a string. So overriding you would need to convert the string to a guid inside the methods where you need to fetch or find by id.

I'm doing exactly that in my cloudscribe project which has a custom multi-tenant implementation of Identity

EDIT: actually looking closer at the code the IdentityUser has a generic version where you can define the type of the key

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

So you might be able to just define your own base class like

IdentityUser<Guid>

but I still think you would need to override the UserStore methods that take an id string and convert the string to a guid.