Precisão e escala decimais no EF Code First

Eu estou experimentando com essa abordagem de código primeiro, mas eu estou descobrir agora que uma propriedade do tipo System.Decimal é mapeada para uma coluna sql do tipo decimal (18, 0).

Como faço para definir a precisão da coluna do database?

A resposta de Dave Van den Eynde está desatualizada. Existem 2 alterações importantes, a partir do EF 4.1 em diante, a class ModelBuilder é agora DbModelBuilder e agora existe um DecimalPropertyConfiguration.HasPrecision Method que possui uma assinatura de:

public DecimalPropertyConfiguration HasPrecision( byte precision, byte scale ) 

onde precisão é o número total de dígitos que o database armazenará, independentemente de onde o ponto decimal caia e escala é o número de casas decimais que serão armazenadas.

Portanto, não há necessidade de iterar através de propriedades como mostrado, mas o pode apenas ser chamado de

 public class EFDbContext : DbContext { protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder) { modelBuilder.Entity().Property(object => object.property).HasPrecision(12, 10); base.OnModelCreating(modelBuilder); } } 

Se você quiser definir a precisão para todos os decimals no EF6, você pode replace a convenção DecimalPropertyConvention padrão usada no DbModelBuilder :

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

O padrão DecimalPropertyConvention no EF6 mapeia propriedades decimal(18,2) colunas decimal(18,2) .

Se você quiser que apenas propriedades individuais tenham uma precisão especificada, você pode definir a precisão para a propriedade da entidade no DbModelBuilder :

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

Ou adicione um EntityTypeConfiguration<> para a entidade que especifica a precisão:

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new MyEntityConfiguration()); } internal class MyEntityConfiguration : EntityTypeConfiguration { internal MyEntityConfiguration() { this.Property(e => e.Value).HasPrecision(38, 18); } } 

Eu me diverti criando um Atributo Personalizado para isso:

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

usando-o assim

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

e a mágica acontece na criação de modelos com alguma reflection

 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() != null).Select( p => new { prop = p, attr = p.GetCustomAttribute(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); } } } 

a primeira parte é obter todas as classs no modelo (meu atributo personalizado é definido nesse assembly, então usei isso para obter o assembly com o modelo)

o segundo foreach obtém todas as propriedades nessa class com o atributo customizado e o próprio atributo para que eu possa obter os dados de precisão e de escala

depois disso eu tenho que ligar

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

então eu chamo o modelBuilder.Entity () por reflection e armazeno na variável entityConfig então eu construo a expressão lambda “c => c.PROPERTY_NAME”

Depois disso, se o decimal é anulável eu chamo o

 Property(Expression> propertyExpression) 

método (eu chamo isso pela posição na matriz, não é ideal eu sei, qualquer ajuda será muito apreciada)

e se não é anulável eu chamo o

 Property(Expression> propertyExpression) 

método.

Tendo o DecimalPropertyConfiguration eu chamo o método HasPrecision.

Aparentemente, você pode replace o método DbContext.OnModelCreating () e configurar a precisão da seguinte forma:

 protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) { modelBuilder.Entity().Property(product => product.Price).Precision = 10; modelBuilder.Entity().Property(product => product.Price).Scale = 2; } 

Mas isso é um código muito tedioso quando você tem que fazer isso com todas as suas propriedades relacionadas a preço, então eu inventei isto:

  protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) { var properties = new[] { modelBuilder.Entity().Property(product => product.Price), modelBuilder.Entity().Property(order => order.OrderTotal), modelBuilder.Entity().Property(detail => detail.Total), modelBuilder.Entity 

É uma boa prática chamar o método base quando você replace um método, mesmo que a implementação base não faça nada.

Atualização: Este artigo também foi muito útil.

Usando o DecimalPrecisonAttribute do KinSlayerUY, no EF6 você pode criar uma convenção que manipulará as propriedades individuais que têm o atributo (em oposição à configuração do DecimalPropertyConvention como nesta resposta, que afetará todas as propriedades decimais).

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

Então, no seu DbContext :

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

O Entity Framework Ver 6 (Alpha, rc1) tem algo chamado Convenções Personalizadas . Para definir a precisão decimal:

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

Referência:

essa linha de código poderia ser uma maneira mais simples de realizar o mesmo:

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

No EF6

 modelBuilder.Properties() .Where(x => x.GetCustomAttributes(false).OfType().Any()) .Configure(c => { var attr = (DecimalPrecisionAttribute)c.ClrPropertyInfo.GetCustomAttributes(typeof (DecimalPrecisionAttribute), true).FirstOrDefault(); c.HasPrecision(attr.Precision, attr.Scale); }); 

Você sempre pode dizer ao EF para fazer isso com convenções na class Context na function OnModelCreating da seguinte maneira:

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { // < ... other configurations ...> // modelBuilder.Conventions.Remove(); // modelBuilder.Conventions.Remove(); // modelBuilder.Conventions.Remove(); // Configure Decimal to always have a precision of 18 and a scale of 4 modelBuilder.Conventions.Remove(); modelBuilder.Conventions.Add(new DecimalPropertyConvention(18, 4)); base.OnModelCreating(modelBuilder); } 

Isso se aplica somente ao Code First EF fyi e se aplica a todos os tipos decimais mapeados para o db.

Usando

 System.ComponentModel.DataAnnotations; 

Você pode simplesmente colocar esse atributo em seu modelo:

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

Você pode encontrar mais informações no MSDN – faceta do Entity Data Model. http://msdn.microsoft.com/pt-br/library/ee382834.aspx Recomendada.

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

isso funcionará nas primeiras migrações do código, conforme descrito aqui .

O atributo personalizado do KinSlayerUY funcionou muito bem para mim, mas eu tive problemas com o ComplexTypes. Eles estavam sendo mapeados como entidades no código do atributo, portanto, não poderiam ser mapeados como um ComplexType.

Eu, portanto, estendi o código para permitir isso:

 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() != null).Select( p => new { prop = p, attr = p.GetCustomAttribute(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() != 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, mudei os critérios de seleção de tipo para montar as propriedades DbSet <> do DbContext. Acho que isso é mais seguro porque há momentos em que você tem classes no namespace determinado que não deveriam fazer parte da definição do modelo ou são, mas não são entidades. Ou suas entidades podem residir em namespaces separados ou em assemblies separados e ser reunidos em uma vez em Context.

Além disso, mesmo que improvável, não acho que seja seguro confiar na ordenação das definições de método, por isso é melhor extraí-las com a lista de parameters. (.GetTypeMethods () é um método de extensão que construí para trabalhar com o novo paradigma TypeInfo e pode nivelar hierarquias de classs ao procurar por methods).

Observe que OnModelCreating delegates para 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(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 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; } } }