28

My client has a standard of storing SQL Server decimals with a decimal(13,4) specification. As a result, in a very large and still-growing schema, I have nearly a hundred statements like these:

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)");

If there is a feature where I can tell EF directly that all decimals should be decimal(13,4) by default, I would like to use that. If not, can I use reflection to loop through every object/property in the model so I can do this in a couple statements?

Something like:

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)");
        }
    }
}

Reflection seems like a great way to go, because then I can implement some of our other conventions where, if an object has a Name and Description, the Name is required and limited to 256 chars.

Update: I followed the link in Ivan's comment and adapted it to this, which works for me:

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)";
}

Soon after, he provided a full answer, which I changed slightly to work with both decimal and nullable decimal:

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)");
}

Both approaches work!

Update 2: I had to have my objects declared as DbSet<> in the context for the above to work. This didn't seem to be required when I was setting properties line by line.

Christopher
  • 10,409
  • 13
  • 73
  • 97
  • 1
    See [change all string property max length](http://stackoverflow.com/questions/41427030/change-all-string-property-max-length/41427828#41427828) for something similar in v1.1.0 (can't test in v.1.0) – Ivan Stoev Jan 04 '17 at 16:33
  • 1
    Regarding Update 2: The code should be at the end of the `OnModelCreating` after all entity types are discovered, thus including non `DbSet` types, introduced for instance with `modelBuilder.Entity<..>` calls. – Ivan Stoev Jan 05 '17 at 00:48
  • @IvanStoev that makes sense, but I had some `modelBuilder.Entity<..>` statements in there solely to specify the decimal column type, so those statements are gone now. – Christopher Jan 06 '17 at 19:02
  • You need to have something in order to let EF Core treat your class as entity (it's not reflecting your assembly like EF6). AFAIK it should be either `DbSet`, navigation property referring to entity class (either simple or collection) or `modelBuilder.Entity<..>`. If none of these exists, I don't see how the class will be mapped to a table. Of course the method looks more like workaround rather than a solution like EF6 `modelBuilder.Type<...>` configuration, but are you saying that if you move the procedure at the end of the `OnModelCreating`, it misses some entitities? I'm really interested. – Ivan Stoev Jan 06 '17 at 19:11
  • [The answer is very simple as of EF Core 6.0](https://stackoverflow.com/a/69922761/5405967). – MarredCheese Nov 11 '21 at 03:22

4 Answers4

54

In EF Core v1.1.0 you can use something like this:

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)");
}

Update (EF Core 2.x): Starting from EF Core 2.0, the model is built separately for each database provider, so HasAbcXyz methods are replaced with common HasXyz. The updated code (which also skips the explicitly configured properties) looks like this:

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)";
}

Update (EF Core 3.x): With EF Core 3.0 metadata API changes (Relational() extensions removed, properties replaced with Get / Set method pair), the code is as follows:

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)");
}

Update (Entity Framework Core 6): EF Core 6 includes convention model configuration that can be used to achieve this to all types. The advantage over looping through entities manually, is that this conventions already ignore certain types (like Ignore(), or properties that have Converters).

public class SomeDbContext : DbContext
{
    protected override void ConfigureConventions(
        ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder
                .Properties<decimal>()
                .HavePrecision(19, 4);
    }
}
Luke
  • 743
  • 6
  • 20
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 5
    This has changed a little, at least for ef core 2.0. Instead of ForSqlServer, you need to use the nuget Microsoft.EntityFrameworkCore.SqlServer. The extension is named HasColumnType. – Joshit Aug 29 '17 at 13:55
  • 1
    pb.HasColumnType("decimal(13,4)"); would be right if one uses SqlServer – Joshit Aug 29 '17 at 13:56
  • 1
    For EF Core 3.0, they flattened the extension methods. Now use if (property.GetColumnType() == null) { property.SetColumnType("decimal(13,4)"); } https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#provider-specific-metadata-api-changes – Josh McKearin Sep 17 '19 at 17:00
  • @JoshMcKearin Correct. I'm just waiting for 3.0 RTM before posting update (most probably they won't change them from Preview, but one never knows :) – Ivan Stoev Sep 17 '19 at 17:53
  • The RC was released yesterday and can be used in production :D https://devblogs.microsoft.com/dotnet/announcing-net-core-3-0-release-candidate-1/ – Josh McKearin Sep 17 '19 at 20:12
  • could you please update the solution as per @JoshMcKearin comment because from EF core 3+ RelationalMetadataExtensions has been removed – Anjo Apr 08 '20 at 02:17
  • @Anjo Sure, here it is. – Ivan Stoev Apr 08 '20 at 07:40
  • For `EF Core 3.x` you need to install the NuGet `Microsoft.EntityFrameworkCore.Relational` to get the extension method `SetColumnType`. – Ogglas Aug 26 '20 at 20:21
8

EF Core 6.0

It's now simple to configure defaults for every decimal property (or string, etc.):

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

More info: pre-convention model configuration

sommmen
  • 6,570
  • 2
  • 30
  • 51
MarredCheese
  • 17,541
  • 8
  • 92
  • 91
  • nice! was looking for exactly this. I edited you code to actually compile with net 6 now - `HavePrecision` instead of has. – sommmen Jan 25 '22 at 09:49
4

The approach mentioned above does not work when I'm working on EFCore 5.0.1 DB-First. The method below on MS document works:

[Column(TypeName = "decimal(18, 4)")]
public decimal Numeric { get; set; }
Mason Zhang
  • 3,423
  • 24
  • 35
0

New feature will be introduced in EF Core 5.0

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

Reference : https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#preview-4

Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197