ความแม่นยำและมาตราส่วนทศนิยมใน EF Code First


230

ฉันกำลังทดลองกับวิธีแรกของรหัสนี้ แต่ตอนนี้ฉันรู้แล้วว่าคุณสมบัติของ System.Decimal ได้รับการแมปกับคอลัมน์ sql ของทศนิยมประเภท (18, 0)

ฉันจะตั้งค่าความแม่นยำของคอลัมน์ฐานข้อมูลได้อย่างไร


11
วิธีหนึ่งคือการใช้แอ[Column(TypeName = "decimal(18,4)")]ททริบิวสำหรับคุณสมบัติทศนิยมของคุณ
S.Serpooshan

[คอลัมน์ (TypeName = "ทศนิยม (18,4)")] ใช้งานได้ดีมาก !!!
Brian Rice

คำตอบ:


257

คำตอบจาก Dave Van den Eynde นั้นล้าสมัยแล้ว มีการเปลี่ยนแปลงที่สำคัญ 2 อย่างจาก EF 4.1 เป็นต้นไปคลาสModelBuilderคือDbModelBuilderและตอนนี้มี DecimalPropertyConfiguration.HasPrecision ซึ่งมีลายเซ็นของ:

public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )

โดยที่ความแม่นยำคือจำนวนหลักทั้งหมดที่ db จะจัดเก็บโดยไม่คำนึงถึงจุดทศนิยมที่ตกลงมาและสเกลเป็นจำนวนตำแหน่งทศนิยมที่จะจัดเก็บ

ดังนั้นจึงไม่จำเป็นต้องวนซ้ำตามคุณสมบัติที่แสดง แต่สามารถเรียกได้จาก

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

สำหรับใครก็ตามที่กำลังมีปัญหากับ DbModelBuilder ลองSystem.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder
Lloyd Powell

1
base.OnModelCreating(modelBuilder);ผมสังเกตเห็นว่าคุณไม่เคยเรียกว่า นั่นเป็นเจตนาหรือเป็นเพียงเหยื่อของการพิมพ์รหัสออนไลน์แทนที่จะเป็น IDE หรือไม่?
BenSwayne

1
@ BenSwayne ขอบคุณสำหรับจุดนี่คือการละเว้นของฉันไม่ได้ตั้งใจอะไร ฉันจะแก้ไขคำตอบ
AlexC

26
2 ข้อโต้แย้งเพื่อ HasPrecision (ความแม่นยำระดับ) มีการบันทึกไว้ไม่ดี ความแม่นยำคือจำนวนรวมของตัวเลขที่จะจัดเก็บโดยไม่คำนึงถึงจุดทศนิยมที่ตกลง scale คือจำนวนตำแหน่งทศนิยมที่จะจัดเก็บ
Chris Moschini

1
มีการกำหนดค่า EF เพื่อตั้งค่าสำหรับคุณสมบัติทศนิยมทั้งหมดในเอนทิตีทั้งหมดในที่เดียวหรือไม่? โดยทั่วไปเราใช้ (19,4) มันจะเป็นการดีถ้าคุณใช้สิ่งนี้กับคุณสมบัติทศนิยมทั้งหมดโดยอัตโนมัติดังนั้นเราจึงไม่ลืมที่จะตั้งค่าความแม่นยำของคุณสมบัติและพลาดความแม่นยำที่คาดไว้ในการคำนวณ
Mike de Klerk

89

หากคุณต้องการตั้งค่าความแม่นยำสำหรับทุกคนdecimalsใน EF6 คุณสามารถแทนที่การDecimalPropertyConventionประชุมเริ่มต้นที่ใช้ในDbModelBuilder:

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

ค่าเริ่มต้นDecimalPropertyConventionใน EF6 จะจับคู่decimalคุณสมบัติกับdecimal(18,2)คอลัมน์

หากคุณต้องการให้แต่ละคุณสมบัติมีความแม่นยำที่ระบุคุณสามารถตั้งค่าความแม่นยำสำหรับคุณสมบัติของเอนทิตีบนDbModelBuilder:

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

หรือเพิ่มEntityTypeConfiguration<>สำหรับเอนทิตีที่ระบุความแม่นยำ:

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

