Précision décimale et échelle dans EF Code First

230

J'expérimente cette approche avec le code d'abord, mais je découvre maintenant qu'une propriété de type System.Decimal est mappée sur une colonne sql de type décimal (18, 0).

Comment définir la précision de la colonne de base de données?

Dave Van den Eynde
la source
11
une façon consiste à utiliser l' [Column(TypeName = "decimal(18,4)")]attribut pour vos propriétés décimales
S.Serpooshan
[Colonne (TypeName = "décimal (18,4)")] a très bien fonctionné !!!
Brian Rice

Réponses:

257

La réponse de Dave Van den Eynde est désormais obsolète. Il y a 2 changements importants, à partir d'EF 4.1, la classe ModelBuilder est maintenant DbModelBuilder et il y a maintenant une méthode DecimalPropertyConfiguration.HasPrecision qui a une signature de:

public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )

où précision est le nombre total de chiffres que la base de données stockera, quel que soit l'endroit où le point décimal tombe et l'échelle est le nombre de décimales qu'elle stockera.

Par conséquent, il n'est pas nécessaire de parcourir les propriétés comme indiqué, mais le peut simplement être appelé depuis

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
la source
Pour tous ceux qui rencontrent des problèmes avec DbModelBuilder, essayezSystem.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder
Lloyd Powell
1
J'ai remarqué que tu n'as jamais appelé base.OnModelCreating(modelBuilder);. Était-ce intentionnel ou simplement victime de taper du code en ligne plutôt que dans un IDE?
BenSwayne
1
@BenSwayne merci pour la place, c'est mon omission, rien d'intentionnel. Je vais modifier la réponse.
AlexC
26
Les 2 arguments de HasPrecision (précision, échelle) sont mal documentés. La précision est le nombre total de chiffres qu'elle stockera, quel que soit le point décimal. échelle est le nombre de décimales qu'il stockera.
Chris Moschini
1
Existe-t-il une configuration EF pour la définir pour toutes les propriétés décimales sur toutes les entités en un seul endroit? Nous utilisons généralement (19,4). Ce serait bien de l'appliquer automatiquement à toutes les propriétés décimales, donc nous ne pouvons pas oublier de définir une précision de propriété et de manquer la précision anticipée dans les calculs.
Mike de Klerk
89

Si vous souhaitez définir la précision pour tous decimalsdans EF6, vous pouvez remplacer la valeur par défautDecimalPropertyConvention convention utilisée dans DbModelBuilder:

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

Le défaut DecimalPropertyConvention par dans EF6 mappe les decimalpropriétés àdecimal(18,2) colonnes.

Si vous souhaitez uniquement que les propriétés individuelles aient une précision spécifiée, vous pouvez définir la précision de la propriété de l'entité sur le DbModelBuilder :

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

Ou, ajoutez un EntityTypeConfiguration<>pour l'entité qui spécifie la précision:

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
la source
1
Ma solution préférée. Fonctionne parfaitement lors de l'utilisation de CodeFirst et des migrations: EF recherche toutes les propriétés dans toutes les classes où "décimal" est utilisé et génère une migration pour ces propriétés. Génial!
okieh
75

J'ai passé un bon moment à créer un attribut personnalisé pour cela:

[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; }

}

l'utiliser comme ça

[DecimalPrecision(20,10)]
public Nullable<decimal> DeliveryPrice { get; set; }

et la magie se produit lors de la création du modèle avec une certaine réflexion

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 première partie consiste à obtenir toutes les classes dans le modèle (mon attribut personnalisé est défini dans cet assemblage, donc je l'ai utilisé pour obtenir l'assemblage avec le modèle)

le deuxième foreach obtient toutes les propriétés de cette classe avec l'attribut personnalisé et l'attribut lui-même afin que je puisse obtenir les données de précision et d'échelle

après ça je dois appeler

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

donc j'appelle le modelBuilder.Entity () par réflexion et le stocke dans la variable entityConfig puis je construis l'expression lambda "c => c.PROPERTY_NAME"

Après cela, si la décimale est nulle, j'appelle le

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

