Dezimalgenauigkeit und Skalierung im EF-Code zuerst

230

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?

Dave Van den Eynde
quelle
11
Eine Möglichkeit besteht darin, ein [Column(TypeName = "decimal(18,4)")]Attribut für Ihre Dezimaleigenschaften zu verwenden
S.Serpooshan
[Column (TypeName = "decimal (18,4)")] hat super funktioniert !!!
Brian Rice

Antworten:

257

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);
   }
}
AlexC
quelle
Für alle, die Probleme mit dem DbModelBuilder haben, versuchen SieSystem.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder
Lloyd Powell
1
Ich habe bemerkt, dass du nie angerufen hast base.OnModelCreating(modelBuilder);. War das beabsichtigt oder nur ein Opfer der Online-Eingabe von Code anstelle einer IDE?
BenSwayne
1
@ BenSwayne danke für den Spot, das ist meine Auslassung, nichts Absichtliches. Ich werde die Antwort bearbeiten.
AlexC
26
Die beiden Argumente für HasPrecision (Genauigkeit, Skalierung) sind schlecht dokumentiert. Genauigkeit ist die Gesamtzahl der gespeicherten Stellen, unabhängig davon, wo der Dezimalpunkt liegt. scale ist die Anzahl der Dezimalstellen, die gespeichert werden.
Chris Moschini
1
Gibt es eine EF-Konfiguration, um sie für alle Dezimaleigenschaften aller Entitäten an einer Stelle festzulegen? Wir verwenden im Allgemeinen (19,4). Es wäre schön, wenn dies automatisch auf alle Dezimaleigenschaften angewendet würde, sodass wir nicht vergessen können, eine Eigenschaftsgenauigkeit festzulegen und die erwartete Genauigkeit bei Berechnungen zu verfehlen.
Mike de Klerk
89

Wenn Sie die Genauigkeit für alle decimalsin EF6 festlegen möchten, können Sie die Standardkonvention ersetzen, die DecimalPropertyConventionin DbModelBuilder:

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

Die Standardeinstellung DecimalPropertyConventionin EF6 ordnet decimalEigenschaften 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);
    }
}
kjbartel
quelle
1
Meine Lieblingslösung. Funktioniert perfekt bei Verwendung von CodeFirst und Migrationen: EF sucht nach allen Eigenschaften in allen Klassen, in denen "dezimal" verwendet wird, und generiert eine Migration für diese Eigenschaften. Toll!
Okieh
75

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.

KinSlayerUY
quelle
3
Danke dafür. Es hat mich davor bewahrt, Tausende von Lambda-Ausdrücken zu erzeugen.
Sean
1
Das funktioniert super und ist super sauber! Für EF 5 habe ich System.Data.Entity.ModelConfiguration.ModelBuilder in System.Data.Entity.DbModelBuilder geändert
Colin
3
Ich benutze MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });, um die richtige Überlastung zu bekommen. scheint soweit zu funktionieren.
fscan
3
Ich habe dies in eine Bibliothek eingepackt und es einfacher gemacht, über den DbContext anzurufen: github.com/richardlawley/EntityFrameworkAttributeConfig (auch über Nuget erhältlich)
Richard
Richard, ich liebe die Idee Ihres Projekts, aber gibt es etwas, das EF6 erfordert? Ich würde es verwenden, wenn es eine EF5-kompatible Version gäbe, damit ich es mit meiner Version von ODP.NET verwenden kann.
Patrick Szalapski
50

Mit dem DecimalPrecisonAttributevon KinSlayerUY können Sie in EF6 eine Konvention erstellen, die einzelne Eigenschaften behandelt, die das Attribut haben (im Gegensatz zum Festlegen des DecimalPropertyConventionGleichen 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());
}
kjbartel
quelle
@MichaelEdenfield Eigentlich nein, es gibt keine davon in EF6. Deshalb habe ich zwei Antworten hinzugefügt, diese und die andere, auf die Sie sich bezogen haben. Dies ist ein Attribut, das Sie einer einzelnen Dezimaleigenschaft zuweisen können, anstatt alle Dezimaleigenschaften im Modell zu beeinflussen.
Kjbartel
Mein schlechtes, habe nicht bemerkt, dass du sie beide geschrieben hast: \
Michael Edenfield
1
Wenn Sie die Grenzen überprüfen möchten Precision, empfehle ich, die Obergrenze auf 28 zu setzen (also > 28in Ihrem Zustand). Laut MSDN-Dokumentation System.Decimalkönnen maximal 28 bis 29 Stellen Genauigkeit dargestellt werden ( msdn.microsoft.com/en-us/library/364x0z75.aspx ). Außerdem wird das Attribut Scaleals deklariert byte, was bedeutet, dass Ihre Vorbedingung attribute.Scale < 0nicht erforderlich ist.
NathanAldenSr
2
@kjbartel Es stimmt, dass einige Datenbankanbieter Präzisionen unterstützen, die größer als 28 sind. Laut MSDN System.Decimaljedoch nicht. Daher macht es keinen Sinn, die Obergrenze auf etwas größer als 28 zu setzen; System.Decimalkann anscheinend keine so großen Zahlen darstellen. Beachten Sie auch, dass dieses Attribut für andere Datenanbieter als SQL Server nützlich ist. Beispielsweise numericunterstützt der Typ von PostgreSQL eine Genauigkeit von bis zu 131072 Stellen.
NathanAldenSr
1
@NathanAldenSr Wie ich bereits sagte, verwenden Datenbanken eine Festkomma- Dezimalstelle ( msdn ), während System.Decimal Gleitkomma ist . Sie sind völlig anders. Wenn Sie beispielsweise eine decimal(38,9)Spalte haben, halten Sie diese gerne, System.Decimal.MaxValueeine decimal(28,9)Spalte jedoch nicht. Es gibt keinen Grund, die Genauigkeit auf nur 28 zu beschränken.
kjbartel
47

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.

