如何使用 Pomelo.EntityFrameworkCore.MySql 为主键设置 AUTO_INCREMENT 初始值?

How can set AUTO_INCREMENT initial value for primary key using Pomelo.EntityFrameworkCore.MySql?

如何使用 Pomelo.EntityFrameworkCore.MySql 为主键设置 AUTO_INCREMENT 初始值?

像这样How to set initial value and auto increment in MySQL?

CREATE TABLE my_table (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  PRIMARY KEY (id)
) AUTO_INCREMENT = 10000;

问题

我需要用从 10000 开始的 bigint 主键创建一个 table。

生成脚本

CREATE TABLE `Identity.User` (
    `Id` bigint NOT NULL AUTO_INCREMENT,
    `UniqueId` varchar(128) CHARACTER SET utf8mb4 NOT NULL,
    `Username` varchar(128) CHARACTER SET utf8mb4 NOT NULL,
    `NormalizedUsername` varchar(128) CHARACTER SET utf8mb4 NOT NULL,
    `Password` varchar(128) CHARACTER SET utf8mb4 NOT NULL,
    `Email` varchar(128) CHARACTER SET utf8mb4 NOT NULL,
    `NormalizedEmail` varchar(128) CHARACTER SET utf8mb4 NOT NULL,
    `Phone` varchar(16) CHARACTER SET utf8mb4 NULL,
    `Mobile` varchar(16) CHARACTER SET utf8mb4 NOT NULL,
    `CreatedAt` datetime(6) NOT NULL,
    `Enabled` tinyint(1) NOT NULL,
    `Active` tinyint(1) NOT NULL,
    `EmailConfirmed` tinyint(1) NOT NULL,
    `EmailConfirmationCode` longtext CHARACTER SET utf8mb4 NOT NULL,
    `EmailConfirmationDeadline` datetime(6) NOT NULL,
    `MobileConfirmed` tinyint(1) NOT NULL,
    `MobileConfirmationCode` longtext CHARACTER SET utf8mb4 NOT NULL,
    `MobileConfirmationDeadline` datetime(6) NOT NULL,
    `LoginFailCount` int NOT NULL,
    `LockoutUntil` datetime(6) NOT NULL,
    CONSTRAINT `P_Identity.User__Id` PRIMARY KEY (`Id`)
) CHARACTER SET utf8mb4; -- **AUTO_INCREMENT=10000 need this**

我的标识列的 C# 静态方法

public static PropertyBuilder<long> SetIdentity(this PropertyBuilder<long> builder, DatabaseFacade database, int startsAt = 1, int incrementsBy = 1)
{
    switch (database)
    {
        case DatabaseFacade db when db.IsSqlServer():
            SqlServerPropertyBuilderExtensions.UseIdentityColumn(builder, startsAt, incrementsBy);
            break;
        case DatabaseFacade db when db.IsNpgsql():
            NpgsqlPropertyBuilderExtensions.HasIdentityOptions(builder, startsAt, incrementsBy);
            break;
        case DatabaseFacade db when db.IsMySql():
            //MySqlPropertyBuilderExtensions;
            break;
        case DatabaseFacade db when db.IsOracle():
            OraclePropertyBuilderExtensions.UseIdentityColumn(builder, startsAt, incrementsBy);
            break;
        default:
            throw new NotImplementedException("Unknown database provider");
    }
    builder.ValueGeneratedOnAdd();
    return builder;
}

更多技术细节

MySQL 版本:8.x.x 操作系统:Windows Pomelo.EntityFrameworkCore.MySql版本:5.0.0 Microsoft.AspNetCore.App版本:5.0.0

感谢@lauxjpn(lauxjpn)

原始代码在How can set AUTO_INCREMENT initial value for primary key?

我修改的解决方案。

