EF Code First中的十进制精度和比例

287

我正在尝试使用代码优先的方法,但是现在发现一个类型为System.Decimal的属性被映射到了一个sql列的decimal(18, 0)类型中。

我该如何设置数据库列的精度?


23
一种方法是在你的小数属性上使用[Column(TypeName = "decimal(18,4)")]属性。 - S.Serpooshan
1
[Column(TypeName = "decimal(18,4)")] 很棒!!! - Brian Rice
19个回答

4
您可以通过上下文类中的OnModelCreating函数中的约定来告诉EF始终执行此操作,如下所示:
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);
}

此内容仅适用于 Code First EF,适用于映射到数据库的所有十进制类型。


1
直到 Remove<DecimalPropertyConvention>();Add(new DecimalPropertyConvention(18, 4)); 之前才开始工作。我认为奇怪的是它不会自动覆盖。 - Mike de Klerk

3

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

1
这个答案似乎是对另一个定义属性的答案的升级,你应该将其编辑到那个答案中。 - Rhys Bevilaqua

3

使用

System.ComponentModel.DataAnnotations;

你可以直接将该属性放入你的模型中:
[DataType("decimal(18,5)")]

1
这是可读性和简单性最简单的实现。在我看来。 - ransems
14
根据https://msdn.microsoft.com/en-us/library/jj591583(v=vs.113).aspx,这个答案事实上是错误的。"不要将Column的TypeName属性与DataType DataAnnotation混淆。DataType是用于UI的注释,在Code First中被忽略。" - speckledcarp
2
@ransems 我也是这么认为的,直到我刚刚测试了一下,正如上面所说的那样,这对于CodeFirst不起作用,也不会迁移到数据库中。 - RoLYroLLs

2

这正是我要找的,适用于普通的MVC项目(非.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);

    }
}

包管理器控制台

add-migration changeDecimalPrecision

生成的迁移

    public override void Up()
    {
        AlterColumn("dbo.YOUR_CLASS_NAME", "YOUR_DECIAML_PROP", c => c.Decimal(nullable: false, precision: 18, scale: 6));
    }

2
已经有一个被接受的答案解释了这个问题(还有更多答案)。如果没有新信息,请不要发布答案。 - Gert Arnold

1

这很好,但这与 Code-First 有什么关系呢? - Dave Van den Eynde
这很有用,但我仍然无法为Decimal指定[精度]属性。因此,我使用了@KinSlayerUY提供的解决方案。 - Colin

1

针对EntityFrameworkCore 3.1.3实际情况:

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

1

你可以通过使用ColumnAttribute或者PrecisionAttribute在模型设计类中简单地设置十进制精度和比例,如下所示:

  // 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;

0
KinSlayerUY的自定义属性对我很有用,但是在处理复杂类型时出现了问题。它们被映射为属性代码中的实体,因此不能再作为复杂类型进行映射。
因此,我扩展了代码以允许这种情况:
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);
            }
        }
    }

0

@Mark007,我已经将类型选择标准更改为DbContext的DbSet<>属性。我认为这样更安全,因为有时您在给定命名空间中有不应该是模型定义的类,或者它们是实体但不是。或者您的实体可以驻留在单独的命名空间或单独的程序集中,并被合并到一个上下文中。

此外,尽管不太可能,但我认为依赖方法定义的排序不安全,因此最好通过参数列表将它们提取出来。(.GetTypeMethods()是我构建的扩展方法,用于与新的TypeInfo范例一起工作,并且可以在查找方法时展平类层次结构)。

请注意,OnModelCreating委托给了这个方法:

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

我刚意识到我没有用这种方法处理复杂类型。稍后会进行修订。 - Eniola
然而,Brian 提出的解决方案简单、优雅且有效。我不会对性能做任何绝对的陈述,但是利用已经反射的 PropertyInfo 而不是寻找你自己的 PropertyInfo 应该会在非常大的模型(200及以上)上产生更好的性能。 - Eniola

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接