1
ทางออกที่ฉันชอบ ทำงานได้สมบูรณ์แบบเมื่อใช้ CodeFirst และการย้ายข้อมูล: EF ค้นหาคุณสมบัติทั้งหมดในคลาสทั้งหมดที่ใช้ "ทศนิยม" และสร้างการย้ายข้อมูลสำหรับคุณสมบัติเหล่านี้ ที่ดี!
okieh

75

ฉันมีความสุขในการสร้างแอตทริบิวต์ที่กำหนดเองสำหรับสิ่งนี้:

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

}

ใช้มันแบบนี้

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

และความมหัศจรรย์ก็เกิดขึ้นกับการสร้างแบบจำลองด้วยการสะท้อนบางอย่าง

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

ส่วนแรกคือการรับคลาสทั้งหมดในโมเดล (แอตทริบิวต์ที่กำหนดเองของฉันถูกกำหนดไว้ในชุดประกอบนั้นดังนั้นฉันจึงใช้มันเพื่อให้ได้ชุดประกอบพร้อมกับโมเดล)

foreach ตัวที่สองรับคุณสมบัติทั้งหมดในคลาสนั้นด้วยแอตทริบิวต์แบบกำหนดเองและแอตทริบิวต์เองดังนั้นฉันจึงสามารถรับข้อมูลความแม่นยำและสเกลได้

หลังจากนั้นฉันต้องโทร

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

ดังนั้นฉันเรียก modelBuilder.Entity () โดยการไตร่ตรองและเก็บไว้ในตัวแปร entityConfig จากนั้นฉันสร้าง "c => c.PROPERTY_NAME" นิพจน์แลมบ์ดา

หลังจากนั้นถ้าทศนิยมเป็นโมฆะฉันเรียก

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

วิธีการ (ฉันเรียกสิ่งนี้โดยตำแหน่งในอาเรย์มันไม่เหมาะที่ฉันรู้ความช่วยเหลือใด ๆ จะได้รับการชื่นชมมาก)

และถ้ามันไม่เป็นโมฆะฉันเรียก

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

วิธี.

มี DecimalPropertyConfiguration ฉันเรียกวิธี HasPrecision


3
ขอบคุณสำหรับสิ่งนี้. มันช่วยฉันจากการสร้างแลมบ์ดานับพัน
ฌอน

1
มันใช้งานได้ดีและสะอาดมาก! สำหรับ EF 5 ฉันเปลี่ยน System.Data.Entity.ModelConfiguration.ModelBuilder เป็น System.Data.Entity.DbModelBuilder
Colin

3
ฉันใช้MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });เพื่อรับโอเวอร์โหลดที่ถูกต้อง ดูเหมือนว่าจะทำงานได้ไกล
fscan

3
ฉันรวบรวมสิ่งนี้ไว้ในห้องสมุดและทำให้การโทรจาก DbContext ง่ายขึ้น: github.com/richardlawley/EntityFrameworkAttributeConfig (มีให้ผ่านทาง nuget)
Richard

ริชาร์ดฉันชอบความคิดของโครงการของคุณ แต่มีอะไรบ้างที่ต้องใช้ EF6 ฉันจะใช้มันถ้ามีรุ่นที่เข้ากันได้กับ EF5 เพื่อที่ฉันจะสามารถใช้กับ ODP.NET รุ่นของฉันได้
Patrick Szalapski

50

การใช้DecimalPrecisonAttributeจาก KinSlayerUY ใน EF6 คุณสามารถสร้างแบบแผนซึ่งจะจัดการคุณสมบัติของแต่ละคนที่มีคุณสมบัติ (ตรงข้ามกับการตั้งค่าDecimalPropertyConventionเหมือนในคำตอบนี้ซึ่งจะมีผลต่อคุณสมบัติทศนิยมทั้งหมด)

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

จากนั้นในDbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
}

@MichaelEdenfield ที่จริงแล้วไม่มีสิ่งใดใน EF6 ดังนั้นทำไมฉันจึงเพิ่มคำตอบสองข้ออันนี้และอีกคำตอบที่คุณอ้างถึง นี่คือแอททริบิวต์ที่คุณสามารถใส่ลงไปในคุณสมบัติทศนิยมเดียวแทนที่จะส่งผลกระทบต่อคุณสมบัติทศนิยมทั้งหมดในรูปแบบ
kjbartel