Program.cs

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
using Pomelo.EntityFrameworkCore.MySql.Metadata.Internal;
using Pomelo.EntityFrameworkCore.MySql.Migrations;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Satancito
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "<Pending>")]
    public class CustomMySqlAnnotationProvider : MySqlAnnotationProvider
    {
        public const string AutoincrementAnnotation = "Insane:AutoIncrement";

        public CustomMySqlAnnotationProvider(
            RelationalAnnotationProviderDependencies dependencies,
            IMySqlOptions options)
            : base(dependencies, options)
        {
        }

        public override IEnumerable<IAnnotation> For(ITable table)
        {
            var annotations = base.For(table);
            IEntityType entityType = table.EntityTypeMappings.First().EntityType;

            IAnnotation autoIncrement = entityType.FindAnnotation(AutoincrementAnnotation);
            if (autoIncrement is not null)
            {
                annotations = annotations.Append(autoIncrement);
            }

            return annotations;
        }

    }

    public class CustomMySqlMigrationsSqlGenerator : MySqlMigrationsSqlGenerator
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "<Pending>")]
        public CustomMySqlMigrationsSqlGenerator(
            MigrationsSqlGeneratorDependencies dependencies,
            IRelationalAnnotationProvider annotationProvider,
            IMySqlOptions options)
            : base(dependencies, annotationProvider, options)
        {
        }


        protected override void Generate(
            CreateTableOperation operation,
            IModel model,
            MigrationCommandListBuilder builder,
            bool terminate = true)
        {
            base.Generate(operation, model, builder, terminate: false);
            var autoIncrementValue = operation[CustomMySqlAnnotationProvider.AutoincrementAnnotation];
            if (autoIncrementValue is not null && (autoIncrementValue.GetType().Equals(typeof(int)) || autoIncrementValue.GetType().Equals(typeof(long))))//Check if annotation exists.
            {
                builder.Append($" AUTO_INCREMENT {autoIncrementValue.ToString()}");
            }

            if (terminate)
            {
                builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
                EndStatement(builder);
            }
        }

        protected override void Generate(
            AlterTableOperation operation,
            IModel model,
            MigrationCommandListBuilder builder)
        {
            base.Generate(operation, model, builder);
            var autoIncrementValue = operation[CustomMySqlAnnotationProvider.AutoincrementAnnotation];

            if (autoIncrementValue is not null && (autoIncrementValue.GetType().Equals(typeof(int)) | autoIncrementValue.GetType().Equals(typeof(long))))
            {
                builder.Append("ALTER TABLE ")
                    .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name, operation.Schema))
                    .Append(" AUTO_INCREMENT ")
                    .Append(autoIncrementValue.ToString());

                builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
                EndStatement(builder);
            }
        }
    }

    public class IceCream
    {
        public int IceCreamId { get; set; }
        public string Name { get; set; } = null!;
    }

    public class Context : DbContext
    {
        public DbSet<IceCream> IceCreams { get; set; } = null!; //Using NRT

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                // Register our custom service implementations (and some logging).
                var serviceProvider = new ServiceCollection()
                    .AddEntityFrameworkMySql()
                    .AddSingleton<IRelationalAnnotationProvider, CustomMySqlAnnotationProvider>()
                    .AddScoped<IMigrationsSqlGenerator, CustomMySqlMigrationsSqlGenerator>()
                    .AddScoped(
                        _ => LoggerFactory.Create(
                            b => b
                                .AddConsole()
                                .AddFilter(level => level >= LogLevel.Information)))
                    .BuildServiceProvider();

                var connectionString = "server=127.0.0.1;port=3306;user=root;password=;database=Issue1460";
                var serverVersion = ServerVersion.AutoDetect(connectionString);

                optionsBuilder.UseMySql(connectionString, serverVersion)
                    .UseInternalServiceProvider(serviceProvider) // <-- use our service provider 
                    .EnableSensitiveDataLogging()
                    .EnableDetailedErrors();
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<IceCream>(
                entity =>
                {
                    // Add the custom annotation.
                    entity.HasAnnotation(CustomMySqlAnnotationProvider.AutoincrementAnnotation, 10_000);
                });
        }
    }

    public static class Program
    {
        private static void Main()
        {
            using var context = new Context();

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            context.Add(
                new IceCream { Name = "Vanilla" });

            context.SaveChanges();

            var iceCream = context.IceCreams.Single();

            Trace.Assert(iceCream.IceCreamId == 10_000);
        }
    }
}

Sql创建时生成table

CREATE TABLE `IceCreams` (
          `IceCreamId` int NOT NULL AUTO_INCREMENT,
          `Name` longtext CHARACTER SET utf8mb4 NULL,
          CONSTRAINT `PK_IceCreams` PRIMARY KEY (`IceCreamId`)
      ) CHARACTER SET utf8mb4 AUTO_INCREMENT 10000;