Decimal precision and scale in EF Code First
Asked Answered
M

19

290

I'm experimenting with this code-first approach, but I'm find out now that a property of type System.Decimal gets mapped to a sql column of type decimal(18, 0).

How do I set the precision of the database column?

Mezzorelievo answered 17/8, 2010 at 16:20 Comment(1)
one way is to use [Column(TypeName = "decimal(18,4)")] attribute for your decimal propertiesOctastyle
L
313

The answer from Dave Van den Eynde is now out of date. There are 2 important changes, from EF 4.1 onwards the ModelBuilder class is now DbModelBuilder and there is now a DecimalPropertyConfiguration.HasPrecision Method which has a signature of:

public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )

where precision is the total number of digits the db will store, regardless of where the decimal point falls and scale is the number of decimal places it will store.

Therefore there is no need to iterate through properties as shown but the can just be called from

public class EFDbContext : DbContext
{
   protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
   {
       modelBuilder.Entity<Class>().Property(object => object.property).HasPrecision(12, 10);

       base.OnModelCreating(modelBuilder);
   }
}
Lankton answered 14/10, 2011 at 14:30 Comment(13)
For anyone that is getting issues with the DbModelBuilder, try System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilderBurble
I noticed you never called base.OnModelCreating(modelBuilder);. Was that intentional or just a victim of typing code online instead of in an IDE?Enabling
@Enabling thanks for the spot, this is my omission, not anything intentional. I will edit the answer.Lankton
The 2 arguments to HasPrecision(precision, scale) are poorly documented. precision is the total number of digits it will store, regardless of where the decimal point falls. scale is the number of decimal places it will store.Murchison
Code second with power tools did not do this for me! Now my database type numeric(18,4) got truncated to numeric(18,2) by EF model. Thanks for this tip.Cap
Is there a EF configuration to set it for all decimal properties on all entities in one place? We generally use (19,4) . It would be nice to have this automatically be applied to all decimal properties, so we can't forget to set a property precision and miss anticipated precision in calculations.Eanes
Worked perfect for me. I noticed this issue specifically in SQL Server LocalDb and Sqlite.Windle
@ChrisMoschini your comment was the answer ;-)Buckthorn
Why not simply use [Column(TypeName = "decimal(18,4)")] attribute ?Octastyle
@Octastyle I was hoping that was the answer -- it was simple and could be attached to the property -- but it didn't work for me. I kept getting the error that "the store type decimal(18,4) was not found in the manifest." Using EF 6.2. What finally worked for me was kjbartel's DecimalPrecisionAttribute solution below.Panorama
In Property(object => object.property), change object to x to get it compilingZeena
This line base.OnModelCreating(modelBuilder); did it for me. Thanks @LanktonUntrue
In EF5 I tried .HasColumnType() and I tried HasPrecision(), but the warning is still displaying in the log file. How do I make the warning go away?Extirpate
C
105

If you want to set the precision for all decimals in EF6 you could replace the default DecimalPropertyConvention convention used in the DbModelBuilder:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Remove<DecimalPropertyConvention>();
    modelBuilder.Conventions.Add(new DecimalPropertyConvention(38, 18));
}

The default DecimalPropertyConvention in EF6 maps decimal properties to decimal(18,2) columns.

If you only want individual properties to have a specified precision then you can set the precision for the entity's property on the DbModelBuilder:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<MyEntity>().Property(e => e.Value).HasPrecision(38, 18);
}

Or, add an EntityTypeConfiguration<> for the entity which specifies the precision:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new MyEntityConfiguration());
}

internal class MyEntityConfiguration : EntityTypeConfiguration<MyEntity>
{
    internal MyEntityConfiguration()
    {
        this.Property(e => e.Value).HasPrecision(38, 18);
    }
}
Cornflakes answered 3/4, 2014 at 9:45 Comment(1)
My favorite solution. Works perfect when using CodeFirst and migrations: EF looks for all properties in all classes where "decimal" is used and generates a migration for these properties. Great!Ernestoernestus
S
83

I had a nice time creating an Custom Attribute for this:

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
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; }

}

using it like this

[DecimalPrecision(20,10)]
public Nullable<decimal> DeliveryPrice { get; set; }

and the magic happens at model creation with some reflection

protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
    foreach (Type classType in from t in Assembly.GetAssembly(typeof(DecimalPrecisionAttribute)).GetTypes()
                                   where t.IsClass && t.Namespace == "YOURMODELNAMESPACE"
                                   select t)
     {
         foreach (var propAttr in classType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttribute<DecimalPrecisionAttribute>() != null).Select(
                p => new { prop = p, attr = p.GetCustomAttribute<DecimalPrecisionAttribute>(true) }))
         {

             var entityConfig = modelBuilder.GetType().GetMethod("Entity").MakeGenericMethod(classType).Invoke(modelBuilder, null);
             ParameterExpression param = ParameterExpression.Parameter(classType, "c");
             Expression property = Expression.Property(param, propAttr.prop.Name);
             LambdaExpression lambdaExpression = Expression.Lambda(property, true,
                                                                      new ParameterExpression[]
                                                                          {param});
             DecimalPropertyConfiguration decimalConfig;
             if (propAttr.prop.PropertyType.IsGenericType && propAttr.prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
             {
                 MethodInfo methodInfo = entityConfig.GetType().GetMethods().Where(p => p.Name == "Property").ToList()[7];
                 decimalConfig = methodInfo.Invoke(entityConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;
             }
             else
             {
                 MethodInfo methodInfo = entityConfig.GetType().GetMethods().Where(p => p.Name == "Property").ToList()[6];
                 decimalConfig = methodInfo.Invoke(entityConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;
             }

             decimalConfig.HasPrecision(propAttr.attr.Precision, propAttr.attr.Scale);
        }
    }
}

the first part is to get all classes in the model (my custom attribute is defined in that assembly so i used that to get the assembly with the model)

the second foreach gets all properties in that class with the custom attribute, and the attribute itself so i can get the precision and scale data

after that i have to call

modelBuilder.Entity<MODEL_CLASS>().Property(c=> c.PROPERTY_NAME).HasPrecision(PRECISION,SCALE);

so i call the modelBuilder.Entity() by reflection and store it in the entityConfig variable then i build the "c => c.PROPERTY_NAME" lambda expression

After that, if the decimal is nullable i call the

Property(Expression<Func<TStructuralType, decimal?>> propertyExpression) 

method (i call this by the position in the array, it's not ideal i know, any help will be much appreciated)

and if it's not nullable i call the

Property(Expression<Func<TStructuralType, decimal>> propertyExpression)

method.

Having the DecimalPropertyConfiguration i call the HasPrecision method.

Sogdiana answered 13/3, 2013 at 13:36 Comment(7)
Thanks for this. It saved me from generating thousands of lambda expressions.Sheriesherif
This works great, and is super clean! For EF 5, I changed System.Data.Entity.ModelConfiguration.ModelBuilder to System.Data.Entity.DbModelBuilderEnvirons
i use MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() }); to get the correct overload. seems to work so far.Camarena
I've wrapped this up into a library and made it easier to call from the DbContext: github.com/richardlawley/EntityFrameworkAttributeConfig (also available via nuget)Doublet
Richard, I love the idea of your project but is there anything about it that requires EF6? I would use it if there was an EF5-compatible version, so that I can use it with my version of ODP.NET.Turgor
this has a giant bug: MethodInfo methodInfo = entityConfig.GetType().GetMethods().Where(p => p.Name == "Property").ToList()[7]; 7 here is hard-coded and assumes that the type will be nullable decimal. But for some reason, my .net decided to switch up the order and now datetime is at index 7 in that list and makes the whole thing blow up.Madagascar
@Madagascar as the code is 5 years old .NET may have changed things up a bitSogdiana
C
53

