web-dev-qa-db-fra.com

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

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

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

208
Dave Van den Eynde

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

public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )

où précision correspond au nombre total de chiffres que la base de données stockera, quel que soit le point de la virgule et où scale correspond au nombre de décimales à stocker.

Par conséquent, il n’est pas nécessaire de parcourir les propriétés comme indiqué, mais on peut simplement les appeler à partir de

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

Si vous souhaitez définir la précision pour toutes les decimals dans EF6, vous pouvez remplacer la convention par défaut DecimalPropertyConvention utilisée dans la DbModelBuilder:

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

La valeur par défaut DecimalPropertyConvention dans EF6 mappe les propriétés decimal à 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 la propriété 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);
    }
}
80
kjbartel

J'ai passé un bon moment en créant 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 passe à la création du modèle avec une 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 du modèle (mon attribut personnalisé est défini dans cet assemblage, donc je l'ai utilisé pour obtenir l'assembly avec le modèle)

le second 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 cela je dois appeler

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

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

Après cela, si le nombre décimal est nullable, j’appelle le

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

méthode (j'appelle cela par la position dans le tableau, ce n'est pas 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 DecimalPropertyConfiguration, j'appelle la méthode HasPrecision.

69
KinSlayerUY

Apparemment, vous pouvez remplacer la méthode DbContext.OnModelCreating () et configurer la précision de la manière suivante:

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 je suis venu avec 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 substituez une méthode, même si l'implémentation de base ne fait rien.

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

47
Dave Van den Eynde

En utilisant la DecimalPrecisonAttribute de KinSlayerUY, dans EF6, vous pouvez créer une convention qui gérera des propriétés individuelles ayant l'attribut (par opposition à la définition de la DecimalPropertyConvention comme dans cette réponse qui affecter 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());
}
46
kjbartel

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:

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

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

15
Elnoor

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

- FOR EF CORE - avec à l'aide de System.ComponentModel.DataAnnotations;

utiliser [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

5
sofsntp

En 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

Vous pouvez toujours dire à EF de le faire avec les conventions de la classe Context de 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 à tous les types décimaux mappés à la base de données.

3
Gecko IT

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 Complètement recommandé.

1
Jaider

En utilisant

System.ComponentModel.DataAnnotations;

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

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

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, ils 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);
            }
        }
    }
0
Mark007

@ Mark007, j'ai changé les critères de sélection de type à utiliser pour les propriétés DbSet <> du DbContext. Je pense que cela est plus sûr car il arrive parfois que des classes dans l’espace de noms donné ne doivent pas faire partie de la définition du modèle ou qu’elles le soient, mais ne soient pas des entités. Ou bien, vos entités pourraient résider dans des espaces de noms ou des assemblages distincts et être rassemblées dans un contexte unique.

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

Notez que OnModelCreating délègue des délégués à 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;
            }
        }
    }
0
Eniola