【问题标题】:EF Core 2 Provider specific HasDefaultValueSqlEF Core 2 提供程序特定 HasDefaultValueSql
【发布时间】:2020-04-22 10:17:06
【问题描述】:

我正在尝试将我的DbContext 与多个提供商一起使用。到目前为止一切正常,除非使用HasDefaultValueSql()

我想要做的是在dbContext 上添加(和保存)具有当前时间的属性AuditTimestamp。这可以使用适用于 SQL Server 提供程序的类似方法轻松完成

modelBuilder
   .Entity(entityType.ClrType)
   .Property(nameof(AuditBase.AuditTimestamp))
   .HasDefaultValueSql("SYSUTCDATETIME()");

但是当尝试将相同的dbContext 与 SqLite 提供程序一起使用时,它不起作用,因为 SYSUTCDATETIME 在 Sqlite 中不是有效的 SQL 函数。

基于this question我尝试做

modelBuilder
   .Entity(entityType.ClrType)
   .Property(nameof(AuditBase.AuditTimestamp))
   .HasDefaultValueSql(Database.IsSqlite() ? "DATETIME('now')" : "SYSUTCDATETIME()");

但它会在迁移中生成这个

b.Property<DateTime>("AuditTimestamp")
   .ValueGeneratedOnAdd()
   .HasDefaultValueSql("SYSUTCDATETIME()");

因为我在生成迁移时使用了 SQL Server 连接字符串。这意味着我需要根据我使用的提供程序进行不同的迁移集,这不应该是这种情况。

基于this question,可以在迁移文件中包含提供程序特定的配置,但该示例基于自动生成的 Id 列,我不知道如何将其应用于HasDefaultValueSql()

来自here 我发现这可能有效

HasDefaultValueSql("CURRENT_TIMESTAMP")

但是,当您有两个(或更多)不共享相同 SQL 函数名称的提供程序时,我想要一个更通用的解决方案。

我使用的提供程序是用于生产的 SQL Server 和用于单元测试的 SqlLite。 EF Core 是 2.2 版

【问题讨论】:

  • 这是 EF Core 设计者的愿景 - Migrations with Multiple Providers
  • @IvanStoev,您能否给出解释如何使用链接中的注释的答案?
  • CURRENT_TIMESTAMP 和 SYSUTCDATETIME 提供相同的值吗?

标签: c# entity-framework-core


【解决方案1】:

因此,似乎规定的方法是手动编辑迁移(正如您在this answer 中指出的那样,Ivan 在 cmets 中建议您提出问题) 但是,我确实相信有一种方法可以在执行阶段覆盖实际的 SQL 代码生成。

您要查看的主要内容是MigrationsSqlGenerator 类。它为需要运行的每种类型的迁移命令构建 SQL。因此,在您的情况下,覆盖 DefaultValue 可能就足够了:

 public class CustomMigrationsSqlGenerator : MigrationsSqlGenerator
    {
        public CustomMigration(MigrationsSqlGeneratorDependencies dependencies) : base(dependencies)
        {
        }

        /// <param name="defaultValue">The default value for the column.</param>
        /// <param name="defaultValueSql">The SQL expression to use for the column's default constraint.</param>
        /// <param name="builder"></param>        
        /// <see cref="https://github.com/aspnet/EntityFrameworkCore/blob/v2.2.8/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs#L1407"/>
        protected override void DefaultValue(object defaultValue, string defaultValueSql, MigrationCommandListBuilder builder)
        {
            Debugger.Launch();
            if (defaultValueSql != null) // I assume you only want to adapt specific values rather than whole type
            {
                if (Dependencies.SqlGenerationHelper is SqliteSqlGenerationHelper)
                { 
                builder
                    .Append(" DEFAULT (") 
                    .Append(defaultValueSql) //replace with your specific provider logic
                    .Append(")");
                    return;
                } else if (Dependencies.SqlGenerationHelper is SqlServerSqlGenerationHelper)
                {
                    builder
                        .Append(" DEFAULT (") 
                        .Append(defaultValueSql) //replace with your specific provider logic
                        .Append(")");
                    return;
                }
            }
            //fall back to default implementation
            base.DefaultValue(defaultValue, defaultValueSql, builder);
        }
    }

现在我们有了自定义迁移生成器,我们需要替换 EF 的默认实现:

var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseWhateverDbProviderForMigrations();
optionsBuilder.ReplaceService<IMigrationsSqlGenerator, CustomMigrationsSqlGenerator>();
var context = new MyDbContext(optionsBuilder.Options);

我不太了解如何将 DbContext 引导到应用程序中以及如何调用迁移,但假设您是从包管理器控制台调用它,following trick 将允许您劫持控制:

 public class MigrationStartup: IDesignTimeDbContextFactory<MyDbContext>
    {
        public MyDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
            optionsBuilder.UseWhateverDbProviderForMigrations();
            optionsBuilder.ReplaceService<IMigrationsSqlGenerator, CustomMigration>(); // this is the important bit

            return new MyDbContext(optionsBuilder.Options);
        }
    }

这种方法可以扩展到生成的其他类型的 SQL,只需提供相应的覆盖即可。

需要注意的一点:在 EF 2.2 和 EF 3 之间,DefaultValue() 方法签名发生了变化。所以我想我应该警告你,这更像是一种黑客行为,并带有一定的支持含义。

【讨论】:

  • 我没有尝试过你的方法,但即使它有效,对于框架应该能够以更简单的方式处理的东西来说似乎还有很多工作。
猜你喜欢
  • 1970-01-01
  • 2021-03-29
  • 2018-08-09
  • 1970-01-01
  • 2022-08-02
  • 1970-01-01
  • 2011-09-09
  • 2019-11-12
  • 2020-06-16
相关资源
最近更新 更多