ไม่ดีไม่ได้สังเกตว่าคุณเขียนทั้งคู่: \
Michael Edenfield

1
หากคุณจะตรวจสอบขอบเขตPrecisionฉันแนะนำให้ตั้งค่าขอบเขตบนเป็น 28 (ดังนั้น> 28ในสภาพของคุณ) ตามเอกสารของ MSDN System.Decimalสามารถแสดงความแม่นยำได้สูงสุด 28-29 หลักเท่านั้น ( msdn.microsoft.com/en-us/library/364x0z75.aspx ) นอกจากนี้แอตทริบิวต์ยังประกาศScaleว่าbyteซึ่งหมายความว่าคุณattribute.Scale < 0ไม่จำเป็นต้องมีเงื่อนไขเบื้องต้น
NathanAldenSr

2
@kjbartel มันเป็นความจริงที่ผู้ให้บริการฐานข้อมูลบางรายสนับสนุน precensions มากกว่า 28; อย่างไรก็ตามตาม MSDN System.Decimalไม่ได้ ดังนั้นจึงไม่มีเหตุผลที่จะตั้งเงื่อนไขเบื้องต้นบนให้เป็นอะไรที่มากกว่า 28; System.Decimalไม่สามารถแสดงตัวเลขที่มีขนาดใหญ่อย่างชัดเจน นอกจากนี้โปรดทราบว่าคุณลักษณะนี้มีประโยชน์สำหรับผู้ให้บริการข้อมูลอื่นที่ไม่ใช่ SQL Server ตัวอย่างเช่นnumericชนิดของ PostgreSQL รองรับความแม่นยำสูงสุด 131072 หลัก
NathanAldenSr

1
@NathanAldenSr เช่นฉันกล่าวว่าฐานข้อมูลที่ใช้จุดคงทศนิยม ( MSDN ) ในขณะที่ System.Decimal เป็นจุดลอย พวกเขาแตกต่างอย่างสิ้นเชิง เช่นมีdecimal(38,9)คอลัมน์จะมีความสุขSystem.Decimal.MaxValueแต่decimal(28,9)คอลัมน์จะไม่ ไม่มีเหตุผลที่จะจำกัดความแม่นยำเพียง 28.
kjbartel

47

เห็นได้ชัดว่าคุณสามารถแทนที่เมธอด DbContextOnModelCreating () และกำหนดค่าความแม่นยำดังนี้:

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

แต่นี่เป็นรหัสที่น่าเบื่อเมื่อคุณต้องทำกับคุณสมบัติที่เกี่ยวข้องกับราคาทั้งหมดของคุณดังนั้นฉันจึงได้สิ่งนี้:

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

เป็นวิธีปฏิบัติที่ดีที่คุณเรียกใช้เมธอด base เมื่อคุณแทนที่เมธอดแม้ว่าการใช้งานพื้นฐานจะไม่ทำอะไรเลย

อัปเดต: บทความนี้มีประโยชน์มาก


10
ขอบคุณสิ่งนี้ชี้ให้ฉันไปในทิศทางที่ถูกต้อง ใน CTP5 ไวยากรณ์มีการเปลี่ยนแปลงเพื่ออนุญาตให้เพิ่มความแม่นยำและมาตราส่วนในคำสั่งเดียวกัน: modelBuilder.Entity <Product> () .Property (product => product.Price) .HasPrecision (6, 2);
Col

2
ถึงกระนั้นมันจะดีหรือไม่ถ้ามี "ค่าเริ่มต้น" ที่คุณสามารถตั้งค่าสำหรับทศนิยมทั้งหมด
Dave Van den Eynde

3
ฉันไม่คิดว่าการโทรbase.OnModelCreating(modelBuilder);เป็นสิ่งจำเป็น จากข้อมูลเมตา DbContext ใน 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: มันดี แต่ในฐานะผู้ดำเนินการฉันไม่ควรสนใจเรื่องนี้และเรียกฐานเสมอ
Dave Van den Eynde