Using the DecimalPrecisonAttribute from KinSlayerUY, in EF6 you can create a convention which will handle individual properties which have the attribute (as opposed to setting the DecimalPropertyConvention like in this answer which will affect all decimal properties).

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
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 your DbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
}
Cornflakes answered 3/4, 2014 at 7:56 Comment(10)
@MichaelEdenfield Actually no there isn't one of these in EF6. Hence why I added two answers, this one and the other one you referred to. This is an attribute you can put on a single decimal property rather than affecting all decimal properties in the model.Cornflakes
my bad, didn't notice you wrote them both :\Corbitt
If you are going to bounds-check Precision, then I recommend setting the upper bound to 28 (so > 28 in your condition). According to MSDN documentation, System.Decimal can only represent a maximum of 28-29 digits of precision (msdn.microsoft.com/en-us/library/364x0z75.aspx). Also, the attribute declares Scale as byte, which means your precondition attribute.Scale < 0 is unnecessary.Assassinate
@Assassinate the precision is not for the System.Decimal it is for the SQL decimal column which has a maximum precision of 38. They are not the same thing though as the SQL decimal is fixed point (specified by the scale) not floating point like the System.Decimal.Cornflakes
@Cornflakes It is true that some database providers support precisions larger than 28; however, according to MSDN, System.Decimal does not. Therefore it makes no sense to set the upper bound precondition to anything larger than 28; System.Decimal can't represent numbers that large, apparently. Also, be aware that this attribute is useful for data providers other than SQL Server. For example, PostgreSQL's numeric type supports up to 131072 digits of precision.Assassinate
@Assassinate Like I said, databases use a fixed point decimal (msdn) whereas System.Decimal is floating point. They are completely different. For example having a decimal(38,9) column will happy hold the System.Decimal.MaxValue but a decimal(28,9) column will not. There is no reason to limit the precision to only 28.Cornflakes
I needed to define precision for geographical coordinates (lat and lon). This is by far the cleanest solution. Now, every class that has lat and lon properties, has them decorated with the proper attribute and everything works without any other additional code.Urbannai
according to msdn , maxvalue for decimal uses 29 digits and you can't uses 999... when using 29 digits, so it support 28 full and 29 partial. In both cases 38 digits is impossible to use in a decimal in c#.Euratom
@Euratom We are talking about SQL decimal not .Net. They are completely different as one is fixed point and the other floating point, that is why you need greater precision on the sql side to account for the floating range of .Net decimal. This is already explained above in the comments.Cornflakes
This is definitely the nicest solution if you want to use code first data annotations.Storer
M
49

Apparently, you can override the DbContext.OnModelCreating() method and configure the precision like this:

protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>().Property(product => product.Price).Precision = 10;
    modelBuilder.Entity<Product>().Property(product => product.Price).Scale = 2;
}

But this is pretty tedious code when you have to do it with all your price-related properties, so I came up with this:

    protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
    {
        var properties = new[]
        {
            modelBuilder.Entity<Product>().Property(product => product.Price),
            modelBuilder.Entity<Order>().Property(order => order.OrderTotal),
            modelBuilder.Entity<OrderDetail>().Property(detail => detail.Total),
            modelBuilder.Entity<Option>().Property(option => option.Price)
        };

        properties.ToList().ForEach(property =>
        {
            property.Precision = 10;
            property.Scale = 2;
        });

        base.OnModelCreating(modelBuilder);
    }

It's good practice that you call the base method when you override a method, even though the base implementation does nothing.

Update: This article was also very helpful.

Mezzorelievo answered 17/8, 2010 at 16:39 Comment(9)
Thanks, this pointed me in the right direction. In CTP5 the syntax has changed to allow adding Precision and Scale in the same statement : modelBuilder.Entity<Product>().Property(product => product.Price).HasPrecision(6, 2);Quetzalcoatl
Still, wouldn't it be nice to have some sort of "default" you could set for all decimals?Mezzorelievo
I don't think calling base.OnModelCreating(modelBuilder); is necessary. From the DbContext metadata in VS: The default implementation of this method does nothing, but it can be overridden in a derived class such that the model can be further configured before it is locked down.Tumult
@Matt: That's nice, but as an implementor I shouldn't care about this and always call the base.Mezzorelievo
@ Dave and @Matt: There was a comment it was "IMPORTANT" to call base. It is good practice, but when the EF source has an empty implementation , it is misleading to claim it is Important. That leaves people wondering what the base does. I was so curious what was IMPORTANT i decompiled to the ef5.0 to check. Nothing there. So just a good habit.Bellinzona
Well, my answer was written 2,5 years ago for EF-Idontremember.0, and I don't recall what made me write the "apparently" comment.Mezzorelievo
@soadyp I edited the answer to reflect my changed view in this matter. I hope you agree.Mezzorelievo
This is a general question. Should you not call base.OnModelCreating first, then the overriding code? Build foundation first then build first floor, second floor etc?Gaulish
@SoundarRajan it depends, in this case it does nothing so it doesn't matter. You typically call base implementation first, but I've seen examples where it was called last. Could be bad API design as well, but that's another topic.Mezzorelievo
P
49
[Column(TypeName = "decimal(18,2)")]

