ฉันกำลังทดลองกับวิธีแรกของรหัสนี้ แต่ตอนนี้ฉันรู้แล้วว่าคุณสมบัติของ System.Decimal ได้รับการแมปกับคอลัมน์ sql ของทศนิยมประเภท (18, 0)
ฉันจะตั้งค่าความแม่นยำของคอลัมน์ฐานข้อมูลได้อย่างไร
ฉันกำลังทดลองกับวิธีแรกของรหัสนี้ แต่ตอนนี้ฉันรู้แล้วว่าคุณสมบัติของ System.Decimal ได้รับการแมปกับคอลัมน์ sql ของทศนิยมประเภท (18, 0)
ฉันจะตั้งค่าความแม่นยำของคอลัมน์ฐานข้อมูลได้อย่างไร
คำตอบ:
คำตอบจาก 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);
}
}
System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder
base.OnModelCreating(modelBuilder);
ผมสังเกตเห็นว่าคุณไม่เคยเรียกว่า นั่นเป็นเจตนาหรือเป็นเพียงเหยื่อของการพิมพ์รหัสออนไลน์แทนที่จะเป็น IDE หรือไม่?
หากคุณต้องการตั้งค่าความแม่นยำสำหรับทุกคน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);
}
}
ฉันมีความสุขในการสร้างแอตทริบิวต์ที่กำหนดเองสำหรับสิ่งนี้:
[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
MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });
เพื่อรับโอเวอร์โหลดที่ถูกต้อง ดูเหมือนว่าจะทำงานได้ไกล
การใช้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());
}
Precision
ฉันแนะนำให้ตั้งค่าขอบเขตบนเป็น 28 (ดังนั้น> 28
ในสภาพของคุณ) ตามเอกสารของ MSDN System.Decimal
สามารถแสดงความแม่นยำได้สูงสุด 28-29 หลักเท่านั้น ( msdn.microsoft.com/en-us/library/364x0z75.aspx ) นอกจากนี้แอตทริบิวต์ยังประกาศScale
ว่าbyte
ซึ่งหมายความว่าคุณattribute.Scale < 0
ไม่จำเป็นต้องมีเงื่อนไขเบื้องต้น
System.Decimal
ไม่ได้ ดังนั้นจึงไม่มีเหตุผลที่จะตั้งเงื่อนไขเบื้องต้นบนให้เป็นอะไรที่มากกว่า 28; System.Decimal
ไม่สามารถแสดงตัวเลขที่มีขนาดใหญ่อย่างชัดเจน นอกจากนี้โปรดทราบว่าคุณลักษณะนี้มีประโยชน์สำหรับผู้ให้บริการข้อมูลอื่นที่ไม่ใช่ SQL Server ตัวอย่างเช่นnumeric
ชนิดของ PostgreSQL รองรับความแม่นยำสูงสุด 131072 หลัก
เห็นได้ชัดว่าคุณสามารถแทนที่เมธอด 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 เมื่อคุณแทนที่เมธอดแม้ว่าการใช้งานพื้นฐานจะไม่ทำอะไรเลย
อัปเดต: บทความนี้มีประโยชน์มาก
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.
Entity Framework Ver 6 (อัลฟา RC1) มีสิ่งที่เรียกว่าการประชุมที่กำหนดเอง วิธีตั้งค่าความแม่นยำทศนิยม:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<decimal>().Configure(config => config.HasPrecision(18, 4));
}
อ้างอิง:
[Column(TypeName = "decimal(18,2)")]
นี้จะทำงานร่วมกับรหัส EF หลักโยกย้ายครั้งแรกตามที่อธิบายไว้ที่นี่
The store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
บรรทัดรหัสนี้อาจเป็นวิธีที่ง่ายกว่าในการทำสิ่งเดียวกันให้สำเร็จ:
public class ProductConfiguration : EntityTypeConfiguration<Product>
{
public ProductConfiguration()
{
this.Property(m => m.Price).HasPrecision(10, 2);
}
}
- สำหรับ 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
ใน 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);
});
คุณสามารถบอกให้ 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));
ฉันคิดว่ามันแปลกที่ไม่ได้ถูกแทนที่โดยอัตโนมัติ
การใช้
System.ComponentModel.DataAnnotations;
คุณสามารถใส่แอตทริบิวต์นั้นในแบบจำลองของคุณ:
[DataType("decimal(18,5)")]
คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับ MSDN - แง่มุมของรูปแบบข้อมูลเอนทิตี http://msdn.microsoft.com/en-us/library/ee382834.aspx แนะนำแบบเต็ม
จริงสำหรับ 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)");
แอตทริบิวต์ที่กำหนดเองของ 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);
}
}
}
@ 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;
}
}
}
[Column(TypeName = "decimal(18,4)")]
ททริบิวสำหรับคุณสมบัติทศนิยมของคุณ