@ Dave และ @Matt: มีความคิดเห็นว่า "สำคัญ" ในการโทรหาฐาน มันเป็นแนวปฏิบัติที่ดี แต่เมื่อแหล่งข้อมูลของ EF มีการนำไปใช้งานที่ว่างเปล่ามันจะทำให้เข้าใจผิดว่าเป็นสิ่งสำคัญ นั่นทำให้ผู้คนสงสัยว่าฐานทำอะไร ฉันอยากรู้อยากเห็นสิ่งที่สำคัญฉัน decompiled เพื่อ ef5.0 เพื่อตรวจสอบ ไม่มีอะไรที่นั่น. ดังนั้นนิสัยที่ดี
phil soady

30

Entity Framework Ver 6 (อัลฟา RC1) มีสิ่งที่เรียกว่าการประชุมที่กำหนดเอง วิธีตั้งค่าความแม่นยำทศนิยม:

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

อ้างอิง:


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

นี้จะทำงานร่วมกับรหัส EF หลักโยกย้ายครั้งแรกตามที่อธิบายไว้ที่นี่


1
หากคุณเพียงแค่เพิ่มไปยังรูปแบบของคุณคุณจะได้รับThe store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
โหด

@Savage ดูเหมือนว่าจะมีปัญหากับผู้ให้บริการฐานข้อมูลของคุณหรือรุ่นของฐานข้อมูล
Elnoor

@Elnoor Savage ถูกต้องนี่จะทำให้เกิดข้อผิดพลาดใน EF Migrations 6.x รุ่นเก่าไม่ใช่คอร์ไม่สนับสนุนการระบุความแม่นยำ / มาตราส่วนผ่านแอตทริบิวต์คอลัมน์และไม่ทำอะไรเลย (ค่าเริ่มต้นถึง 18,2) หากคุณใช้แอตทริบิวต์ DataType หากต้องการให้มันทำงานผ่าน Attribute ใน EF 6.x คุณจะต้องใช้ส่วนขยายของคุณเองกับ ModelBuilder
Chris Moschini

1
@ChrisMoschini ฉันเปลี่ยนคำตอบของฉันที่กล่าวถึง EF Core ขอบคุณ
Elnoor

14

บรรทัดรหัสนี้อาจเป็นวิธีที่ง่ายกว่าในการทำสิ่งเดียวกันให้สำเร็จ:

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

9

- สำหรับ EF CORE - ด้วยการใช้ System.ComponentModel.DataAnnotations

การใช้งาน [Column( TypeName = "decimal( ความแม่นยำ , ขนาด )")]

ความแม่นยำ = จำนวนตัวอักษรที่ใช้ทั้งหมด

สเกล = จำนวนรวมหลังจุด (สับสนง่าย)

ตัวอย่าง :

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

รายละเอียดเพิ่มเติมได้ที่นี่: https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types


3

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

คำตอบนี้ดูเหมือนจะเป็นการอัพเกรดเป็นคำตอบอื่นที่กำหนดคุณลักษณะคุณควรแก้ไขคำตอบนี้เป็นคำตอบ
Rhys Bevilaqua

3

คุณสามารถบอกให้ EF ทำสิ่งนี้ด้วยอนุสัญญาในคลาส Context ในฟังก์ชัน OnModelCreating ดังนี้:

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

สิ่งนี้ใช้ได้กับ Code First EF fyi เท่านั้นและใช้กับทศนิยมทุกประเภทที่แมปกับ db


มันก็ไม่ได้ทำงานจนมาก่อนRemove<DecimalPropertyConvention>(); Add(new DecimalPropertyConvention(18, 4));ฉันคิดว่ามันแปลกที่ไม่ได้ถูกแทนที่โดยอัตโนมัติ
Mike de Klerk

2

การใช้

System.ComponentModel.DataAnnotations;

คุณสามารถใส่แอตทริบิวต์นั้นในแบบจำลองของคุณ:

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

1
นี่คือการใช้งานที่ง่ายที่สุดสำหรับการอ่านและเรียบง่าย IMHO
ransems