this will work with EF Core code first migrations as described here.

Paderna answered 18/7, 2018 at 4:33 Comment(4)
If you just add this to your model, you can get The store type 'decimal(18,2)' could not be found in the SqlServer provider manifestZeena
@Zeena looks like it is an issue with your database provider or the version of databasePaderna
@Paderna Savage is correct, this will throw an error in EF Migrations 6.x. The legacy, non-Core version does not support specifying precision/scale via the Column attribute, and does nothing (defaults to 18,2) if you use the DataType attribute. To get it to work via Attribute in EF 6.x, you would need to implement your own extension to ModelBuilder.Murchison
@ChrisMoschini, i changed my answer mentioning EF Core. ThanksPaderna
I
33

Entity Framework Ver 6 (Alpha, rc1) has something called Custom Conventions. To set decimal precision:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Properties<decimal>().Configure(config => config.HasPrecision(18, 4));
}

Reference:

Instigation answered 18/9, 2013 at 14:33 Comment(0)
C
27

Edit, From .NET 6, this can been replaced with precision attribute

[Precision(precision, scale)]

Previous version of EF Core:

[Column(TypeName = "decimal(precision, scale)")]

Definitions:

Precision = Total number of characters used

Scale = Total number after the dot. (easy to get confused)

Example:

using System.ComponentModel.DataAnnotations; //.Net Core
using Microsoft.EntityFrameworkCore; //.NET 6+

public class Blog
{
    public int BlogId { get; set; }
    [Column(TypeName = "varchar(200)")]
    public string Url { get; set; }
    [Column(TypeName = "decimal(5, 2)")]
    public decimal Rating { get; set; }
    [Precision(28, 8)]
    public decimal RatingV6 { get; set; }
}

More details here: https://learn.microsoft.com/en-us/ef/core/modeling/relational/data-types

Chaliapin answered 13/9, 2018 at 17:32 Comment(0)
B
26

From .NET EF Core 6 onwards you can use the Precision attribute.

    [Precision(18, 2)]
    public decimal Price { get; set; }

make sure that you need to install EF Core 6 and do following using line

using Microsoft.EntityFrameworkCore;
Beezer answered 22/11, 2021 at 16:52 Comment(1)
Should be the accepted answer from EF Core 6+, because this is database independentCoggins
D
15

this code line could be a simpler way to acomplish the same:

 public class ProductConfiguration : EntityTypeConfiguration<Product>
    {
        public ProductConfiguration()
        {
            this.Property(m => m.Price).HasPrecision(10, 2);
        }
    }
Destructor answered 13/10, 2011 at 1:52 Comment(1)
Work perfect on EF Core 7.Holmann
P
4

You can always tell EF to do this with conventions in the Context class in the OnModelCreating function as follows:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // <... other configurations ...>
    // modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    // modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
    // modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

    // Configure Decimal to always have a precision of 18 and a scale of 4
    modelBuilder.Conventions.Remove<DecimalPropertyConvention>();
    modelBuilder.Conventions.Add(new DecimalPropertyConvention(18, 4));

    base.OnModelCreating(modelBuilder);
}

This only applies to Code First EF fyi and applies to all decimal types mapped to the db.

Precarious answered 24/8, 2016 at 11:42 Comment(1)
It was not working until Remove<DecimalPropertyConvention>(); comes before the Add(new DecimalPropertyConvention(18, 4));. I think it is strange that is not just overridden automatically.Eanes
T
3

In EF6

modelBuilder.Properties()
    .Where(x => x.GetCustomAttributes(false).OfType<DecimalPrecisionAttribute>().Any())
    .Configure(c => {
        var attr = (DecimalPrecisionAttribute)c.ClrPropertyInfo.GetCustomAttributes(typeof (DecimalPrecisionAttribute), true).FirstOrDefault();

        c.HasPrecision(attr.Precision, attr.Scale);
    });
Twentieth answered 20/2, 2014 at 13:22 Comment(1)
This answer seems to be an upgrade to another answer that defines the attribute, you should edit this into that answerHysteresis
R
3

Using

System.ComponentModel.DataAnnotations;

You can simply put that attribute in your model :

