Adding an attribute to configure Decimal precision in Entity Framework Core
Asked Answered
S

2

8

I had the following attribute property for my EF 6 .Net Framework app:

[AttributeUsage(AttributeTargets.Property)]          
public sealed class DecimalPrecisionAttribute : Attribute
{              
    public DecimalPrecisionAttribute(Byte precision, Byte scale)
    {
        Precision = precision;
        Scale = scale;
    }

    public Byte Precision { get; set; }
    public Byte Scale { get; set; }
}

public class DecimalPrecisionAttributeConvention
    : PrimitivePropertyAttributeConfigurationConvention<DecimalPrecisionAttribute>
{
    public override void Apply(
        ConventionPrimitivePropertyConfiguration configuration,
        DecimalPrecisionAttribute attribute)
    {
        if (attribute.Precision < 1 || attribute.Precision > 38)
        {
            throw new InvalidOperationException("Precision must be between 1 and 38.");
        }

        if (attribute.Scale > attribute.Precision)
        {
            throw new InvalidOperationException(
                "Scale must be between 0 and the Precision value.");
        }

        configuration.HasPrecision(attribute.Precision, attribute.Scale);
    }
}

Then in my DbContext:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
        base.OnModelCreating(modelBuilder);
    }

This allowed me to set the precision on properties on my POCO's:

[DecimalPrecision(5,2)]
public Decimal WeirdNumber { get; set; }

But I am having trouble puzzling out how to do this for EF Core.

Is there an equivalent in .Core?

I have comme up with the following:

public static class DecimalPrecisionAttributeConvention
{
    public static void ApplyToTable(IMutableEntityType table)
    {
        foreach (var col in table.GetProperties())
        {
            var attribute = col
                ?.PropertyInfo
                ?.GetCustomAttributes<DecimalPrecisionAttribute>()
                ?.FirstOrDefault();

            if (attribute == null)
            {
                continue;
            }

            if (attribute.Precision < 1 || attribute.Precision > 38)
            {
                throw new InvalidOperationException("Precision must be between 1 and 38.");
            }

            if (attribute.Scale > attribute.Precision)
            {
                throw new InvalidOperationException(
                    "Scale must be between 0 and the Precision value.");
            }

            col.Relational().ColumnType =
                $"decimal({attribute.Precision}, {attribute.Scale})";
        }
    }
}

And thin my my on model create:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        foreach (var table in modelBuilder.Model.GetEntityTypes())
        {
            DecimalPrecisionAttributeConvention.ApplyToTable(table);
        }
    }

Is the the best way to accomplish this?

Sheffield answered 14/10, 2017 at 20:30 Comment(5)
Could you not use HasPrecision?Tranquilize
I could. However, 1.) I find fluent to be error-prone, too easy to forget to when you add or change a field in your POCO. 2.) I am porting a large data set that already has all the annotations on it.Sheffield
@Sheffield - If that works then it is the right way to do it as of now.Serviceberry
For future tracking issue github.com/aspnet/EntityFrameworkCore/issues/214Serviceberry
In EF-core 6, PrecisionAttribute was introduced.Batter
C
3

For EntityFrameworkCore, I use this solution. In your DbContext, override OnModelCreating method:

protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // Set default precision to decimal property
            foreach (var property in modelBuilder.Model.GetEntityTypes()
                .SelectMany(t => t.GetProperties())
                .Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
            {
                property.SetColumnType("decimal(18, 2)");
            }
        }

Hope this helps.

Chiropteran answered 17/2, 2020 at 10:22 Comment(0)
R
1

Since EF 6.0 you can use the native attribute for precision and scale!

public class Blog
{
    public int BlogId { get; set; }
    [Precision(14, 2)]
    public decimal Score { get; set; }
    [Precision(3)]
    public DateTime LastUpdated { get; set; }
}

If someone else is still interested in the EF Core equivalent code for custom attributes, here's an implementation for EF Core 6+

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class DecimalPrecisionAttribute(Byte precision, Byte scale) : Attribute
{
    public Byte Precision { get; set; } = precision;
    public Byte Scale { get; set; } = scale;
}

public class DecimalPrecisionAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) :
    PropertyAttributeConventionBase<DecimalPrecisionAttribute>(dependencies)
{
    protected override void ProcessPropertyAdded(
        IConventionPropertyBuilder propertyBuilder,
        DecimalPrecisionAttribute attribute,
        MemberInfo clrMember,
        IConventionContext context)
    {
        propertyBuilder.HasPrecision(attribute.Precision);
        propertyBuilder.HasScale(attribute.Scale);
    }
}


internal class DbContext : Microsoft.EntityFrameworkCore.DbContext
{
    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder.Conventions.Add(sp => new DecimalPrecisionAttributeConvention(
            sp.GetRequiredService<ProviderConventionSetBuilderDependencies>()));

}

Prior to EF Core 6 since 1.0 you can add the convention with the help of an IDbContextOptionsExtension which is described in this post.

Riobard answered 21/8, 2024 at 12:34 Comment(3)
The native attribute was introduced in EF-core 6. Therefore, if you offer an alternative, make sure it works with EF-core 5 and/or lower, otherwise it's pointless. You code works with EF core 6 and higher (ModelConfigurationBuilder is an EFC6 class).Batter
I found this thread searching for how to implement attribute conventions in ef core, not specifically for decimals. Since this is high ranked on google, I'm sure others will appreciate it ;) I will add a note, thanks for pointing it outRiobard
I'm sure others will appreciate an alternative that's useful for previous versions (although they shouldn't be used any more).Batter

© 2022 - 2025 — McMap. All rights reserved.