Десятичная точность и масштаб в EF Code First - PullRequest
205 голосов
/ 17 августа 2010

Я экспериментирую с этим первым подходом к коду, но теперь выясняю, что свойство типа System.Decimal отображается на столбец sql типа decimal (18, 0).

Как настроить точность столбца базы данных?

Ответы [ 15 ]

234 голосов
/ 14 октября 2011

Ответ от Дейва Ван ден Эйнда сейчас устарел. Есть 2 важных изменения, начиная с EF 4.1 и далее, класс ModelBuilder теперь DbModelBuilder , и теперь существует метод DecimalPropertyConfiguration.HasPrecision, имеющий подпись:

public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )

где точность - общее количество цифр, которые будет хранить БД, независимо от того, где находится десятичная точка, а шкала - это количество десятичных знаков, которые она будет хранить.

Поэтому нет необходимости перебирать свойства, как показано, но их можно просто вызвать из

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);
   }
}
78 голосов
/ 03 апреля 2014

Если вы хотите установить точность для всех decimals в EF6, вы можете заменить соглашение DecimalPropertyConvention по умолчанию, используемое в DbModelBuilder:

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

Значение по умолчанию DecimalPropertyConvention в EF6 сопоставляет decimal свойства с decimal(18,2) столбцами.

Если вы хотите, чтобы только отдельные свойства имели указанную точность, вы можете установить точность для свойства объекта в DbModelBuilder:

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

Или добавьте EntityTypeConfiguration<> для объекта, который определяет точность:

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);
    }
}
70 голосов
/ 13 марта 2013

Я хорошо провел время, создавая собственный атрибут для этого:

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

}

используя его вот так

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

и волшебство происходит при создании модели с некоторым отражением

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

первая часть - получить все классы в модели (мой пользовательский атрибут определен в этой сборке, поэтому я использовал ее для получения сборки с моделью)

второй foreach получает все свойствав этом классе с пользовательским атрибутом и самим атрибутом, чтобы я мог получить данные о точности и масштабе

, после этого мне нужно вызвать

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

, поэтому я вызываю modelBuilder.Entity () путем отражения и сохраните его в переменной entityConfig, затем я создам лямбда-выражение "c => c.PROPERTY_NAME"

После этого, если десятичная дробь имеет значение null, я вызываю

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

метод (я называю это положением в массиве, он не идеален, я знаю, любая помощь будет высоко ценится)

и, если он не может быть равен null, я вызываю метод

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

.

ИмеяDecimalPropertyConfiguration, я вызываю метод HasPrecision.

47 голосов
/ 17 августа 2010

Видимо, вы можете переопределить метод DbContext.OnModelCreating () и настроить точность следующим образом:

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

Но это довольно утомительный код, когда вам нужно сделать это со всеми вашими ценовыми свойствамиИтак, я придумал следующее:

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

Хорошей практикой является вызов базового метода при переопределении метода, даже если базовая реализация ничего не делает.

Обновление: Эта статья также была очень полезна.

46 голосов
/ 03 апреля 2014

Используя DecimalPrecisonAttribute из KinSlayerUY, в EF6 вы можете создать соглашение, которое будет обрабатывать отдельные свойства, имеющие атрибут (в отличие от установки DecimalPropertyConvention, как в этот ответ , который будет влиять на вседесятичные свойства).

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

Тогда в вашем DbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
}
30 голосов
/ 18 сентября 2013

Entity Framework версии 6 (Alpha, rc1) имеет то, что называется Пользовательские соглашения . Чтобы установить десятичную точность:

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

Справка:

14 голосов
/ 13 октября 2011

эта строка кода может быть проще для того же:

 public class ProductConfiguration : EntityTypeConfiguration<Product>
    {
        public ProductConfiguration()
        {
            this.Property(m => m.Price).HasPrecision(10, 2);
        }
    }
12 голосов
/ 18 июля 2018
[Column(TypeName = "decimal(18,2)")]

это будет работать с первыми миграциями кода, как описано здесь .

5 голосов
/ 13 сентября 2018

- ДЛЯ EF CORE - с с использованием System.ComponentModel.DataAnnotations;

использование [Column (TypeName = "decimal ( точность, шкала ) ")]

Точность = Общее количество используемых символов

Масштаб = Общее число после точки. (Легко запутаться)

Пример :

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

Подробнее здесь: https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types

3 голосов
/ 20 февраля 2014

В 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);
    });
...