zoukankan      html  css  js  c++  java
  • 解决 Entity Framework 6.0 decimal 类型精度问题

    Ø  前言

    本文主要解决 EF 中对于 MSSQL 数据库的 decimal 类型经度问题,经实验该问题仅在 CodeFirst 模式的情况下发生,话不多说直接看代码。

     

    1.   假设我们有一张 Customer 数据表,主要探究:LongitudeLatitudeLonLatSum 这三个字段。

    1)   结构如下:

    CREATE TABLE dbo.Customer

    (

        Id int NOT NULL,                            --客户Id

        Name nvarchar(25) NOT NULL,                 --客户名称

        Address nvarchar(25) NOT NULL,              --地址

        Longitude real NOT NULL,                    --经度

        Latitude float NOT NULL,                    --纬度

        LonLatSum decimal(18,7) NOT NULL,           --经纬度之和

        CONSTRAINT PK_Customer_Id PRIMARY KEY CLUSTERED

        (

            Id ASC

        ) ON [PRIMARY]

    ) ON [PRIMARY];

    2)   数据如下:

    clip_image001[4]

     

    2.   首先,我们先使用 DB Frirst 的方式对数据更新(更新 Id 1的数据)

    1)   C# 代码如下:

    using (MyTestingEntities context = new MyTestingEntities())

    {

        Customer entity = context.Customers.Attach(new Customer() { Id = 1 });

        entity.Longitude = 123.1256789f;    //123.125679

        entity.Latitude = 456.1295678d;     //456.1295678

        entity.LonLatSum = (decimal)(entity.Longitude + entity.Latitude);   //579.255246816113M

        context.Configuration.ValidateOnSaveEnabled = false;

        result = context.SaveChanges() > 0;

    }

     

    2)   生成SQL

    exec sp_executesql N'UPDATE [dbo].[Customer]

    SET [Longitude] = @0, [Latitude] = @1, [LonLatSum] = @2

    WHERE ([Id] = @3)

    ',N'@0 real,@1 float,@2 decimal(18,7),@3 int',@0=123.12567901611328,@1=456.12956780000002,@2=579.2552468,@3=1

     

    3)   执行结果:

    clip_image002[4]

     

    4)   结论:

    1.   Longitudereal 类型(对应 C# 中的 float 类型),进行了四舍五入,保留了4位小数。

    2.   Latitudefloat 类型(对应 C# 中的 double 类型),保留了7位小数。

    3.   LonLatSumdecimal 类型(对应 C# 中的 decimal 类型),也保留了7位小数。

    4.   OK 这是正常的。

     

    3.   然后,我们再使用 Code Frirst 的方式对数据更新(更新 Id 2的数据)

    1)   C# 代码如下:

    using (MyTestingContext context = new MyTestingContext())

    {

        Customer entity = context.Customer.Attach(new Customer() { Id = 2 });

        entity.Longitude = 123.1256789f;    //123.125679

        entity.Latitude = 456.1295678d;     //456.1295678

        entity.LonLatSum = (decimal)(entity.Longitude + entity.Latitude);   //579.255246816113M

        result = context.SaveChanges() > 0;

    }

    return result;

     

    2)   生成SQL

    exec sp_executesql N'UPDATE [dbo].[Customer]

    SET [Longitude] = @0, [Latitude] = @1, [LonLatSum] = @2

    WHERE ([Id] = @3)

    ',N'@0 real,@1 float,@2 decimal(18,2),@3 int',@0=123.12567901611328,@1=456.12956780000002,@2=579.25,@3=2

     

    3)   执行结果:

    clip_image003[4]

     

    4)   结论:

    1.   Longitude:与 DB First 相同。

    2.   Latitude:与 DB First 相同。

    3.   LonLatSum:却进行了四舍五入,只保留了两位小数,这是为什么呢?

     

    4.   问题分析

    1)   DB First Code First 几乎是相同的代码,为什么 DB First 就是正常的呢,这是因为 DB First 的配置文件有这样一句:<Property Name="LonLatSum" Type="decimal" Precision="18" Scale="7" Nullable="false" />,这里明确指定了该字段精度与小数位数,生成SQL时就会按照配置去生成,例如:decimal(18,7)

    2)   Code First 并没有这样的配置,所以就采用了 decimal 的默认精确度(18)和小数位数(2位)的方式生成了,结果 SQL 的类型声明是这样:decimal(18,2)

    3)   搞清楚了问题,下面我们就来解决这个问题吧。

     

    5.   解决问题

    1)   创建一个 DecimalPrecisionAttribute 特性

    /// <summary>

    /// 用于指定 decimal 类型的精确度与小数保留位数。

    /// </summary>

    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]

    public class DecimalPrecisionAttribute : Attribute

    {

        private byte _precision;

        /// <summary>

        /// 精确度。

        /// </summary>

        public byte Precision

        {

            get { return _precision; }

            set { _precision = value; }

        }

     

        private byte _scale;

        /// <summary>

        /// 小数保留位数。

        /// </summary>

        public byte Scale

        {

            get { return _scale; }

            set { _scale = value; }

        }

     

        /// <summary>

        /// 根据指定的精确度与小数保留位数,初始化 DecimalPrecisionAttribute 的实例。

        /// </summary>

        /// <param name="precision">精确度。</param>

        /// <param name="scale">小数保留位数。</param>

        public DecimalPrecisionAttribute(byte precision, byte scale)

        {

            this.Precision = precision;

            this.Scale = scale;

        }

    }

     

    2)   再创建一个 DecimalPrecisionAttributeConvention 类(表示 DecimalPrecisionAttribute 的一种约定)

    1.   该类继承于 System.Data.Entity.ModelConfiguration.Conventions.PrimitivePropertyAttributeConfigurationConvention 类。

    2.   并实现 Apply 抽象方法,通俗点说:该方法在生成 SQL”时被调用,对于打了 DecimalPrecisionAttribute 标记的实体属性,精度将根据 configuration.HasPrecision() 方法中的设置去生成。

    /// <summary>

    /// 表示 DecimalPrecisionAttribute 的一种约定。

    /// </summary>

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

        }

    }

     

    3)   在数据上下文的 OnModelCreating() 方法将该 DecimalPrecisionAttributeConvention(约定)加入数据约定集合中。

    protected override void OnModelCreating(DbModelBuilder modelBuilder)

    {

        base.OnModelCreating(modelBuilder);

        modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());

    }

     

    4)   最后一步,将需要设置小数位数的属性打上 DecimalPrecision 标记,例如:

    [DecimalPrecision(18, 7)]

    public decimal LonLatSum { get; set; }

     

    5)   好了之后再次运行代码,生成 SQL 如下:

    exec sp_executesql N'UPDATE [dbo].[Customer]

    SET [Longitude] = @0, [Latitude] = @1, [LonLatSum] = @2

    WHERE ([Id] = @3)

    ',N'@0 real,@1 float,@2 decimal(18,7),@3 int',@0=123.12567901611328,@1=456.12956780000002,@2=579.2552468,@3=2

     

    6)   结果如下:

    clip_image004[4]

     

    7)   OK,这样就与 DB First 生成的 SQL 没什么区别了。

  • 相关阅读:
    DOS命令备忘
    JS面向对象总结
    java 内部类如何访问外部类的对象
    java注解的学习
    VisualVM远程连接服务器设置方法
    oracle 多表插入
    jvm对类的加载顺序测试
    oracle性能学习中总结
    java 权限修饰符的总结
    oracle多单号的说明
  • 原文地址:https://www.cnblogs.com/abeam/p/8591875.html
Copyright © 2011-2022 走看看