Keywords: Entity Framework | Code First | Decimal Precision
Abstract: This article explores how to configure the precision and scale of decimal database columns in Entity Framework Code First. It covers the DbModelBuilder and DecimalPropertyConfiguration.HasPrecision method introduced in EF 4.1 and later, with detailed code examples. Advanced techniques like global configuration and custom attributes are also discussed to help developers choose the right strategy for their needs.
Default Mapping of Decimal Types in the Database
In Entity Framework Code First, when defining a property of type System.Decimal, EF maps it by default to a decimal(18, 0) column in SQL databases. This means the column has a total precision of 18 digits with 0 decimal places. Such default settings may not meet practical business requirements, especially in scenarios requiring high precision, such as financial applications or scientific computations.
Configuring Precision Using the HasPrecision Method
Starting from Entity Framework 4.1, the DbModelBuilder class and the DecimalPropertyConfiguration.HasPrecision method were introduced, allowing developers to precisely control the precision and scale of decimal columns. The method signature is as follows:
public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale
)
Here, the precision parameter indicates the total number of digits stored in the database column (including both integer and fractional parts), while scale specifies the number of decimal places. For example, configuring HasPrecision(12, 10) results in a database column that stores up to 12 digits, with 10 of them as fractional parts.
Configuring Individual Properties in DbContext
By overriding the OnModelCreating method in a custom DbContext class, you can configure precision for specific entity properties. Here is a complete example:
public class EFDbContext : DbContext
{
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().Property(p => p.Price).HasPrecision(12, 10);
base.OnModelCreating(modelBuilder);
}
}
In this example, the Price property of the Product entity is configured to be stored as decimal(12, 10) in the database. This approach is suitable for scenarios requiring fine-grained control over specific properties.
Global Configuration for Decimal Properties
If you want to set a uniform precision for all decimal properties, you can remove EF's default convention and add a custom one. In EF6, the default DecimalPropertyConvention maps decimal properties to decimal(18, 2). You can globally modify this behavior with the following code:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<DecimalPropertyConvention>();
modelBuilder.Conventions.Add(new DecimalPropertyConvention(38, 18));
}
This ensures that all decimal properties not explicitly configured will use decimal(38, 18) precision. This method simplifies configuration, especially in large projects.
Configuration Using EntityTypeConfiguration
Another approach is to use the EntityTypeConfiguration<T> class to organize entity configurations, improving code maintainability. Example code is as follows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ProductConfiguration());
}
internal class ProductConfiguration : EntityTypeConfiguration<Product>
{
internal ProductConfiguration()
{
this.Property(p => p.Price).HasPrecision(38, 18);
}
}
This method encapsulates configuration logic in separate classes, keeping the DbContext clean and supporting more complex configuration needs.
Custom Attributes and Reflection-Based Configuration
For highly flexible scenarios, you can define custom attributes and apply configurations automatically during model creation using reflection. First, define a DecimalPrecisionAttribute:
[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; }
}
Then, apply this attribute to entity properties:
[DecimalPrecision(20, 10)]
public Nullable<decimal> DeliveryPrice { get; set; }
Finally, read the attributes and apply configurations in the OnModelCreating method via 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);
}
}
}
Although complex, this method offers great flexibility by allowing configuration to be driven by attribute annotations, reducing manual configuration efforts.
Summary and Best Practices
When configuring decimal precision in Entity Framework Code First, choose the method that best fits your project's needs. For simple cases, using the HasPrecision method directly in OnModelCreating is the most straightforward. For projects requiring uniform settings, global conventions or EntityTypeConfiguration are more appropriate. Custom attributes with reflection suit complex systems needing dynamic configuration. Regardless of the method, always test the generated DDL statements to ensure precision configurations are correctly applied.