Sto sperimentando questo approccio basato sul codice, ma ora scopro che una proprietà di tipo System.Decimal viene mappata su una colonna sql di tipo decimale (18, 0).
Come posso impostare la precisione della colonna del database?
Sto sperimentando questo approccio basato sul codice, ma ora scopro che una proprietà di tipo System.Decimal viene mappata su una colonna sql di tipo decimale (18, 0).
Come posso impostare la precisione della colonna del database?
Risposte:
La risposta di Dave Van den Eynde non è più aggiornata. Ci sono 2 importanti cambiamenti, da EF 4.1 in poi la classe ModelBuilder è ora DbModelBuilder e ora esiste un metodo DecimalPropertyConfiguration.HasPrecision che ha una firma di:
public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )
dove precisione è il numero totale di cifre che il db memorizzerà, indipendentemente da dove cade il punto decimale e la scala è il numero di posizioni decimali che memorizzerà.
Pertanto non è necessario scorrere le proprietà come mostrato, ma è possibile richiamare semplicemente
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);
. Era intenzionale o era solo una vittima della digitazione del codice online anziché in un IDE?
Se vuoi impostare la precisione per tutti decimals
in EF6, è possibile sostituire la DecimalPropertyConvention
convenzione predefinita utilizzata in DbModelBuilder
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<DecimalPropertyConvention>();
modelBuilder.Conventions.Add(new DecimalPropertyConvention(38, 18));
}
Il predefinito DecimalPropertyConvention
in EF6 associa le decimal
proprietà alle decimal(18,2)
colonne.
Se si desidera solo che le singole proprietà abbiano una precisione specificata, è possibile impostare la precisione per la proprietà dell'entità su DbModelBuilder
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>().Property(e => e.Value).HasPrecision(38, 18);
}
Oppure aggiungi un EntityTypeConfiguration<>
per l'entità che specifica la precisione:
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);
}
}
Mi sono divertito a creare un attributo personalizzato per questo:
[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 in questo modo
[DecimalPrecision(20,10)]
public Nullable<decimal> DeliveryPrice { get; set; }
e la magia accade alla creazione del modello con qualche riflessione
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 prima parte è quella di ottenere tutte le classi nel modello (il mio attributo personalizzato è definito in quell'assemblaggio, quindi l'ho usato per ottenere l'assemblaggio con il modello)
il secondo foreach ottiene tutte le proprietà in quella classe con l'attributo personalizzato e l'attributo stesso in modo che io possa ottenere la precisione e ridimensionare i dati
dopo che devo chiamare
modelBuilder.Entity<MODEL_CLASS>().Property(c=> c.PROPERTY_NAME).HasPrecision(PRECISION,SCALE);
quindi chiamo modelBuilder.Entity () per riflessione e lo memorizzo nella variabile entityConfig, quindi creo l'espressione lambda "c => c.PROPERTY_NAME"
Dopodiché, se il decimale è nullable chiamo il
Property(Expression<Func<TStructuralType, decimal?>> propertyExpression)
metodo (lo chiamo per posizione nell'array, non è l'ideale lo so, qualsiasi aiuto sarà molto apprezzato)
e se non è nullable chiamo il
Property(Expression<Func<TStructuralType, decimal>> propertyExpression)
metodo.
Avendo DecimalPropertyConfiguration chiamo il metodo HasPrecision.
MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });
per ottenere il sovraccarico corretto. sembra funzionare finora.
Usando DecimalPrecisonAttribute
da KinSlayerUY, in EF6 puoi creare una convenzione che gestirà le singole proprietà che hanno l'attributo (invece di impostare DecimalPropertyConvention
simili in questa risposta che influenzerà tutte le proprietà decimali).
[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);
}
}
Quindi nel tuo DbContext
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
}
Precision
, ti consiglio di impostare il limite superiore su 28 (quindi > 28
nelle tue condizioni). Secondo la documentazione MSDN, System.Decimal
può rappresentare solo un massimo di 28-29 cifre di precisione ( msdn.microsoft.com/en-us/library/364x0z75.aspx ). Inoltre, l'attributo dichiara Scale
come byte
, il che significa che il prerequisito attribute.Scale < 0
non è necessario.
System.Decimal
no. Pertanto non ha senso impostare il presupposto del limite superiore su qualcosa di più grande di 28; System.Decimal
non può rappresentare numeri così grandi, apparentemente. Inoltre, tenere presente che questo attributo è utile per i fornitori di dati diversi da SQL Server. Ad esempio, il numeric
tipo di PostgreSQL supporta fino a 131072 cifre di precisione.
decimal(38,9)
colonna conterrà felice, System.Decimal.MaxValue
ma una decimal(28,9)
colonna no. Non c'è motivo di limitare la precisione a soli 28.
Apparentemente, puoi sovrascrivere il metodo DbContext.OnModelCreating () e configurare la precisione in questo modo:
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;
}
Ma questo è un codice piuttosto noioso quando devi farlo con tutte le tue proprietà legate al prezzo, quindi ho pensato a questo:
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);
}
È buona norma chiamare il metodo di base quando si esegue l'override di un metodo, anche se l'implementazione di base non fa nulla.
Aggiornamento: questo articolo è stato anche molto utile.
base.OnModelCreating(modelBuilder);
sia necessario chiamare . Dai metadati DbContext in 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) ha qualcosa chiamato Convenzioni personalizzate . Per impostare la precisione decimale:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<decimal>().Configure(config => config.HasPrecision(18, 4));
}
Riferimento:
[Column(TypeName = "decimal(18,2)")]
questo funzionerà con le prime migrazioni del codice EF Core come descritto qui .
The store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
questa riga di codice potrebbe essere un modo più semplice per realizzare lo stesso:
public class ProductConfiguration : EntityTypeConfiguration<Product>
{
public ProductConfiguration()
{
this.Property(m => m.Price).HasPrecision(10, 2);
}
}
- PER EF CORE - con utilizzo di System.ComponentModel.DataAnnotations;
use [Column
( TypeName
= "decimal
( precisione , scala )")]
Precisione = numero totale di caratteri utilizzati
Scala = numero totale dopo il punto. (facile confondersi)
Esempio :
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; }
}
Maggiori dettagli qui: https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types
In 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);
});
Puoi sempre dire a EF di farlo con le convenzioni nella classe Context nella funzione OnModelCreating come segue:
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);
}
Questo vale solo per Code First EF fyi e si applica a tutti i tipi decimali mappati sul db.
Remove<DecimalPropertyConvention>();
viene prima Add(new DecimalPropertyConvention(18, 4));
. Penso che sia strano che non venga automaticamente ignorato.
utilizzando
System.ComponentModel.DataAnnotations;
Puoi semplicemente inserire quell'attributo nel tuo modello:
[DataType("decimal(18,5)")]
Puoi trovare ulteriori informazioni su MSDN - aspetto di Entity Data Model. http://msdn.microsoft.com/en-us/library/ee382834.aspx Consigliato per intero.
Effettivo per EntityFrameworkCore 3.1.3:
qualche soluzione in 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)");
L'attributo personalizzato di KinSlayerUY ha funzionato bene per me ma ho avuto problemi con ComplexTypes. Erano stati mappati come entità nel codice dell'attributo, quindi non potevano essere mappati come ComplexType.
Ho quindi esteso il codice per consentire ciò:
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, ho modificato i criteri di selezione del tipo per utilizzare le proprietà DbSet <> di DbContext. Penso che sia più sicuro perché ci sono momenti in cui ci sono classi nello spazio dei nomi dato che non dovrebbero far parte della definizione del modello o lo sono ma non sono entità. Oppure le tue entità potrebbero risiedere in spazi dei nomi separati o assiemi separati ed essere riunite in un unico contesto.
Inoltre, anche se è improbabile, non penso che sia sicuro fare affidamento sull'ordinamento delle definizioni dei metodi, quindi è meglio estrarli dall'elenco dei parametri. (.GetTypeMethods () è un metodo di estensione che ho creato per funzionare con il nuovo paradigma TypeInfo e può appiattire le gerarchie di classi quando si cercano metodi).
Si noti che OnModelCreating è delegato a questo metodo:
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)")]
attributo per le proprietà decimali