web-dev-qa-db-ger.com

Dezimalgenauigkeit und Skalierung in EF Code First

Ich experimentiere mit diesem Code-First-Ansatz, finde aber jetzt heraus, dass eine Eigenschaft vom Typ System.Decimal einer SQL-Spalte vom Typ decimal (18, 0) zugeordnet wird.

Wie stelle ich die Genauigkeit der Datenbankspalte ein?

208

Die Antwort von Dave Van den Eynde ist nicht mehr aktuell. Ab EF 4.1 gibt es zwei wichtige Änderungen: Die ModelBuilder-Klasse lautet 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 von der Datenbank gespeichert werden, unabhängig davon, wo der Dezimalpunkt abfällt, und Skalierung die Anzahl der Dezimalstellen, die gespeichert werden.

Daher ist es nicht erforderlich, die angezeigten Eigenschaften zu durchlaufen, sondern es kann 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);
   }
}
239
AlexC

Wenn Sie die Genauigkeit für alle decimals in EF6 festlegen möchten, können Sie die in DecimalPropertyConvention verwendete Standardkonvention DbModelBuilder ersetzen:

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

Die Standardeinstellung DecimalPropertyConvention in EF6 ordnet die Eigenschaften decimal den Spalten decimal(18,2) 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 in DbModelBuilder festlegen:

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

Oder fügen Sie ein 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);
    }
}
80
kjbartel

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 einigem Nachdenken

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 dieses verwendet, um die Assembly mit dem Modell abzurufen.)

das zweite foreach ruft alle Eigenschaften in dieser Klasse mit dem benutzerdefinierten Attribut und dem Attribut selbst ab, damit ich die Genauigkeits- und Skalierungsdaten abrufen kann

danach muss ich anrufen

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

also rufe ich die modelBuilder.Entity () durch Reflektion auf und speichere sie in der entityConfig-Variablen. Dann erstelle ich den Lambda-Ausdruck "c => c.PROPERTY_NAME"

Danach, wenn die Dezimalstelle nullable ist, rufe ich das auf

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

methode (Ich nenne dies die Position im Array, es ist nicht ideal, ich weiß, jede Hilfe wird sehr geschätzt)

und wenn es nicht nullable ist, nenne ich das

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

methode.

Mit der DecimalPropertyConfiguration rufe ich die HasPrecision-Methode auf.

69
KinSlayerUY

Anscheinend können Sie die DbContext.OnModelCreating () -Methode überschreiben und die Genauigkeit folgendermaßen 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 das ist ziemlich langweilig, wenn Sie es mit all Ihren preisbezogenen Eigenschaften machen müssen, also habe ich mir das 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.

47

Mit dem DecimalPrecisonAttribute von KinSlayerUY können Sie in EF6 eine Konvention erstellen, die einzelne Eigenschaften behandelt, die das Attribut haben (im Gegensatz zum Setzen des DecimalPropertyConvention wie in diese Antwort Dies wirkt sich auf alle Dezimaleigenschaften aus.

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

Entity Framework Ver 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:

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

dies funktioniert mit Code-First-Migrationen, wie hier beschrieben .

15
Elnoor

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);
        }
    }
14
armadillo.mx

- FOR EF CORE - mit unter Verwendung von System.ComponentModel.DataAnnotations;

verwenden [Column (TypeName= "decimal ( Genauigkeit , Maßstab ) ")]

Präzision = Gesamtzahl der verwendeten Zeichen

Scale = Gesamtzahl nach dem Punkt. (leicht zu verwechseln)

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 Informationen finden Sie hier: https://docs.Microsoft.com/en-us/ef/core/modeling/relational/data-types

5
sofsntp

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

Sie können EF immer 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.

3
Gecko IT

Weitere Informationen finden Sie unter MSDN - Facette des Entity Data Model. http://msdn.Microsoft.com/en-us/library/ee382834.aspx Vollständig empfohlen.

1
Jaider

Verwenden

System.ComponentModel.DataAnnotations;

Sie können dieses Attribut einfach in Ihr Modell einfügen:

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

Das benutzerdefinierte Attribut von KinSlayerUY funktionierte gut für mich, aber ich hatte Probleme mit ComplexTypes. Sie wurden als Entitäten im Attributcode zugeordnet, konnten also 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);
            }
        }
    }
0
Mark007

@ Mark007, Ich habe die Typauswahlkriterien geändert, um die DbSet <> -Eigenschaften von DbContext zu berücksichtigen. Ich halte dies für sicherer, da es Zeiten gibt, in denen Klassen im angegebenen Namespace 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 in einem einzigen Kontext zusammengefasst.

Auch wenn es unwahrscheinlich ist, denke ich, dass es nicht sicher ist, sich auf die Reihenfolge der Methodendefinitionen zu verlassen. Daher ist es besser, sie anhand der Parameterliste zu ermitteln. (.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 an 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;
            }
        }
    }
0
Eniola