Entity Framework 核心:无法在生产服务器上 运行 迁移脚本,聚簇索引的最大键长度为 900 字节

Entity Framework Core: Unable to run migration script on production server, The maximum key length for a clustered index is 900 bytes

我正在尝试在使用 Plesk 托管的生产服务器上部署我的 ASP.NET 核心网站。我的解决方案中有 2 个项目:1 个包含我的 DbContext,1 个包含我的 Web 项目。

我为我的DbContext生成了迁移:

dotnet ef migrations add AddIdentity --project ..\MyProject.Data

然后我生成了文档中提到的生产迁移脚本 (https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet#dotnet-ef-migrations-script)

dotnet ef migrations script --idempotent --output "MigrationScripts\AddIdentity.sql" --context MyDbContext --project ..\MyProject.Data

然后我导航到生产服务器 (MyLittleAdmin) 上的 SQL 实用程序,并将生成的 SQL 脚本粘贴到其中。这成功了,但我收到以下错误:

Warning! The maximum key length for a clustered index is 900 bytes. The index 'PK_AspNetUserLogins' has maximum length of 1800 bytes. For some combination of large values, the insert/update operation will fail.

Warning! The maximum key length for a clustered index is 900 bytes. The index 'PK_AspNetUserTokens' has maximum length of 1804 bytes. For some combination of large values, the insert/update operation will fail.

如果忽略这个错误,我以后可能会因为 "no apparent reason"(这个原因)的查询失败而遇到麻烦。

我的代码非常简单:

internal class MintPlayerContext : IdentityDbContext<User, Role, int>
{
    private readonly IConfiguration configuration;
    public MintPlayerContext(IConfiguration configuration) : base()
    {
        this.configuration = configuration;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseSqlServer(configuration.GetConnectionString("MintPlayer"), options => options.MigrationsAssembly("MintPlayer.Data"));
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

internal class User : IdentityUser<int>
{
    public string PictureUrl { get; set; }
}

internal class Role : IdentityRole<int>
{
}

我也尝试过使用 Guid(我最终想使用 Guid),但即使 int 的 MyLittleAdmin 抱怨密钥太长。 我已经检查过 Google 但这只提供诊断文章,并没有真正提供解决方案。

现在这是迁移的 AspNetUsers 部分:

CREATE TABLE [AspNetUsers] (
    [Id] int NOT NULL IDENTITY,
    [UserName] nvarchar(256) NULL,
    [NormalizedUserName] nvarchar(256) NULL,
    [Email] nvarchar(256) NULL,
    [NormalizedEmail] nvarchar(256) NULL,
    [EmailConfirmed] bit NOT NULL,
    [PasswordHash] nvarchar(max) NULL,
    [SecurityStamp] nvarchar(max) NULL,
    [ConcurrencyStamp] nvarchar(max) NULL,
    [PhoneNumber] nvarchar(max) NULL,
    [PhoneNumberConfirmed] bit NOT NULL,
    [TwoFactorEnabled] bit NOT NULL,
    [LockoutEnd] datetimeoffset NULL,
    [LockoutEnabled] bit NOT NULL,
    [AccessFailedCount] int NOT NULL,
    [PictureUrl] nvarchar(max) NULL,
    CONSTRAINT [PK_AspNetUsers] PRIMARY KEY ([Id])
);

我该怎么做才能解决这个问题?

编辑:

我尝试修改我的 DbContext 配置以强制 Key 仅为 Id:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<User>().HasKey(u => u.Id);
    modelBuilder.Entity<Role>().HasKey(r => r.Id);
}

这似乎确实影响了迁移:

[Id] int NOT NULL IDENTITY,

而不是:

[Id] uniqueidentifier NOT NULL,

但是还是报同样的错误

编辑:添加 AspNetUserLogins、AspNetUserRoles,删除 if's

CREATE TABLE [AspNetUserLogins] (
    [LoginProvider] nvarchar(450) NOT NULL,
    [ProviderKey] nvarchar(450) NOT NULL,
    [ProviderDisplayName] nvarchar(max) NULL,
    [UserId] int NOT NULL,
    CONSTRAINT [PK_AspNetUserLogins] PRIMARY KEY ([LoginProvider], [ProviderKey]),
    CONSTRAINT [FK_AspNetUserLogins_AspNetUsers_UserId] FOREIGN KEY ([UserId]) REFERENCES [AspNetUsers] ([Id]) ON DELETE CASCADE
);

CREATE TABLE [AspNetUserTokens] (
    [UserId] int NOT NULL,
    [LoginProvider] nvarchar(450) NOT NULL,
    [Name] nvarchar(450) NOT NULL,
    [Value] nvarchar(max) NULL,
    CONSTRAINT [PK_AspNetUserTokens] PRIMARY KEY ([UserId], [LoginProvider], [Name]),
    CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId] FOREIGN KEY ([UserId]) REFERENCES [AspNetUsers] ([Id]) ON DELETE CASCADE
);

AspNetUserLoginsAspNetUserTokens 需要定义一个较短的键。我假设模型中没有定义任何键,因此所有列都添加到键中。可能包括一些大字符串列。

你可以试试改成:

CREATE TABLE [AspNetUserTokens] (
    [UserId] int NOT NULL,
    [LoginProvider] nvarchar(150) NOT NULL,
    [Name] nvarchar(150) NOT NULL,
    [Value] nvarchar(150) NULL,
    CONSTRAINT [PK_AspNetUserTokens] PRIMARY KEY ([UserId], [LoginProvider], [Name]),
    CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId] FOREIGN KEY ([UserId]) REFERENCES [AspNetUsers] ([Id]) ON DELETE CASCADE
);