méthode (j'appelle cela par la position dans le tableau, ce n'est pas l'idéal je sais, toute aide sera très appréciée)

et si ce n'est pas nullable j'appelle le

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

méthode.

Ayant la DecimalPropertyConfiguration j'appelle la méthode HasPrecision.

KinSlayerUY
la source
3
Merci pour cela. Cela m'a évité de générer des milliers d'expressions lambda.
Sean
1
Cela fonctionne très bien et est super propre! Pour EF 5, j'ai changé System.Data.Entity.ModelConfiguration.ModelBuilder en System.Data.Entity.DbModelBuilder
Colin
3
j'utilise MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });pour obtenir la surcharge correcte. semble fonctionner jusqu'à présent.
fscan
3
J'ai enveloppé cela dans une bibliothèque et rendu plus facile d'appeler à partir du DbContext: github.com/richardlawley/EntityFrameworkAttributeConfig (également disponible via nuget)
Richard
Richard, j'adore l'idée de votre projet mais y a-t-il quelque chose qui nécessite EF6? Je l'utiliserais s'il y avait une version compatible EF5, afin de pouvoir l'utiliser avec ma version d'ODP.NET.
Patrick Szalapski
50

À l'aide de DecimalPrecisonAttributefrom KinSlayerUY, dans EF6, vous pouvez créer une convention qui gérera les propriétés individuelles qui ont l'attribut (par opposition à la définition de la DecimalPropertyConventionmême chose dans cette réponse qui affectera toutes les propriétés décimales).

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

Puis dans votre DbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
}
kjbartel
la source
@MichaelEdenfield En fait non, il n'y en a pas dans EF6. C'est pourquoi j'ai ajouté deux réponses, celle-ci et l'autre dont vous avez parlé. Il s'agit d'un attribut que vous pouvez attribuer à une seule propriété décimale au lieu d'affecter toutes les propriétés décimales du modèle.
kjbartel
mon mauvais, je n'ai pas remarqué que vous les avez tous les deux écrits: \
Michael Edenfield
1
Si vous allez vérifier les limites Precision, je recommande de définir la limite supérieure à 28 (donc > 28dans votre état). Selon la documentation MSDN, System.Decimalne peut représenter qu'un maximum de 28 à 29 chiffres de précision ( msdn.microsoft.com/en-us/library/364x0z75.aspx ). De plus, l'attribut déclare Scalecomme byte, ce qui signifie que votre précondition attribute.Scale < 0n'est pas nécessaire.
NathanAldenSr
2
@kjbartel Il est vrai que certains fournisseurs de bases de données prennent en charge des précisions supérieures à 28; cependant, selon MSDN, System.Decimalnon. Par conséquent, cela n'a aucun sens de fixer la condition préalable de limite supérieure à quelque chose de plus grand que 28; System.Decimalne peut pas représenter des nombres aussi grands, apparemment. Sachez également que cet attribut est utile pour les fournisseurs de données autres que SQL Server. Par exemple, le numerictype de PostgreSQL prend en charge jusqu'à 131072 chiffres de précision.
NathanAldenSr
1
@NathanAldenSr Comme je l'ai dit, les bases de données utilisent une virgule décimale fixe ( msdn ) tandis que System.Decimal est une virgule flottante . Ils sont complètement différents. Par exemple, avoir une decimal(38,9)colonne tiendra heureux, System.Decimal.MaxValuemais pas une decimal(28,9)colonne. Il n'y a aucune raison de limiter la précision à seulement 28.
kjbartel
47

Apparemment, vous pouvez remplacer la méthode DbContext.OnModelCreating () et configurer la précision comme ceci:

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;
}

Mais c'est un code assez fastidieux lorsque vous devez le faire avec toutes vos propriétés liées au prix, alors j'ai trouvé ceci:

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

Il est recommandé d'appeler la méthode de base lorsque vous remplacez une méthode, même si l'implémentation de base ne fait rien.

Mise à jour: cet article a également été très utile.

Dave Van den Eynde
la source
10
Merci, cela m'a orienté dans la bonne direction. Dans CTP5, la syntaxe a changé pour permettre d'ajouter la précision et l'échelle dans la même instruction: modelBuilder.Entity <Product> () .Property (product => product.Price) .HasPrecision (6, 2);
Col
2
Pourtant, ne serait-il pas agréable d'avoir une sorte de "valeur par défaut" que vous pourriez définir pour toutes les décimales?
Dave Van den Eynde
3
Je ne pense pas qu'il base.OnModelCreating(modelBuilder);soit nécessaire d' appeler . À partir des métadonnées DbContext dans 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: C'est bien, mais en tant qu'implémenteur, je ne devrais pas m'en soucier et appeler toujours la base.
Dave Van den Eynde
@ Dave et @Matt: Il y avait un commentaire qu'il était "IMPORTANT" d'appeler la base. C'est une bonne pratique, mais lorsque la source EF a une implémentation vide, il est trompeur de prétendre qu'elle est importante. Cela laisse les gens se demander ce que fait la base. J'étais tellement curieux de savoir ce qui était IMPORTANT que j'ai décompilé en ef5.0 pour vérifier. Rien ici. Donc juste une bonne habitude.
phil soady
30

Entity Framework Ver 6 (Alpha, rc1) a quelque chose appelé Conventions personnalisées . Pour définir la précision décimale:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Properties<decimal>().Configure(config => config.HasPrecision(18, 4));
}

Référence:

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

cela fonctionnera avec les premières migrations de code EF Core comme décrit ici .

