【问题标题】:How can I change ASP.Net Identity 2 on SQL Server to create a newSequentialId Primary Key?如何更改 SQL Server 上的 ASP.Net Identity 2 以创建 newSequentialId 主键?
【发布时间】:2015-05-14 10:06:52
【问题描述】:

我有一个具有 UNIQUEIDENTIFIER 类型的 userId 的 ASP.NET Identity 2 实现(没有用户数据,只有基表)。

应用程序首先是代码,我使用的是 EF6。

这是 DDL:

CREATE TABLE [dbo].[AspNetUsers] (
    [Id]                   UNIQUEIDENTIFIER NOT NULL,
    [FirstName]            NVARCHAR (MAX) NULL,
    [LastName]             NVARCHAR (MAX) NULL,
    [Email]                NVARCHAR (256) NULL,
    [EmailConfirmed]       BIT            NOT NULL,
    [PasswordHash]         NVARCHAR (MAX) NULL,
    [SecurityStamp]        NVARCHAR (MAX) NULL,
    [PhoneNumber]          NVARCHAR (MAX) NULL,
    [PhoneNumberConfirmed] BIT            NOT NULL,
    [TwoFactorEnabled]     BIT            NOT NULL,
    [LockoutEndDateUtc]    DATETIME       NULL,
    [LockoutEnabled]       BIT            NOT NULL,
    [AccessFailedCount]    INT            NOT NULL,
    [UserName]             NVARCHAR (256) NOT NULL,
    [SubjectId]            INT            DEFAULT ((0)) NOT NULL,
    [SubjectIds]           VARCHAR (50)   NULL,
    [OrganizationId]       INT            DEFAULT ((0)) NOT NULL,
    [OrganizationIds]      VARCHAR (50)   NULL,
    [RoleId]               INT            DEFAULT ((0)) NOT NULL,
    CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED ([Id] ASC)
);


GO
CREATE UNIQUE NONCLUSTERED INDEX [UserNameIndex]
    ON [dbo].[AspNetUsers]([UserName] ASC);

我了解正常的 GUID 创建是正常的 GUID。

谁能告诉我如何让它创建一个 newSequential GUID?

请注意

我正在寻找专门使用 ASP.Net Identity 2 执行此操作的正确方法。特别是我想知道是否需要对 Identity 2 UserManager 等进行任何更改。

【问题讨论】:

  • 我认为不可能更改 b/c 身份以创建顺序 Guid,b/c Guid 不实现 IConvertible。通过使用 Fluent API,我调整了 Code-First EF 以添加 DEFAULT (newsequentialid()) FOR [Id]。我可能在解释中遗漏了步骤,但我能够构建和运行应用程序,成功注册/创建用户。
  • 经历过,不推荐。这不是工作量,我的指导方针之间的 b/c,构建错误和智能感知,VS 会带你完成它。我怀疑这是浪费工作。除非您有 10,000 多个用户,否则此优化无济于事。如果您最终拥有非常高的用户群,则使用int 进行PK 并添加GUID 字段以传递给浏览器的性能会更好。此外,许多数据库(包括 Azure)不支持顺序 GUID,因为它们会带来泄露 Mac 地址或用户能够猜测 GUID 增量的安全风险。
  • 艾伦,你有没有尝试实施我的解决方案?

标签: asp.net asp.net-mvc asp.net-mvc-5 asp.net-identity asp.net-identity-2


【解决方案1】:

我终于能够构建项目并运行它。使用 Fluent API 创建后,将 newsequentialid() 分配给 ID 字段:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<ApplicationUser>().Property(t => t.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<CustomUserRole>().HasKey(x => new
        {
            x.RoleId,
            x.UserId
        });

        modelBuilder.Entity<CustomUserLogin>().HasKey(x => new
        {
            x.UserId,
            x.ProviderKey,
            x.LoginProvider
        });
    }

结果是 SQL 表,脚本如下:

/****** Object:  Table [dbo].[AspNetUsers]    Script Date: 4/11/2015 3:40:51 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[AspNetUsers](
    [Id] [uniqueidentifier] NOT NULL,
    [Email] [nvarchar](256) NULL,
    [EmailConfirmed] [bit] NOT NULL,
    [PasswordHash] [nvarchar](max) NULL,
    [SecurityStamp] [nvarchar](max) NULL,
    [PhoneNumber] [nvarchar](max) NULL,
    [PhoneNumberConfirmed] [bit] NOT NULL,
    [TwoFactorEnabled] [bit] NOT NULL,
    [LockoutEndDateUtc] [datetime] NULL,
    [LockoutEnabled] [bit] NOT NULL,
    [AccessFailedCount] [int] NOT NULL,
    [UserName] [nvarchar](256) NOT NULL,
 CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

ALTER TABLE [dbo].[AspNetUsers] ADD  DEFAULT (newsequentialid()) FOR [Id]
GO

必须更改其他实体类型:

public class ApplicationUser : IdentityUser<Guid, CustomUserLogin, CustomUserRole,
    CustomUserClaim>
{


    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public override Guid Id { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, Guid> 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;
    }
}

public class CustomUserRole : IdentityUserRole<Guid> { }
public class CustomUserClaim : IdentityUserClaim<Guid> { }
public class CustomUserLogin : IdentityUserLogin<Guid> { }

public class CustomRole : IdentityRole<Guid, CustomUserRole>
{
    public CustomRole() { }
    public CustomRole(string name) { Name = name; }
}

public class CustomUserStore : UserStore<ApplicationUser, CustomRole, Guid,
    CustomUserLogin, CustomUserRole, CustomUserClaim>
{
    public CustomUserStore(ApplicationDbContext context)
        : base(context)
    {
    }
}

public class CustomRoleStore : RoleStore<CustomRole, Guid, CustomUserRole>
{
    public CustomRoleStore(ApplicationDbContext context)
        : base(context)
    {
    }
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole,
    Guid, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }

在 Startup.Auth.cs 中,我改变了

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator
                    .OnValidateIdentity<ApplicationUserManager, ApplicationUser, Guid>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentityCallback: (manager, user) =>
                            user.GenerateUserIdentityAsync(manager),
                        getUserIdCallback: (id) => new Guid(id.GetUserId()))
            }
        });            
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

在 IdentityConfig.cs 中,我更改了 ApplicationUserManager

这里:

public class ApplicationUserManager : UserManager<ApplicationUser, Guid>
{
    public ApplicationUserManager(IUserStore<ApplicationUser, Guid> store)
        : base(store)
    {
    }

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) 
    {
        var manager = new ApplicationUserManager(
            new CustomUserStore(context.Get<ApplicationDbContext>()));
        // Configure validation logic for usernames             manager.UserValidator = new UserValidator<ApplicationUser>(manager)

        manager.UserValidator = new UserValidator<ApplicationUser, Guid>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

还有

        manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser, Guid>
        {
            MessageFormat = "Your security code is {0}"
        });
        manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser, Guid>
        {
            Subject = "Security Code",
            BodyFormat = "Your security code is {0}"
        });
        manager.EmailService = new EmailService();
        manager.SmsService = new SmsService();
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider =
                new DataProtectorTokenProvider<ApplicationUser, Guid>(dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }
}

// Configure the application sign-in manager which is used in this application.
public class ApplicationSignInManager : SignInManager<ApplicationUser, Guid>

在 ManageController.cs 中,我添加了

public class ManageController : Controller
{
    private ApplicationSignInManager _signInManager;
    private ApplicationUserManager _userManager;
    private Guid userGuidId;

    public ManageController()
    {
        userGuidId= new Guid(User.Identity.GetUserId());
    }

替换userGuidId,而不是我看到userId的所有地方

我必须在这里使用ToString()

BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userGuidId.ToString())

在 Account Controller 中,我似乎只是改变了

    [AllowAnonymous]
    public async Task<ActionResult> ConfirmEmail(string userId, string code)
    {
        Guid GuidUserId = new Guid(userId);
        if (userId == null || code == null)
        {
            return View("Error");
        }
        var result = await UserManager.ConfirmEmailAsync(GuidUserId, code);
        return View(result.Succeeded ? "ConfirmEmail" : "Error");
    }

【讨论】:

  • 这其中的哪一部分使它成为连续的?
  • @Stilgar,fluent api 身份分配被裁剪(剪切和粘贴)
  • @DaveAlperovich - 见我上面的评论。似乎该值是在 C# 代码中创建的,而不是在数据库中创建的。
  • @Alan,你是对的。我给你的是第一部分。当我玩实现时,我意识到我必须对 UserManager 和管道中的其他类进行调整。我沿着兔子洞走得更远,使其他组件与 GUID 类一起工作,但还不能完成任务。如果我成功了,我会通知你的。
【解决方案2】:

首先创建基于“IdentityUser”的类的非泛型版本...

public class AppUserClaim : IdentityUserClaim<Guid> { }
public class AppUserLogin : IdentityUserLogin<Guid> { }
public class AppUserRole : IdentityUserRole<Guid> { }

...那么IdentityRoleUserStore 和`UserManager...

public class AppRole : IdentityRole<Guid, AppUserRole> 
{ 
}

public class AppUserStore : UserStore<AppUser, AppRole, Guid, AppUserLogin, AppUserRole, AppUserClaim>
{
    public AppUserStore(DbContext context)
        : base(context)
    {
    }
}

public class AppUserManager : UserManager<AppUser, Guid>
{
    public AppUserManager(IUserStore<AppUser, Guid> store)
        : base(store)
    {
    }
}

...最后是IdentityDbContext...

public class AppIdentityContext : IdentityDbContext<AppUser, AppRole, Guid, AppUserLogin, AppUserRole, AppUserClaim>
{
    public AppIdentityContext()
        : base("name=AspNetIdentity")
    {
    }
}

在所有这些新类中,您会注意到基类使用了 Identity 类的通用版本,我们使用 AppUserClaimAppUserLoginAppUserRoleAppRole 代替了对应的 Identity。

我们为用户创建一个名为AppUser 的类,该类将派生自IdentityUser

public class AppUser : IdentityUser<Guid, AppUserLogin, AppUserRole, AppUserClaim>
{
    [DllImport("rpcrt4.dll", SetLastError = true)]
    private static extern int UuidCreateSequential(out Guid guid);

    private Guid _id;

    public AppUser()
    {
        UuidCreateSequential(out _id);
    }        

    /// <summary>
    /// User ID (Primary Key)
    /// </summary>
    public override Guid Id
    {
        get { return _id; }
        set { _id = value; }
    }
}

在构造函数中,我们使用UuidCreateSequential 函数创建一个新ID,并通过Id 属性返回它。我想在数据库中设置Id 列以使用newsequentialid() 作为默认值并使用它而不是DllImport,但我还没有解决这个问题。

在控制器动作中使用:

public async Task<ActionResult> ActionName()
{
    AppIdentityContext dbContext = new AppIdentityContext();
    AppUserStore store = new AppUserStore(dbContext);
    AppUserManager manager = new AppUserManager(store);
    AppUser user = new AppUser { UserName = "<name>", Email = "<email>" };

    await manager.CreateAsync(user);

    return this.View();
}

需要注意的几点:

  1. 如果您使用的是现有数据库,即使用 SQL 脚本创建的数据库,并且AspNetUsers 中的Id 列是nvarchar,那么您需要将以下列更改为uniqueidentifier

    • AspNetUsers.Id
    • AspNetRoles.Id
    • AspNetUserRoles.UserId
    • AspNetUserRoles.RoleId
  2. 在 ASP.NET MVC 控制器中的IIdentity 接口上使用GetUserId 扩展方法,即this.User.Identity.GetUserId(),将返回string,因此在将返回值转换为字符串:

    new Guid(this.User.Identity.GetUserId())

    这个方法有一个通用版本,但在它下面使用Convert.ChangeType,这需要传入的值实现IConvertable,而Guid没有。

我无法对此进行全面测试,但如果它不能完全满足您的需求,希望它能提供一个有用的基础。

更新 #1:这些是我经历的步骤:

  1. 创建一个没有身份验证的新 ASP.NET MVC 应用程序
  2. 添加以下 NuGet 包

    • 实体框架
    • Microsoft.AspNet.Identity.Core
    • Microsoft.AspNet.Identity.EntityFramework
  3. 将所有代码示例添加到App_Start 文件夹中名为Identity.cs 的文件中

    注意:排除控制器操作示例。这将在步骤 #6 中完成

  4. web.config中删除所有实体框架部分

  5. web.config 添加一个名为AspNetIdentity 的新连接字符串
  6. 将控制器操作示例添加到HomeController 上的Index 操作并替换&lt;name&gt;&lt;email&gt; 部分
  7. 向您的 SQL Server 添加一个名为 AspNetIdentity 的新空数据库
  8. 运行应用程序

如果您使用选择了个人用户帐户身份验证选项的 ASP.NET MVC 模板,那么将会出现一些必须修复的错误。这些主要集中在将IdentityUser* 类的引用更改为基于AppUser* 的新类,并替换对User.Identity.GetUserId() 的调用以使用我原始答案中步骤#2 中提供的代码示例。

【讨论】:

  • 我觉得你的帖子很有趣。经历了相同的步骤,但无法编译。您是否成功构建了您的实施?
  • 这种方法似乎不足。如果 OP 能够实现您的方法并构建它,那么您仍然没有实现 Identity 查询最后一个 User-Id 并在序列中创建下一个的方法。 Identity 没有生成 GUID 的实现,更不用说顺序 GUID。
  • 这是在“AppUser”类中完成的,其中“Id”被覆盖并在构造函数中调用“UuidCreateSequential”。
  • 您会以这种方式将 DB 类型更改为 Seq GUID 吗?
  • 使用UuidCreateSequential在代码中分配一个顺序ID与使用SQL Server函数newsequentialid没有什么不同,因为SQL Server函数newsequentialid只是UuidCreateSequential的一个包装器。
【解决方案3】:

这对我来说是为了使 RolesUsers Guid 类型的 Id 字段在默认值或绑定中具有 newsequentialid()

  1. 在 Visual Studio 的 Migrations 文件夹中删除 *.cs 文件
  2. 删除表 __MigrationHistory 和数据库中的所有 AspNet*
  3. 将以下代码添加到 ApplicationDbContext 类中:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<ApplicationUser>().Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<ApplicationRole>().Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    }
    
  4. 在 Visual Studio 包管理器控制台中运行 Add-Migration Initial
  5. 在 Visual Studio 包管理器控制台中运行 Update-Database

警告:这将从您的数据库中删除所有角色中的用户

【讨论】:

    猜你喜欢
    • 2015-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多