我认为名称不能超过 150 个字符。我的意思是它只是一个名字。

所有列的总长度需要小于 900。

ALTER TABLE sampleTable ALTER COLUMN column1 VARCHAR (400) NULL;
ALTER TABLE sampleTable ALTER COLUMN column2 VARCHAR (200) NULL;
ALTER TABLE sampleTable ALTER COLUMN column3 VARCHAR (200) NULL;

根据评论,您应该尝试外部 Oauth 提供并得出一个合理的数字。

另外,nvarchar的存储大小是*2。 450 * 2 将触发错误。你可以试着减少它。

谢谢大家,很到位。 感谢您的所有建议,我设法使其正常工作。

因为我使用的是代码优先,所以我不能只修改我生成的迁移。 这就是我所做的:

internal class MintPlayerContext : IdentityDbContext<User, Role, int>
{
    // dotnet ef migrations add AddIdentity --project ..\MyProject.Data
    // dotnet ef database update --project ..\MyProject.Data
    // (only looks at your appsettings.json file, not your appsettings.*.json, so for local development)

    // To generate production migrations
    // dotnet ef migrations script --idempotent --output "MigrationScripts\AddIdentity.sql" --context MyDbContext --project ..\MyProject.Data

    private readonly IConfiguration configuration;
    public MintPlayerContext(IConfiguration configuration) : base()
    {
        this.configuration = configuration;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseSqlServer(configuration.GetConnectionString("MyProject"), options => options.MigrationsAssembly("MyProject.Data"));
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Microsoft.AspNetCore.Identity.IdentityUserLogin<int>>()
            .Property(ut => ut.LoginProvider)
            .HasMaxLength(50);
        modelBuilder.Entity<Microsoft.AspNetCore.Identity.IdentityUserLogin<int>>()
            .Property(ut => ut.ProviderKey)
            .HasMaxLength(200);

        modelBuilder.Entity<Microsoft.AspNetCore.Identity.IdentityUserToken<int>>()
            .Property(ut => ut.LoginProvider)
            .HasMaxLength(50);
        modelBuilder.Entity<Microsoft.AspNetCore.Identity.IdentityUserToken<int>>()
            .Property(ut => ut.Name)
            .HasMaxLength(50);
    }
}

我将使用 Guid,以免让 UserId 变得可猜测。