Ich experimentiere mit diesem Code-First-Ansatz, finde aber jetzt heraus, dass eine Eigenschaft vom Typ System.Decimal einer SQL-Spalte vom Typ Dezimal (18, 0) zugeordnet wird.
Wie stelle ich die Genauigkeit der Datenbankspalte ein?
Ich experimentiere mit diesem Code-First-Ansatz, finde aber jetzt heraus, dass eine Eigenschaft vom Typ System.Decimal einer SQL-Spalte vom Typ Dezimal (18, 0) zugeordnet wird.
Wie stelle ich die Genauigkeit der Datenbankspalte ein?
Antworten:
Die Antwort von Dave Van den Eynde ist jetzt veraltet. Es gibt zwei wichtige Änderungen: Ab EF 4.1 lautet die ModelBuilder-Klasse jetzt DbModelBuilder und es gibt jetzt eine DecimalPropertyConfiguration.HasPrecision-Methode mit der Signatur:
public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )
Dabei ist Genauigkeit die Gesamtzahl der Stellen, die die Datenbank speichert, unabhängig davon, wo der Dezimalpunkt liegt, und Skalierung die Anzahl der Dezimalstellen, die gespeichert werden.
Daher ist es nicht erforderlich, die gezeigten Eigenschaften zu durchlaufen, sondern die können nur von aufgerufen werden
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);
. War das beabsichtigt oder nur ein Opfer der Online-Eingabe von Code anstelle einer IDE?
Wenn Sie die Genauigkeit für alle decimals
in EF6 festlegen möchten, können Sie die Standardkonvention ersetzen, die DecimalPropertyConvention
in DbModelBuilder
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<DecimalPropertyConvention>();
modelBuilder.Conventions.Add(new DecimalPropertyConvention(38, 18));
}
Die Standardeinstellung DecimalPropertyConvention
in EF6 ordnet decimal
Eigenschaften zudecimal(18,2)
Spalten zu.
Wenn Sie nur möchten, dass einzelne Eigenschaften eine bestimmte Genauigkeit haben, können Sie die Genauigkeit für die Eigenschaft der Entität wie folgt festlegen DbModelBuilder
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>().Property(e => e.Value).HasPrecision(38, 18);
}
Oder fügen Sie eine EntityTypeConfiguration<>
für die Entität hinzu, die die Genauigkeit angibt:
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);
}
}
Ich hatte eine schöne Zeit, ein benutzerdefiniertes Attribut dafür zu erstellen:
[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; }
}
benutze es so
[DecimalPrecision(20,10)]
public Nullable<decimal> DeliveryPrice { get; set; }
und die Magie geschieht bei der Modellerstellung mit einigen Überlegungen
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);
}
}
}
Der erste Teil besteht darin, alle Klassen im Modell abzurufen (mein benutzerdefiniertes Attribut ist in dieser Assembly definiert, daher habe ich das verwendet, um die Assembly mit dem Modell abzurufen).
Das zweite foreach erhält alle Eigenschaften in dieser Klasse mit dem benutzerdefinierten Attribut und dem Attribut selbst, damit ich die Genauigkeits- und Skalierungsdaten erhalten kann
danach muss ich anrufen
modelBuilder.Entity<MODEL_CLASS>().Property(c=> c.PROPERTY_NAME).HasPrecision(PRECISION,SCALE);
Also rufe ich den modelBuilder.Entity () durch Reflektion auf und speichere ihn in der entityConfig-Variablen. Dann baue ich den Lambda-Ausdruck "c => c.PROPERTY_NAME"
Danach, wenn die Dezimalzahl nullbar ist, rufe ich die
Property(Expression<Func<TStructuralType, decimal?>> propertyExpression)
Methode (ich nenne dies durch die Position im Array, es ist nicht ideal, ich weiß, jede Hilfe wird sehr geschätzt)
und wenn es nicht nullbar ist, rufe ich das an
Property(Expression<Func<TStructuralType, decimal>> propertyExpression)
Methode.
Mit der DecimalPropertyConfiguration rufe ich die HasPrecision-Methode auf.
MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });
, um die richtige Überlastung zu bekommen. scheint soweit zu funktionieren.
Mit dem DecimalPrecisonAttribute
von KinSlayerUY können Sie in EF6 eine Konvention erstellen, die einzelne Eigenschaften behandelt, die das Attribut haben (im Gegensatz zum Festlegen des DecimalPropertyConvention
Gleichen in dieser Antwort, das sich auf alle Dezimaleigenschaften auswirkt).
[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);
}
}
Dann in Ihrem DbContext
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
}
Precision
, empfehle ich, die Obergrenze auf 28 zu setzen (also > 28
in Ihrem Zustand). Laut MSDN-Dokumentation System.Decimal
können maximal 28 bis 29 Stellen Genauigkeit dargestellt werden ( msdn.microsoft.com/en-us/library/364x0z75.aspx ). Außerdem wird das Attribut Scale
als deklariert byte
, was bedeutet, dass Ihre Vorbedingung attribute.Scale < 0
nicht erforderlich ist.
System.Decimal
jedoch nicht. Daher macht es keinen Sinn, die Obergrenze auf etwas größer als 28 zu setzen; System.Decimal
kann anscheinend keine so großen Zahlen darstellen. Beachten Sie auch, dass dieses Attribut für andere Datenanbieter als SQL Server nützlich ist. Beispielsweise numeric
unterstützt der Typ von PostgreSQL eine Genauigkeit von bis zu 131072 Stellen.
decimal(38,9)
Spalte haben, halten Sie diese gerne, System.Decimal.MaxValue
eine decimal(28,9)
Spalte jedoch nicht. Es gibt keinen Grund, die Genauigkeit auf nur 28 zu beschränken.
Anscheinend können Sie die DbContext.OnModelCreating () -Methode überschreiben und die Genauigkeit wie folgt konfigurieren:
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;
}
Aber dies ist ein ziemlich langwieriger Code, wenn Sie ihn mit all Ihren preisbezogenen Eigenschaften ausführen müssen. Deshalb habe ich mir Folgendes ausgedacht:
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 wird empfohlen, die Basismethode aufzurufen, wenn Sie eine Methode überschreiben, obwohl die Basisimplementierung nichts bewirkt.
Update: Dieser Artikel war auch sehr hilfreich.
base.OnModelCreating(modelBuilder);
notwendig ist. Aus den DbContext-Metadaten 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 Version 6 (Alpha, rc1) enthält sogenannte benutzerdefinierte Konventionen . So stellen Sie die Dezimalgenauigkeit ein:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<decimal>().Configure(config => config.HasPrecision(18, 4));
}
Referenz:
[Column(TypeName = "decimal(18,2)")]
Dies funktioniert bei ersten Migrationen mit EF Core-Code wie hier beschrieben .
The store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
Diese Codezeile könnte eine einfachere Möglichkeit sein, dasselbe zu erreichen:
public class ProductConfiguration : EntityTypeConfiguration<Product>
{
public ProductConfiguration()
{
this.Property(m => m.Price).HasPrecision(10, 2);
}
}
- FÜR EF KERN - mit System.ComponentModel.DataAnnotations;
Verwendung [Column
( TypeName
= "decimal
( Präzision , Skalierung )")]
Präzision = Gesamtzahl der verwendeten Zeichen
Skala = Gesamtzahl nach dem Punkt. (leicht zu verwirren)
Beispiel :
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; }
}
Weitere Details finden Sie hier: 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);
});
Sie können EF jederzeit anweisen, dies mit Konventionen in der Context-Klasse in der OnModelCreating-Funktion wie folgt zu tun:
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);
}
Dies gilt nur für Code First EF fyi und für alle Dezimaltypen, die der Datenbank zugeordnet sind.
Remove<DecimalPropertyConvention>();
vor dem kommt Add(new DecimalPropertyConvention(18, 4));
. Ich finde es seltsam, dass nicht nur automatisch überschrieben wird.
Verwenden von
System.ComponentModel.DataAnnotations;
Sie können dieses Attribut einfach in Ihr Modell einfügen:
[DataType("decimal(18,5)")]
Weitere Informationen finden Sie unter MSDN - Facette des Entitätsdatenmodells. http://msdn.microsoft.com/en-us/library/ee382834.aspx Vollständig empfohlen.
Tatsächlich für EntityFrameworkCore 3.1.3:
eine Lösung 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)");
Das benutzerdefinierte Attribut von KinSlayerUY hat bei mir gut funktioniert, aber ich hatte Probleme mit ComplexTypes. Sie wurden als Entitäten im Attributcode zugeordnet und konnten dann nicht als ComplexType zugeordnet werden.
Ich habe daher den Code erweitert, um dies zu ermöglichen:
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, ich habe die Typauswahlkriterien geändert, um die DbSet <> -Eigenschaften des DbContext zu nutzen. Ich denke, das ist sicherer, weil es Zeiten gibt, in denen Sie Klassen im angegebenen Namespace haben, die nicht Teil der Modelldefinition sein sollten oder aber keine Entitäten sind. Oder Ihre Entitäten befinden sich in separaten Namespaces oder separaten Assemblys und werden zu einem einzigen Kontext zusammengefasst.
Auch wenn dies unwahrscheinlich ist, halte ich es nicht für sicher, sich auf die Reihenfolge der Methodendefinitionen zu verlassen. Daher ist es besser, sie anhand der Parameterliste herauszuholen. (.GetTypeMethods () ist eine Erweiterungsmethode, die ich für die Arbeit mit dem neuen TypeInfo-Paradigma erstellt habe und die Klassenhierarchien bei der Suche nach Methoden reduzieren kann.)
Beachten Sie, dass OnModelCreating diese Methode delegiert:
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)")]
Attribut für Ihre Dezimaleigenschaften zu verwenden