type the specified microsoft has framework for entityframeworkcore dataannotations data column attribute c# .net entity-framework ef-code-first decimal

c# - the - precision sql decimal



Precisión y escala decimales en el Código EF Primero (15)

!!! para EF CORE (con System.ComponentModel.DataAnnotations)

use [Column(TypeName = "decimal(precision, scale)")]

Ejemplo :

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

Más detalles aquí: https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types

Estoy experimentando con este enfoque de código primero, pero ahora descubro que una propiedad de tipo System.Decimal se asigna a una columna sql de tipo decimal (18, 0).

¿Cómo configuro la precisión de la columna de la base de datos?


@ Mark007, he cambiado los criterios de selección de tipo para utilizar las propiedades DbSet <> del DbContext. Creo que esto es más seguro porque hay veces en que tienes clases en el espacio de nombres dado que no deberían formar parte de la definición del modelo o son entidades que no son. O sus entidades podrían residir en espacios de nombres separados o ensamblajes separados y agruparse en un contexto único.

Además, aunque es poco probable, no creo que sea seguro confiar en el orden de las definiciones de los métodos, por lo que es mejor sacarlos con la lista de parámetros. (.GetTypeMethods () es un método de extensión que construí para trabajar con el nuevo paradigma TypeInfo y que puede aplanar las jerarquías de clase al buscar métodos).

Tenga en cuenta que OnModelCreating delegados a este método:

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


Aparentemente, puede anular el método DbContext.OnModelCreating () y configurar la precisión de esta manera:

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

Pero este es un código bastante tedioso cuando tienes que hacerlo con todas tus propiedades relacionadas con el precio, por lo que se me ocurrió esto:

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

Es una buena práctica que llame al método base cuando invalida un método, aunque la implementación base no haga nada.

Actualización: Este artículo también fue muy útil.


El atributo personalizado de KinSlayerUY funcionó muy bien para mí, pero tuve problemas con ComplexTypes. Se estaban asignando como entidades en el código de atributo, por lo que no podían asignarse como ComplexType.

Por lo tanto, extendí el código para permitir esto:

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


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



La respuesta de Dave Van den Eynde está ahora desactualizada. Hay 2 cambios importantes, desde EF 4.1 en adelante, la clase ModelBuilder ahora es DbModelBuilder y ahora hay un DecimalPropertyConfiguration.HasPrecision Method que tiene una firma de:

public DecimalPropertyConfiguration HasPrecision( byte precision, byte scale )

donde precisión es el número total de dígitos que almacenará la db, independientemente de dónde caiga el punto decimal y escala sea el número de lugares decimales que almacenará.

Por lo tanto, no hay necesidad de recorrer las propiedades como se muestra, pero se puede llamar desde

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


Me divertí mucho creando un atributo personalizado para esto:

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

usandolo asi

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

Y la magia pasa en la creación del modelo con algo de reflexión.

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

la primera parte es obtener todas las clases en el modelo (mi atributo personalizado está definido en ese ensamblaje, así que usé eso para obtener el ensamblaje con el modelo)

el segundo foreach obtiene todas las propiedades de esa clase con el atributo personalizado y el atributo en sí mismo para que pueda obtener los datos de precisión y escala.

después de eso tengo que llamar

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

así que llamo a modelBuilder.Entity () por reflexión y lo guardo en la variable entityConfig, luego compilo la expresión lambda "c => c.PROPERTY_NAME"

Después de eso, si el decimal es anulable, llamo al

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

método (lo llamo por la posición en la matriz, no es ideal, sé que cualquier ayuda será muy apreciada)

y si no es anulable llamo al

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

método.

Teniendo el DecimalPropertyConfiguration llamo el método HasPrecision.



Si desea establecer la precisión para todos los decimals en EF6, puede reemplazar la convención predeterminada DecimalPropertyConvention utilizada en DbModelBuilder :

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

El DecimalPropertyConvention predeterminado en EF6 asigna propiedades decimal(18,2) columnas decimal(18,2) .

Si solo desea que las propiedades individuales tengan una precisión específica, puede establecer la precisión para la propiedad de la entidad en DbModelBuilder :

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

O, agregue una EntityTypeConfiguration<> para la entidad que especifica la precisión:

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


Siempre puede decirle a EF que haga esto con las convenciones en la clase de contexto en la función OnModelCreating de la siguiente manera:

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

Esto solo se aplica a Code First EF fyi y se aplica a todos los tipos de decimales asignados a la db.


Usando el atributo DecimalPrecisonAttribute de KinSlayerUY, en EF6 puede crear una convención que manejará las propiedades individuales que tienen el atributo (en lugar de establecer la convención DecimalPropertyConvention como en esta respuesta que afectará todas las propiedades decimales).

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

Luego en tu DbContext :

protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention()); }


Utilizando

System.ComponentModel.DataAnnotations;

Simplemente puede poner ese atributo en su modelo:

[DataType("decimal(18,5)")]


esta línea de código podría ser una forma más sencilla de lograr lo mismo:

public class ProductConfiguration : EntityTypeConfiguration<Product> { public ProductConfiguration() { this.Property(m => m.Price).HasPrecision(10, 2); } }


[Column(TypeName = "decimal(18,2)")]

esto funcionará con las primeras migraciones de código como se describe here .