Dave Van den Eynde
quelle
10
Danke, das hat mich in die richtige Richtung gelenkt. In CTP5 wurde die Syntax geändert, um das Hinzufügen von Präzision und Skalierung in derselben Anweisung zu ermöglichen: modelBuilder.Entity <Produkt> () .Property (product => product.Price) .HasPrecision (6, 2);
Col
2
Wäre es nicht schön, eine Art "Standard" zu haben, die Sie für alle Dezimalstellen festlegen könnten ?
Dave Van den Eynde
3
Ich denke nicht, dass ein Anruf 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.
Matt Jenkins
@Matt: Das ist schön, aber als Implementierer sollte mich das nicht interessieren und ich rufe immer die Basis an.
Dave Van den Eynde
@ Dave und @Matt: Es gab einen Kommentar, dass es "WICHTIG" war, base anzurufen. Es ist eine gute Praxis, aber wenn die EF-Quelle eine leere Implementierung hat, ist es irreführend zu behaupten, dass sie wichtig ist. Die Leute fragen sich, was die Basis macht. Ich war so neugierig, was WICHTIG war, dass ich zur Überprüfung auf ef5.0 dekompiliert habe. Nichts hier. Also nur eine gute Angewohnheit.
Phil Soady
30

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:

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

Dies funktioniert bei ersten Migrationen mit EF Core-Code wie hier beschrieben .

Elnoor
quelle
1
Wenn Sie dies nur zu Ihrem Modell hinzufügen, können SieThe store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
Savage
@ Savage sieht aus wie es ein Problem mit Ihrem Datenbankanbieter oder der Version der Datenbank ist
Elnoor
@Elnoor Savage ist korrekt. Dies führt zu einem Fehler in EF Migrations 6.x. Die ältere Nicht-Core-Version unterstützt die Angabe von Genauigkeit / Skalierung über das Column-Attribut nicht und führt nichts aus (standardmäßig 18,2), wenn Sie das DataType-Attribut verwenden. Damit es über Attribute in EF 6.x funktioniert, müssen Sie Ihre eigene Erweiterung für ModelBuilder implementieren.
Chris Moschini
1
@ ChrisMoschini, ich habe meine Antwort geändert und EF Core erwähnt. Danke
Elnoor
14

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

- 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

sofsntp
quelle
3

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);
    });
user3332875
quelle
Diese Antwort scheint ein Upgrade auf eine andere Antwort zu sein, die das Attribut definiert. Sie sollten dies in diese Antwort
umwandeln
3

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.

Gecko IT
quelle
Es hat nicht funktioniert, bis es Remove<DecimalPropertyConvention>();vor dem kommt Add(new DecimalPropertyConvention(18, 4));. Ich finde es seltsam, dass nicht nur automatisch überschrieben wird.
Mike de Klerk
2

Verwenden von

System.ComponentModel.DataAnnotations;

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

[DataType("decimal(18,5)")]
VinnyG
quelle
1
Dies ist die einfachste Implementierung für Lesbarkeit und Einfachheit. IMHO
ransems
11
Per msdn.microsoft.com/en-us/library/jj591583(v=vs.113).aspx ist diese Antwort sachlich falsch. "Verwechseln Sie das TypeName-Attribut der Spalte nicht mit dem DataType DataAnnotation. DataType ist eine Anmerkung, die für die Benutzeroberfläche verwendet wird und von Code First ignoriert wird."
Speckledcarp
2
@ransems Ich dachte auch, bis ich es gerade getestet habe und wie oben gesagt, funktioniert dies nicht für CodeFirst und migriert nicht in die Datenbank
RoLYroLLs
1

Weitere Informationen finden Sie unter MSDN - Facette des Entitätsdatenmodells. http://msdn.microsoft.com/en-us/library/ee382834.aspx Vollständig empfohlen.

Jaider
quelle
Das ist großartig und alles, aber wie hängt das mit Code-First zusammen?
Dave Van den Eynde
Es ist nützlich, aber ich kann immer noch kein [Präzisions] -Attribut für eine Dezimalzahl angeben. Also habe ich die von @KinSlayerUY bereitgestellte Lösung verwendet.
Colin
1

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)");
Azamat
quelle
0

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
quelle
0

@ 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;
            }
        }
    }
Eniola
quelle
Ich habe gerade festgestellt, dass ich mich mit diesem Ansatz nicht mit ComplexTypes befasst habe. Werde es später überarbeiten.
Eniola
Die von Brian vorgeschlagene Lösung ist jedoch einfach, elegant und funktioniert. Ich werde keine kategorischen Aussagen über die Leistung machen, aber wenn Sie bereits reflektierte PropertyInfo losfahren, anstatt Ihre zu jagen, sollte dies bei sehr großen Modellen (in der Größenordnung von 200 und höher) zu einer besseren Leistung führen.
Eniola