[DataType("decimal(18,5)")]
Renegado answered 9/3, 2017 at 21:22 Comment(3)
this is the easiest implementation for readability and simplicity. IMHOClipper
Per msdn.microsoft.com/en-us/library/jj591583(v=vs.113).aspx, this answer is factually incorrect. "Don’t confuse Column’s TypeName attribute with the DataType DataAnnotation. DataType is an annotation used for the UI and is ignored by Code First."Guienne
@Clipper I thought so too, until I just tested it and as was said above, this does not work for CodeFirst and does not migrate to the databaseImpossible
S
2

This is what I was looking for, works for ordinary MVC project (No .Net Core)

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<YOUR_CLASS_NAME>().Property(x => x.YOUR_DECIAML_PROP).HasPrecision(18, 6);

        base.OnModelCreating(modelBuilder);

    }
}

Package manager console

add-migration changeDecimalPrecision

Generated migration

    public override void Up()
    {
        AlterColumn("dbo.YOUR_CLASS_NAME", "YOUR_DECIAML_PROP", c => c.Decimal(nullable: false, precision: 18, scale: 6));
    }
Stiver answered 9/6, 2022 at 13:27 Comment(1)
The accepted answser already tells this (and more answers). Please don't post answers if they don't add new information.Filia
F
1

You can found more information on MSDN - facet of Entity Data Model. http://msdn.microsoft.com/en-us/library/ee382834.aspx Full recommended.

Fenugreek answered 9/6, 2011 at 16:57 Comment(2)
That's great and all, but how does that relate to Code-First?Mezzorelievo
It is useful but I can't specify a [Precision] attribute for a Decimal, still. So I used the solution provided by @KinSlayerUY.Environs
V
1

Actual for EntityFrameworkCore 3.1.3:

some solution in OnModelCreating:

var fixDecimalDatas = new List<Tuple<Type, Type, string>>();
foreach (var entityType in builder.Model.GetEntityTypes())
{
    foreach (var property in entityType.GetProperties())
    {
        if (Type.GetTypeCode(property.ClrType) == TypeCode.Decimal)
        {
            fixDecimalDatas.Add(new Tuple<Type, Type, string>(entityType.ClrType, property.ClrType, property.GetColumnName()));
        }
    }
}

foreach (var item in fixDecimalDatas)
{
    builder.Entity(item.Item1).Property(item.Item2, item.Item3).HasColumnType("decimal(18,4)");
}

//custom decimal nullable:
builder.Entity<SomePerfectEntity>().Property(x => x.IsBeautiful).HasColumnType("decimal(18,4)");
Valenciavalenciennes answered 20/4, 2020 at 17:50 Comment(0)
B
1

you can simply set decimal pricision and scale from Model Design class by using Column Attribute or Precision Attribute like following :

  // Option # 1
         [Column(TypeName ="decimal(10,3)")]
            public decimal Price { get; set; } = 5.000;
        
     //Option # 2:  you can also use the ***Precision Attribute*** like the following sample code :
         [Precision(10, 3)]
         public decimal Price { get; set; }=5.000;
Billington answered 3/6, 2023 at 15:7 Comment(0)
P
0

KinSlayerUY's custom attribute worked nicely for me but I had issues with ComplexTypes. They were being mapped as entities in the attribute code so couldn't then be mapped as a ComplexType.

I therefore extended the code to allow for this:

