Я експериментую з цим першим кодовим підходом, але зараз з’ясовую, що властивість типу System.Decimal потрапляє у таблицю sql типу десяткових (18, 0).
Як встановити точність стовпця бази даних?
Я експериментую з цим першим кодовим підходом, але зараз з’ясовую, що властивість типу System.Decimal потрапляє у таблицю sql типу десяткових (18, 0).
Як встановити точність стовпця бази даних?
Відповіді:
Відповідь Дейва Ван ден Ейнде зараз застаріла. Є 2 важливі зміни: від EF 4.1 і далі клас ModelBuilder тепер є DbModelBuilder, і тепер існує метод DecimalPropertyConfiguration.HasPrecision, який має підпис:
public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )
де точність - загальна кількість цифр, яку зберігатиме db, незалежно від того, де падає десяткова крапка, а масштаб - кількість десяткових знаків, які вона буде зберігати.
Тому немає необхідності переглядати властивості, як показано, але можна просто викликати
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 () за допомогою відображення і зберігаю його у змінній entitConfig, тоді я будую лямбдаський вираз "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
від 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 Ver 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);
}
Це стосується лише Code First EF fyi і стосується всіх десяткових типів, відображених на db.
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)")]
атрибут для ваших десяткових властивостей