Я экспериментирую с этим первым подходом к коду, но теперь выясняю, что свойство типа System.Decimal отображается на столбец sql типа decimal (18, 0).
Как мне установить точность столбца базы данных?
Я экспериментирую с этим первым подходом к коду, но теперь выясняю, что свойство типа System.Decimal отображается на столбец sql типа decimal (18, 0).
Как мне установить точность столбца базы данных?
Ответы:
Ответ от Дейва Ван ден Эйнде сейчас устарел. Есть 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);
}
}
System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder
base.OnModelCreating(modelBuilder);
. Было ли это намеренно или просто жертвой ввода кода онлайн вместо IDE?
Если вы хотите установить точность для всех 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.
MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });
чтобы получить правильную перегрузку. кажется, работает до сих пор.
Используя команду DecimalPrecisonAttribute
from 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());
}
Precision
, то я рекомендую установить верхнюю границу на 28 (то есть > 28
в вашем состоянии). Согласно документации MSDN, System.Decimal
точность может составлять не более 28-29 цифр ( msdn.microsoft.com/en-us/library/364x0z75.aspx ). Кроме того, атрибут объявляется Scale
как byte
, что означает, что ваше предварительное условие attribute.Scale < 0
не требуется.
System.Decimal
нет. Поэтому нет смысла устанавливать верхнее предусловие на значение больше 28; System.Decimal
по-видимому, не может представлять такие большие числа. Также помните, что этот атрибут полезен для поставщиков данных, отличных от SQL Server. Например, numeric
тип PostgreSQL поддерживает до 131072 цифр точности.
decimal(38,9)
столбца будет счастливым, System.Decimal.MaxValue
но decimal(28,9)
столбец - нет. Нет оснований ограничивать точность только до 28.
По-видимому, вы можете переопределить метод 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);
}
Рекомендуется вызывать базовый метод при переопределении метода, даже если базовая реализация ничего не делает.
Обновление: эта статья также была очень полезной.
base.OnModelCreating(modelBuilder);
необходимо. Из метаданных DbContext в 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.
В Entity Framework версии 6 (Alpha, rc1) есть нечто, называемое пользовательскими соглашениями . Чтобы установить десятичную точность:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<decimal>().Configure(config => config.HasPrecision(18, 4));
}
Ссылка:
[Column(TypeName = "decimal(18,2)")]
это будет работать с первыми миграциями кода EF Core, как описано здесь .
The store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
эта строка кода может быть более простым способом сделать то же самое:
public class ProductConfiguration : EntityTypeConfiguration<Product>
{
public ProductConfiguration()
{
this.Property(m => m.Price).HasPrecision(10, 2);
}
}
- ДЛЯ 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
В 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);
});
Вы всегда можете сказать 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 и относится ко всем десятичным типам, отображаемым в БД.
Remove<DecimalPropertyConvention>();
наступит раньше Add(new DecimalPropertyConvention(18, 4));
. Я думаю, что это странно, что не просто автоматически переопределяется.
С помощью
System.ComponentModel.DataAnnotations;
Вы можете просто вставить этот атрибут в вашу модель:
[DataType("decimal(18,5)")]
Вы можете найти больше информации о MSDN - аспекте Entity Data Model. http://msdn.microsoft.com/en-us/library/ee382834.aspx Полный рекомендуется.
Актуально для 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)");
Пользовательский атрибут 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;
}
}
}
[Column(TypeName = "decimal(18,4)")]
атрибута для ваших десятичных свойств