public static void OnModelCreating(DbModelBuilder modelBuilder)
    {
        foreach (Type classType in from t in Assembly.GetAssembly(typeof(DecimalPrecisionAttribute)).GetTypes()
                                   where t.IsClass && t.Namespace == "FA.f1rstval.Data"
                                   select t)
        {
            foreach (var propAttr in classType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttribute<DecimalPrecisionAttribute>() != null).Select(
                   p => new { prop = p, attr = p.GetCustomAttribute<DecimalPrecisionAttribute>(true) }))
            {

                ParameterExpression param = ParameterExpression.Parameter(classType, "c");
                Expression property = Expression.Property(param, propAttr.prop.Name);
                LambdaExpression lambdaExpression = Expression.Lambda(property, true,
                                                                         new ParameterExpression[] { param });
                DecimalPropertyConfiguration decimalConfig;
                int MethodNum;
                if (propAttr.prop.PropertyType.IsGenericType && propAttr.prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    MethodNum = 7;
                }
                else
                {
                    MethodNum = 6;
                }

                //check if complextype
                if (classType.GetCustomAttribute<ComplexTypeAttribute>() != null)
                {
                    var complexConfig = modelBuilder.GetType().GetMethod("ComplexType").MakeGenericMethod(classType).Invoke(modelBuilder, null);
                    MethodInfo methodInfo = complexConfig.GetType().GetMethods().Where(p => p.Name == "Property").ToList()[MethodNum];
                    decimalConfig = methodInfo.Invoke(complexConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;
                }
                else
                {
                    var entityConfig = modelBuilder.GetType().GetMethod("Entity").MakeGenericMethod(classType).Invoke(modelBuilder, null);
                    MethodInfo methodInfo = entityConfig.GetType().GetMethods().Where(p => p.Name == "Property").ToList()[MethodNum];
                    decimalConfig = methodInfo.Invoke(entityConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;
                }

                decimalConfig.HasPrecision(propAttr.attr.Precision, propAttr.attr.Scale);
            }
        }
    }
Pilsen answered 11/8, 2014 at 22:11 Comment(0)
U
0

@Mark007, I have changed the type selection criteria to ride of the DbSet<> properties of the DbContext. I think this is safer because there are times when you have classes in the given namespace that shouldn't be part of the model definition or they are but are not entities. Or your entities could reside in separate namespaces or separate assemblies and be pulled together into once Context.

Also, even though unlikely, I do not think it's safe to rely on ordering of method definitions, so it's better to pull them out with by Parameter list. (.GetTypeMethods() is an extension method I built to work with the new TypeInfo paradigm and can flatten class hierarchies when looking for methods).

Do note that OnModelCreating delegates to this method:

    private void OnModelCreatingSetDecimalPrecisionFromAttribute(DbModelBuilder modelBuilder)
    {
        foreach (var iSetProp in this.GetType().GetTypeProperties(true))
        {
            if (iSetProp.PropertyType.IsGenericType
                    && (iSetProp.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>) || iSetProp.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)))
            {
                var entityType = iSetProp.PropertyType.GetGenericArguments()[0];

                foreach (var propAttr in entityType
                                        .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                        .Select(p => new { prop = p, attr = p.GetCustomAttribute<DecimalPrecisionAttribute>(true) })
                                        .Where(propAttr => propAttr.attr != null))
                {
                    var entityTypeConfigMethod = modelBuilder.GetType().GetTypeInfo().DeclaredMethods.First(m => m.Name == "Entity");
                    var entityTypeConfig = entityTypeConfigMethod.MakeGenericMethod(entityType).Invoke(modelBuilder, null);

                    var param = ParameterExpression.Parameter(entityType, "c");
                    var lambdaExpression = Expression.Lambda(Expression.Property(param, propAttr.prop.Name), true, new ParameterExpression[] { param });

                    var propertyConfigMethod =
                        entityTypeConfig.GetType()
                            .GetTypeMethods(true, false)
                            .First(m =>
                            {
                                if (m.Name != "Property")
                                    return false;

                                var methodParams = m.GetParameters();

                                return methodParams.Length == 1 && methodParams[0].ParameterType == lambdaExpression.GetType();
                            }
                            );

                    var decimalConfig = propertyConfigMethod.Invoke(entityTypeConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;

                    decimalConfig.HasPrecision(propAttr.attr.Precision, propAttr.attr.Scale);
                }
            }
        }
    }



    public static IEnumerable<MethodInfo> GetTypeMethods(this Type typeToQuery, bool flattenHierarchy, bool? staticMembers)
    {
        var typeInfo = typeToQuery.GetTypeInfo();

        foreach (var iField in typeInfo.DeclaredMethods.Where(fi => staticMembers == null || fi.IsStatic == staticMembers))
            yield return iField;

        //this bit is just for StaticFields so we pass flag to flattenHierarchy and for the purpose of recursion, restrictStatic = false
        if (flattenHierarchy == true)
        {
            var baseType = typeInfo.BaseType;

            if ((baseType != null) && (baseType != typeof(object)))
            {
                foreach (var iField in baseType.GetTypeMethods(true, staticMembers))
                    yield return iField;
            }
        }
    }
Uturn answered 23/10, 2014 at 13:11 Comment(2)
I just realized I didn't deal with ComplexTypes by this approach. Will revise it later.Uturn
However, the solution proposed by Brian is simple, elegant and works. I won't make any categorical statements about performance but riding off already reflected PropertyInfo rather than hunting down yours should yield better performance on very large models (in the order of 200 and above).Uturn

© 2022 - 2024 — McMap. All rights reserved.