table 创建后的 EFCore EnsureCreated() 运行 命令

EFCore EnsureCreated() run command after table creation

所以我是 EFCore 的新手(直接进入 5.0 预览版,因为为什么不),并且享受所有流畅的配置方面。整个 IEntityTypeConfiguration<> 概念确实有助于使实体对支持数据库不可知(而不是在实体本身上使用属性)。

我在 Startup.cs 中使用 DbContext.Database.EnsureCreated() 生成整个数据库。当我在玩的时候,它目前由 SQLite 支持,但我想最终转移到带有 TimescaleDB 扩展的 PostgreSQL。

问题是,在使用 TimescaleDB 时,我需要在创建 table 本身后立即发出 create_hypertable()

table 创建后发出命令的最佳方式是什么?

谢谢!

如果您想继续使用 context.Database.EnsureCreated(),那么 运行 您自己的脚本(例如,通过执行 context.Database.ExecuteSqlRaw())是在调用之后的方法。

That means all the tables have been created before the SQL is executed. I would prefer having it as close to the table creation as possible.

据我所知,这应该没有任何缺点,因此通过将 create_hypertable() 调用靠近另一个 table 不会获得任何好处(如果您不同意,请详细说明为什么会这样)。


如果您真的 出于某种原因想要将调用移动到另一个命令附近,那么您可以实施 DbCommandInterceptor。然后您可以解析特定 CREATE TABLE 语句或其他内容的命令文本,然后发出您自己的语句。 (如果你想走那条路,post 发表评论,我会用一些代码更新这个答案。)


如果您从使用 context.Database.EnsureCreated() 切换到迁移,您可以使用 Sql() 方法来注入您自己的语句:

public partial class MyMigration : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql("SELECT create_hypertable('conditions', 'time');")
    }
}

也许你可以详细说明一下,为什么你觉得有必要将 create_hypertable() 调用更接近他们的 table。


如果您只关心在 C# 代码中关闭 create_hypertable() 调用,但不一定在生成的 SQL 代码中关闭,则引入自定义属性(或接口)和方法将 hypertable 应用于所有具有用此属性装饰的属性(或实现此接口)的实体可能就足够了:

[AttributeUsage(AttributeTargets.Property)]
public class HypertableColumnAttribute : Attribute
{
}

public static class DbContextExtensions
{
    public static void ApplyHypertables(this Context context)
    {
        var entityTypes = context.Model.GetEntityTypes();

        foreach (var entityType in entityTypes)
        {
            foreach (var property in entityType.GetProperties())
            {
                if (property.ClrType.GetCustomAttribute(
                    typeof(HypertableColumnAttribute)) != null)
                {
                    var tableName = entityType.GetTableName();
                    var columnName = property.GetColumnName();

                    context.Database.ExecuteSqlInterpolated(
                        $"SELECT create_hypertable({tableName}, {columnName});");
                }
            }
        }
    }
}

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

    [HypertableColumn]
    public DateTime? UpdatedAt { get; set; }
}

public class Context : DbContext
{
    public DbSet<IceCream> IceCreams { get; set; }

    /// ...
}

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

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

        var iceCreams = context.IceCreams
            .OrderBy(i => i.IceCreamId)
            .ToList();
        
        Debug.Assert(iceCreams.Count == 2);
    }
}

如果您想保持模型 类 没有任何特定于数据库的属性,您可以改用注释。想法基本相同,但注释是 EF Core 元数据,可以使用 HasAnnotation() 方法定义为 IEntityTypeConfiguration<T> 实现的一部分:

public static class DbContextExtensions
{
    public static void ApplyHypertables(this Context context)
    {
        var entityTypes = context.Model.GetEntityTypes();

        foreach (var entityType in entityTypes)
        {
            foreach (var property in entityType.GetProperties())
            {
                var isHypertableColumn = property.FindAnnotation(MyAnnotationNames.Hypertable)?.Value;
                if ((bool)(isHypertableColumn ?? false))
                {
                    var tableName = entityType.GetTableName();
                    var columnName = property.GetColumnName();

                    context.Database.ExecuteSqlInterpolated(
                        $"SELECT create_hypertable({tableName}, {columnName});");
                }
            }
        }
    }
}

public static class MyAnnotationNames
{
    public const string Prefix = "MyPrefix:";
    public const string Hypertable = Prefix + "Hypertable";
}

public class IceCream
{
    public int IceCreamId { get; set; }
    public string Name { get; set; }
    public DateTime? UpdatedAt { get; set; }
}

public class IceCreamConfiguration : IEntityTypeConfiguration<IceCream>
{
    public void Configure(EntityTypeBuilder<IceCream> builder)
    {
        builder.Property(e => e.UpdatedAt)
            .HasAnnotation(MyAnnotationNames.Hypertable, true);
        
        builder.HasData(
            new IceCream {IceCreamId = 1, Name = "Vanilla"},
            new IceCream {IceCreamId = 2, Name = "Chocolate"});
    }
}

public class Context : DbContext
{
    public DbSet<IceCream> IceCreams { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
        => modelBuilder.ApplyConfiguration(new IceCreamConfiguration());

    /// ...
}

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

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

        var iceCreams = context.IceCreams
            .OrderBy(i => i.IceCreamId)
            .ToList();
        
        Debug.Assert(iceCreams.Count == 2);
    }
}