Elnoor
la source
1
Si vous ajoutez simplement ceci à votre modèle, vous pouvez obtenirThe store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
Savage
@Savage semble être un problème avec votre fournisseur de base de données ou la version de la base de données
Elnoor
@Elnoor Savage est correct, cela générera une erreur dans EF Migrations 6.x. La version non Core héritée ne prend pas en charge la spécification de la précision / de l'échelle via l'attribut Column et ne fait rien (par défaut 18,2) si vous utilisez l'attribut DataType. Pour le faire fonctionner via Attribute dans EF 6.x, vous devez implémenter votre propre extension à ModelBuilder.
Chris Moschini
1
@ChrisMoschini, j'ai changé ma réponse en mentionnant EF Core. Merci
Elnoor
14

cette ligne de code pourrait être un moyen plus simple de réaliser la même chose:

 public class ProductConfiguration : EntityTypeConfiguration<Product>
    {
        public ProductConfiguration()
        {
            this.Property(m => m.Price).HasPrecision(10, 2);
        }
    }
armadillo.mx
la source
9

- POUR EF CORE - avec utilisation de System.ComponentModel.DataAnnotations;

utilisation [Column( TypeName = "decimal( précision , échelle )")]

Précision = nombre total de caractères utilisés

Échelle = nombre total après le point. (facile à confondre)

Exemple :

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; }
}

Plus de détails ici: https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types

sofsntp
la source
3

Dans 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
la source
Cette réponse semble être une mise à niveau vers une autre réponse qui définit l'attribut, vous devez le modifier dans cette réponse
Rhys Bevilaqua
3

Vous pouvez toujours dire à EF de le faire avec les conventions de la classe Context dans la fonction OnModelCreating comme suit:

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

Cela s'applique uniquement à Code First EF fyi et s'applique à tous les types décimaux mappés à la base de données.

Gecko IT
la source
Cela ne fonctionnait pas Remove<DecimalPropertyConvention>();avant le Add(new DecimalPropertyConvention(18, 4));. Je pense qu'il est étrange que ce ne soit pas simplement remplacé automatiquement.
Mike de Klerk
2

En utilisant

System.ComponentModel.DataAnnotations;

Vous pouvez simplement mettre cet attribut dans votre modèle:

[DataType("decimal(18,5)")]
VinnyG
la source
1
il s'agit de l'implémentation la plus simple pour la lisibilité et la simplicité. IMHO
ransems
11
Par msdn.microsoft.com/en-us/library/jj591583(v=vs.113).aspx , cette réponse est en fait incorrecte. "Ne confondez pas l'attribut TypeName de la colonne avec le DataType DataAnnotation. DataType est une annotation utilisée pour l'interface utilisateur et est ignorée par Code First."
speckledcarp
2
@ransems Je le pensais aussi, jusqu'à ce que je le teste et comme cela a été dit ci-dessus, cela ne fonctionne pas pour CodeFirst et ne migre pas vers la base de données
RoLYroLLs
1

Vous pouvez trouver plus d'informations sur MSDN - facette du modèle de données d'entité. http://msdn.microsoft.com/en-us/library/ee382834.aspx Complet recommandé.

Jaider
la source
C'est génial et tout, mais comment est-ce lié à Code-First?
Dave Van den Eynde
C'est utile mais je ne peux pas encore spécifier un attribut [Precision] pour un Decimal. J'ai donc utilisé la solution fournie par @KinSlayerUY.
Colin
1

Réel pour EntityFrameworkCore 3.1.3:

une solution dans 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
la source
0

L'attribut personnalisé de KinSlayerUY a bien fonctionné pour moi mais j'ai eu des problèmes avec ComplexTypes. Ils étaient mappés en tant qu'entités dans le code d'attribut et ne pouvaient donc pas être mappés en tant que ComplexType.

J'ai donc étendu le code pour permettre ceci:

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
la source
0

@ Mark007, j'ai changé les critères de sélection de type pour chevaucher les propriétés DbSet <> du DbContext. Je pense que c'est plus sûr car il y a des moments où vous avez des classes dans l'espace de noms donné qui ne devraient pas faire partie de la définition du modèle ou elles le sont mais ne sont pas des entités. Ou vos entités peuvent résider dans des espaces de noms ou des assemblages distincts et être regroupées dans un seul contexte.

De plus, même si cela est peu probable, je ne pense pas qu'il soit sûr de s'appuyer sur l'ordre des définitions de méthode, il est donc préférable de les extraire avec la liste de paramètres. (.GetTypeMethods () est une méthode d'extension que j'ai construite pour fonctionner avec le nouveau paradigme TypeInfo et qui peut aplatir les hiérarchies de classes lors de la recherche de méthodes).

Notez que OnModelCreating délègue à cette méthode:

    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
la source
Je viens de réaliser que je n'ai pas traité les ComplexTypes par cette approche. Le révisera plus tard.
Eniola
Cependant, la solution proposée par Brian est simple, élégante et fonctionne. Je ne ferai aucune déclaration catégorique sur les performances, mais le fait de partir déjà de PropertyInfo plutôt que de traquer le vôtre devrait donner de meilleures performances sur les très grands modèles (de l'ordre de 200 et plus).
Eniola