Estou experimentando essa abordagem de primeiro código, mas agora descubro que uma propriedade do tipo System.Decimal é mapeada para uma coluna sql do tipo decimal (18, 0).
Como defino a precisão da coluna do banco de dados?
Estou experimentando essa abordagem de primeiro código, mas agora descubro que uma propriedade do tipo System.Decimal é mapeada para uma coluna sql do tipo decimal (18, 0).
Como defino a precisão da coluna do banco de dados?
Respostas:
A resposta de Dave Van den Eynde está desatualizada. Há duas alterações importantes: a partir do EF 4.1, a classe ModelBuilder agora é DbModelBuilder e agora existe um método DecimalPropertyConfiguration.HasPrecision que possui uma assinatura de:
public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )
onde precisão é o número total de dígitos que o banco de dados armazenará, independentemente de onde o ponto decimal cai e a escala é o número de casas decimais que ele armazenará.
Portanto, não há necessidade de iterar pelas propriedades, como mostrado, mas elas podem ser chamadas a partir de
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);
. Isso foi intencional ou apenas uma vítima de digitar código on-line em vez de em um IDE?
Se você deseja definir a precisão para todos decimals
no EF6, substitua a DecimalPropertyConvention
convenção padrão usada em DbModelBuilder
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<DecimalPropertyConvention>();
modelBuilder.Conventions.Add(new DecimalPropertyConvention(38, 18));
}
O padrão DecimalPropertyConvention
no EF6 mapeia decimal
propriedades para decimal(18,2)
colunas.
Se você deseja que apenas propriedades individuais tenham uma precisão especificada, defina a precisão da propriedade da entidade em DbModelBuilder
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>().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<MyEntity>
{
internal MyEntityConfiguration()
{
this.Property(e => e.Value).HasPrecision(38, 18);
}
}
Eu me diverti bastante 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 assim
[DecimalPrecision(20,10)]
public Nullable<decimal> DeliveryPrice { get; set; }
e a mágica acontece na criação do modelo com alguma reflexão
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);
}
}
}
a primeira parte é obter todas as classes no modelo (meu atributo personalizado é definido nessa montagem, então eu usei isso para obter a montagem com o modelo)
o segundo foreach obtém todas as propriedades dessa classe com o atributo customizado e o próprio atributo para que eu possa obter os dados de precisão e escala
depois disso eu tenho que ligar
modelBuilder.Entity<MODEL_CLASS>().Property(c=> c.PROPERTY_NAME).HasPrecision(PRECISION,SCALE);
então eu chamo o modelBuilder.Entity () por reflexão e armazeno-o na variável entityConfig, então construo a expressão lambda "c => c.PROPERTY_NAME"
Depois disso, se o decimal for anulável, chamo o
Property(Expression<Func<TStructuralType, decimal?>> propertyExpression)
método (eu chamo isso de posição na matriz, não é o ideal, eu sei, qualquer ajuda será muito apreciada)
e se não for anulável eu chamo o
Property(Expression<Func<TStructuralType, decimal>> propertyExpression)
método.
Tendo o DecimalPropertyConfiguration, chamo o método HasPrecision.
MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });
para obter a sobrecarga correta. parece funcionar até agora.
Usando o DecimalPrecisonAttribute
KinSlayerUY, no EF6, você pode criar uma convenção que manipulará propriedades individuais que possuem o atributo (em vez de definir o DecimalPropertyConvention
mesmo 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<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);
}
}
Então no seu DbContext
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
}
Precision
, recomendo definir o limite superior para 28 (portanto, > 28
em sua condição). De acordo com a documentação do MSDN, System.Decimal
pode representar apenas um máximo de 28 a 29 dígitos de precisão ( msdn.microsoft.com/en-us/library/364x0z75.aspx ). Além disso, o atributo declara Scale
como byte
, o que significa que sua pré attribute.Scale < 0
- condição é desnecessária.
System.Decimal
não. Portanto, não faz sentido definir a pré-condição do limite superior para algo maior que 28; System.Decimal
aparentemente não pode representar números tão grandes. Além disso, esteja ciente de que esse atributo é útil para outros provedores de dados que não o SQL Server. Por exemplo, o numeric
tipo do PostgreSQL suporta até 131072 dígitos de precisão.
decimal(38,9)
coluna manterá feliz o botão, System.Decimal.MaxValue
mas uma decimal(28,9)
coluna não. Não há nenhuma razão para limitar a precisão com apenas 28.
Aparentemente, você pode substituir o método DbContext.OnModelCreating () e configurar a precisão assim:
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;
}
Mas esse é um código bastante tedioso quando você precisa fazer isso com todas as suas propriedades relacionadas a preços, então eu vim com isso:
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);
}
É uma boa prática chamar o método base quando você substitui um método, mesmo que a implementação básica não faça nada.
Atualização: este artigo também foi muito útil.
base.OnModelCreating(modelBuilder);
é necessário. Dos metadados do DbContext no 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.
O Entity Framework Ver 6 (Alpha, rc1) possui algo chamado Convenções personalizadas . Para definir a precisão decimal:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<decimal>().Configure(config => config.HasPrecision(18, 4));
}
Referência:
[Column(TypeName = "decimal(18,2)")]
isso funcionará com as primeiras migrações do código EF Core, conforme descrito aqui .
The store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
essa linha de código pode ser uma maneira mais simples de realizar o mesmo:
public class ProductConfiguration : EntityTypeConfiguration<Product>
{
public ProductConfiguration()
{
this.Property(m => m.Price).HasPrecision(10, 2);
}
}
- PARA EF CORE - com o uso de System.ComponentModel.DataAnnotations;
use [Column
( TypeName
= "decimal
( precisão , escala )")]
Precisão = número total de caracteres usados
Escala = Número total após o ponto. (fácil de se confundir)
Exemplo :
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; }
}
Mais detalhes aqui: https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types
No 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);
});
Você sempre pode dizer ao EF para fazer isso com convenções na classe Context na função OnModelCreating da seguinte maneira:
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);
}
Isso se aplica apenas ao Code First EF fyi e se aplica a todos os tipos decimais mapeados para o banco de dados.
Remove<DecimalPropertyConvention>();
venha antes do Add(new DecimalPropertyConvention(18, 4));
. Eu acho estranho que não seja substituído automaticamente.
Usando
System.ComponentModel.DataAnnotations;
Você pode simplesmente colocar esse atributo no seu modelo:
[DataType("decimal(18,5)")]
Você pode encontrar mais informações sobre o MSDN - faceta do modelo de dados da entidade. http://msdn.microsoft.com/en-us/library/ee382834.aspx Totalmente recomendado.
Real para EntityFrameworkCore 3.1.3:
alguma solução no 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)");
O atributo personalizado do KinSlayerUY funcionou muito bem para mim, mas tive problemas com o ComplexTypes. Eles estavam sendo mapeados como entidades no código do atributo e, portanto, não podiam ser mapeados como um ComplexType.
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<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, alterei os critérios de seleção de tipo para rodar nas propriedades DbSet <> do DbContext. Eu acho que isso é mais seguro, porque há momentos em que você tem classes no namespace fornecido que não devem fazer parte da definição do modelo ou são, mas não são entidades. Ou suas entidades podem residir em namespaces ou assemblies separados e serem reunidos em um único contexto.
Além disso, apesar de improvável, não acho seguro confiar na ordenação de definições de métodos, por isso é melhor retirá-las pela lista de Parâmetros. (.GetTypeMethods () é um método de extensão que eu criei para trabalhar com o novo paradigma TypeInfo e pode nivelar hierarquias de classes ao procurar métodos).
Observe que OnModelCreating delega para esse 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;
}
}
}
[Column(TypeName = "decimal(18,4)")]
atributo para suas propriedades decimais