【问题标题】:Loop/reflect through all properties in all EF Models to set Column Type循环/反映所有 EF 模型中的所有属性以设置列类型
【发布时间】:2017-05-19 00:48:10
【问题描述】:

我的客户端有一个存储 SQL Server 小数的标准,其规格为 decimal(13,4)。因此,在一个非常庞大且仍在增长的架构中,我有近百个这样的语句:

builder.Entity<MyObject>()
    .Property(x => x.MyField1)
    .ForSqlServerHasColumnType("decimal(13,4)");
builder.Entity<MyObject>()
    .Property(x => x.MyField2)
    .ForSqlServerHasColumnType("decimal(13,4)");
builder.Entity<MyObject2>()
    .Property(x => x.MyField1)
    .ForSqlServerHasColumnType("decimal(13,4)");

如果有一个功能可以让我直接告诉 EF 所有小数默认都应该是小数(13,4),我想使用它。如果不是,我可以使用反射来遍历模型中的每个对象/属性,以便我可以在几个语句中执行此操作吗?

类似:

foreach(var efObj in EntityFrameWorkObjects)
{
    foreach (var objProperty in efObj)
    {
        if (objProperty is decimal || objProperty is decimal?)
        {
            builder.Entity<efObj>()
                .Property(x => x.efObj)
                .ForSqlServerHasColumnType("decimal(13,4)");
        }
    }
}

反射似乎是一个很好的方法,因为这样我可以实现我们的一些其他约定,如果对象有名称和描述,则名称是必需的,并且限制为 256 个字符。

更新: 我按照 Ivan 评论中的链接进行了修改,这对我有用:

foreach (var p in builder.Model
    .GetEntityTypes()
    .SelectMany(t => t.GetProperties())
    .Where(p => 
        p.ClrType == typeof(decimal) ||
        p.ClrType == typeof(decimal?)))
{
    p.SqlServer().ColumnType = "decimal(13,4)";
}

不久之后,他提供了一个完整的答案,我稍作改动以使用十进制和可为空的十进制:

foreach (var pb in builder.Model
    .GetEntityTypes()
    .SelectMany(t => t.GetProperties())
    .Where(p => 
        p.ClrType == typeof(decimal) ||
        p.ClrType == typeof(decimal?))
    .Select(p => 
        builder.Entity(p.DeclaringEntityType.ClrType)
            .Property(p.Name)))
{
    pb.ForSqlServerHasColumnType("decimal(13,4)");
}

这两种方法都有效!

更新 2: 我必须在上下文中将我的对象声明为 DbSet 才能使上述内容起作用。当我逐行设置属性时,这似乎不是必需的。

【问题讨论】:

  • 查看 change all string property max length 以了解 v1.1.0 中的类似内容(无法在 v.1.0 中测试)
  • 关于更新 2:发现所有实体类型后,代码应位于 OnModelCreating 的末尾,因此包括非 DbSet 类型,例如通过 modelBuilder.Entity&lt;..&gt; 调用引入。跨度>
  • @IvanStoev 说得有道理,但我有一些 modelBuilder.Entity&lt;..&gt; 语句仅用于指定小数列类型,所以这些语句现在已经不存在了。
  • 您需要有一些东西才能让 EF Core 将您的类视为实体(它不像 EF6 那样反映您的程序集)。 AFAIK 它应该是DbSet,导航属性引用实体类(简单或集合)或modelBuilder.Entity&lt;..&gt;。如果这些都不存在,我看不到该类将如何映射到表。当然,该方法看起来更像是解决方法,而不是像 EF6 modelBuilder.Type&lt;...&gt; 配置这样的解决方案,但是您是说如果您在 OnModelCreating 末尾移动该过程,它会遗漏一些实体吗?我真的很感兴趣。

标签: entity-framework asp.net-core entity-framework-core


【解决方案1】:

在 EF Core v1.1.0 中,您可以使用如下内容:

foreach (var pb in modelBuilder.Model
    .GetEntityTypes()
    .SelectMany(t => t.GetProperties())
    .Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?))
    .Select(p => modelBuilder.Entity(p.DeclaringEntityType.ClrType).Property(p.Name)))
{
    pb.ForSqlServerHasColumnType("decimal(13,4)");
}

更新(EF Core 2.x):从 EF Core 2.0 开始,模型是为每个数据库提供者单独构建的,因此HasAbcXyz 方法被替换为常见的HasXyz。更新后的代码(也跳过了显式配置的属性)如下所示:

foreach (var property in modelBuilder.Model.GetEntityTypes()
    .SelectMany(t => t.GetProperties())
    .Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
{
    if (property.Relational().ColumnType == null)
        property.Relational().ColumnType = "decimal(13,4)";
}

更新 (EF Core 3.x): 随着 EF Core 3.0 元数据 API 的更改(删除了 Relational() 扩展,属性替换为 Get / Set 方法对),代码为如下:

foreach (var property in modelBuilder.Model.GetEntityTypes()
    .SelectMany(t => t.GetProperties())
    .Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
{
    if (property.GetColumnType() == null)
        property.SetColumnType("decimal(13,4)");
}

更新(实体框架 6): EF6 包括 convention model configuration,可用于对所有类型实现此目的。 与手动循环实体相比的优势在于,这种约定已经忽略了某些类型(如Ignore(),或具有转换器的属性)。

public class SomeDbContext : DbContext
{
    protected override void ConfigureConventions(
        ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder
                .Properties<decimal>()
                .HavePrecision(19, 4);
    }
}

【讨论】:

  • 这发生了一些变化,至少对于 ef core 2.0。您需要使用 nuget Microsoft.EntityFrameworkCore.SqlServer 而不是 ForSqlServer。该扩展名为 HasColumnType。
  • pb.HasColumnType("decimal(13,4)");如果使用 SqlServer 会是正确的
  • 对于 EF Core 3.0,他们扁平化了扩展方法。现在使用 if (property.GetColumnType() == null) { property.SetColumnType("decimal(13,4)"); } docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/…
  • @JoshMcKearin 正确。我只是在发布更新之前等待 3.0 RTM(很可能他们不会从预览中更改它们,但永远不知道 :)
  • RC昨天发布,可以在生产中使用了:D devblogs.microsoft.com/dotnet/…
【解决方案2】:

EF Core 6.0

现在可以很简单地为每个 decimal 属性(或 string 等)配置默认值:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder
        .Properties<decimal>()
        .HavePrecision(19, 4);
}

更多信息:pre-convention model configuration

【讨论】:

  • 不错!正是在寻找这个。我编辑了你的代码,现在用 net 6 实际编译 - HavePrecision 而不是 has。
【解决方案3】:

当我使用 EFCore 5.0.1 DB-First 时,上述方法不起作用。 MS document 上的以下方法有效:

[Column(TypeName = "decimal(18, 4)")]
public decimal Numeric { get; set; }

【讨论】:

    【解决方案4】:

    新功能将在 EF Core 5.0 中引入

    modelBuilder
        .Entity<Blog>()
        .Property(b => b.Numeric)
        .HasPrecision(16, 4);
    

    参考:https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#preview-4

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-07
      • 1970-01-01
      • 1970-01-01
      • 2012-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多