Десятичная точность и масштаб в EF Code First
Я экспериментирую с этим первым подходом к коду, но теперь выясняю, что свойство типа System.Decimal отображается на столбец sql типа decimal(18, 0).
Как мне установить точность столбца базы данных?
19 ответов
Ответ от Дейва Ван ден Эйнде сейчас устарел. Есть 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);
}
}
Если вы хотите установить точность для всех 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);
}
}
Я хорошо провел время, создавая собственный атрибут для этого:
[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"
После этого, если десятичная дробь обнуляется, я называю
Property(Expression<Func<TStructuralType, decimal?>> propertyExpression)
метод (я называю это положением в массиве, это не идеально, я знаю, любая помощь будет высоко ценится)
и если это не обнуляемо, я называю
Property(Expression<Func<TStructuralType, decimal>> propertyExpression)
метод.
Имея DecimalPropertyConfiguration, я вызываю метод HasPrecision.
С использованием 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());
}
По-видимому, вы можете переопределить метод 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);
}
Рекомендуется вызывать базовый метод при переопределении метода, даже если базовая реализация ничего не делает.
Обновление: эта статья также была очень полезной.
[Column(TypeName = "decimal(18,2)")]
это будет работать с первыми миграциями кода, как описано здесь.
В Entity Framework версии 6 (Alpha, rc1) есть нечто, называемое пользовательскими соглашениями. Чтобы установить десятичную точность:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<decimal>().Configure(config => config.HasPrecision(18, 4));
}
Ссылка:
!!! для EF CORE (с System.ComponentModel.DataAnnotations)
использование [Column(TypeName = "decimal(precision, scale)")]
Пример:
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
Начиная с .NET EF Core 6, вы можете использовать атрибут Precision.
[Precision(18, 2)]
public decimal Price { get; set; }
убедитесь, что вам нужно установить EF Core 6 и выполните следующие действия
using
линия
using Microsoft.EntityFrameworkCore;
Эта строка кода может быть более простым способом сделать то же самое:
public class ProductConfiguration : EntityTypeConfiguration<Product>
{
public ProductConfiguration()
{
this.Property(m => m.Price).HasPrecision(10, 2);
}
}
Вы всегда можете сказать EF сделать это с соглашениями в классе Context в функции OnModelCreating следующим образом:
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);
}
Это относится только к FY Code First EF и относится ко всем десятичным типам, отображаемым в БД.
С помощью
System.ComponentModel.DataAnnotations;
Вы можете просто вставить этот атрибут в вашу модель:
[DataType("decimal(18,5)")]
В 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);
});
Это то, что я искал, работает для обычного проекта 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));
}
Актуально для 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)");
Вы можете найти больше информации о MSDN - аспекте Entity Data Model. http://msdn.microsoft.com/en-us/library/ee382834.aspx Полный рекомендуется.
вы можете просто установить десятичную точность и масштаб из класса Model Design, используя атрибут столбца или атрибут точности , как показано ниже:
// 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;
Пользовательский атрибут KinSlayerUY работал хорошо для меня, но у меня были проблемы с ComplexTypes. Они отображались как сущности в коде атрибута, поэтому их нельзя было сопоставить как ComplexType.
Поэтому я расширил код, чтобы учесть это:
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);
}
}
}
@Mark007, я изменил критерии выбора типа, чтобы использовать свойства DbSet<> DbContext. Я думаю, что это безопаснее, потому что бывают случаи, когда в данном пространстве имен есть классы, которые не должны быть частью определения модели или они не являются сущностями. Или ваши сущности могут находиться в отдельных пространствах имен или отдельных сборках и объединяться в единый контекст.
Кроме того, хотя это маловероятно, я не думаю, что можно полагаться на порядок определений методов, поэтому лучше их извлекать с помощью списка параметров. (.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;
}
}
}