11
สำหรับmsdn.microsoft.com/en-us/library/jj591583(v=vs.113).aspxคำตอบนี้ไม่ถูกต้องตามจริงแล้ว "อย่าสับสนแอตทริบิวต์ TypeName ของคอลัมน์ด้วย DataType DataAnnotation DataType เป็นคำอธิบายประกอบที่ใช้สำหรับ UI และถูกละเว้นโดย Code First"
speckledcarp

2
@ransems ฉันคิดเช่นนั้นจนกระทั่งฉันเพิ่งทดสอบและตามที่ได้กล่าวไว้ข้างต้นสิ่งนี้ใช้ไม่ได้กับ CodeFirst และไม่ย้ายไปยังฐานข้อมูล
RoLYroLLs

1

คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับ MSDN - แง่มุมของรูปแบบข้อมูลเอนทิตี http://msdn.microsoft.com/en-us/library/ee382834.aspx แนะนำแบบเต็ม


นั้นยอดเยี่ยมและทั้งหมด แต่สิ่งที่เกี่ยวข้องกับ Code-First อย่างไร
Dave Van den Eynde

มันมีประโยชน์ แต่ฉันไม่สามารถระบุแอตทริบิวต์ [แม่นยำ] สำหรับทศนิยมได้ ดังนั้นฉันจึงใช้วิธีแก้ปัญหาโดย @KinSlayerUY
โคลิน

1

จริงสำหรับ EntityFrameworkCore 3.1.3:

วิธีแก้ปัญหาบางอย่างใน 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)");

0

แอตทริบิวต์ที่กำหนดเองของ KinSlayerUY ทำงานได้ดีสำหรับฉัน แต่ฉันมีปัญหากับ ComplexTypes พวกเขาถูกแมปเป็นเอนทิตีในรหัสแอตทริบิวต์ดังนั้นจึงไม่สามารถแมปเป็น ComplexType

ฉันจึงขยายรหัสเพื่อให้สิ่งนี้:

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 ฉันได้เปลี่ยนเกณฑ์การเลือกประเภทเพื่อขี่ DbSet <> คุณสมบัติของ DbContext ฉันคิดว่าสิ่งนี้ปลอดภัยกว่าเพราะมีหลายครั้งที่คุณมีคลาสในเนมสเปซที่กำหนดซึ่งไม่ควรเป็นส่วนหนึ่งของคำจำกัดความของโมเดลหรือเป็น แต่ไม่ใช่เอนทิตี หรือเอนทิตีของคุณอาจอยู่ในเนมสเปซที่แยกต่างหากหรือแอสเซมบลีที่แยกต่างหากและถูกดึงเข้าด้วยกันเป็นบริบทเดียว

นอกจากนี้แม้ว่าไม่น่าเป็นไปได้ แต่ฉันไม่คิดว่ามันปลอดภัยที่จะพึ่งพาการเรียงลำดับคำนิยามของวิธีการดังนั้นจึงเป็นการดีกว่าที่จะดึงพวกเขาออกด้วยรายการพารามิเตอร์ (.GetTypeMethods () เป็นวิธีส่วนขยายที่ฉันสร้างขึ้นเพื่อทำงานกับกระบวนทัศน์ TypeInfo ใหม่และสามารถทำให้ลำดับชั้นของคลาสเรียบลงเมื่อมองหาวิธีการ)

โปรดทราบว่า OnModelCreating ผู้รับมอบสิทธิ์ในวิธีนี้:

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

ฉันเพิ่งรู้ว่าฉันไม่ได้จัดการกับ ComplexTypes โดยวิธีการนี้ จะแก้ไขในภายหลัง
Eniola

อย่างไรก็ตามวิธีการแก้ปัญหาที่เสนอโดยBrianนั้นเรียบง่ายสวยงามและใช้งานได้ ฉันจะไม่จัดทำแถลงการณ์เชิงหมวดหมู่ใด ๆ เกี่ยวกับประสิทธิภาพ แต่การขี่ออกไปแล้วสะท้อนให้เห็นว่า PropertyInfo แทนที่จะตามล่าคุณควรให้ประสิทธิภาพที่ดีขึ้นในรุ่นที่มีขนาดใหญ่มาก
Eniola
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.