如何在Entity Framework添加实体时截断Decimal并存入SQL Server?
问题描述
我尝试将从API获取的Decimal值截断后直接存入SQL Server的Recipes表,表结构和Entity Framework添加代码如下:
数据库表结构
CREATE TABLE [dbo].[Recipes] ( [Id] INT PRIMARY KEY NOT NULL, [Uri] NVARCHAR(MAX) NULL, [Label] NVARCHAR(MAX) NULL, [Image] NVARCHAR(MAX) NULL, [Source] NVARCHAR(MAX) NULL, [Url] NVARCHAR(MAX) NULL, [ShareAs] NVARCHAR(MAX) NULL, [Yield] DECIMAL(38) NULL, [Calories] DECIMAL(38) NULL, [TotalWeight] DECIMAL(38) NULL, [TotalTime] DECIMAL(38) NULL, );
EF添加实体代码
myContext.Recipes.Add(new() { Id = IdForRecipesManually, Calories = hit.recipe.calories, ShareAs = hit.recipe.shareAs, Uri = hit.recipe.uri, Url = hit.recipe.url, Image = hit.recipe.image, Label = hit.recipe.label, Yield = hit.recipe.yield, Source = hit.recipe.source, TotalTime = hit.recipe.totalTime, TotalWeight = Decimal.Truncate(hit.recipe.totalWeight) });
遇到两个问题:
- 直接在实体初始化中调用
Decimal.Truncate截断TotalWeight不生效,但把截断逻辑放到外部临时变量(var temp = Decimal.Truncate(hit.recipe.totalWeight);)再赋值就正常,想问能不能在EF的Add操作中直接实现截断? - 最初截断Decimal是因为遇到「argument exception parameter value is out of range decimal」错误,把SQL Server和EF中的Decimal类型改为
decimal(38,30)后,部分对象正常存入,但又出现「Conversion overflow」错误。
解决方案
一、在EF Add操作中实现Decimal截断的方法
1. 临时变量中转(最简单直接)
既然外部变量截断有效,可先将截断后的值存入临时变量,再赋值给实体属性,避免EF表达式树解析可能带来的问题:
var truncatedTotalWeight = Decimal.Truncate(hit.recipe.totalWeight); myContext.Recipes.Add(new() { // 其他属性赋值... TotalWeight = truncatedTotalWeight });
2. 实体属性Setter自动截断
在Recipe实体类中重写TotalWeight的Setter,让所有赋值操作自动触发截断逻辑,一劳永逸:
public class Recipe { // 其他属性... private decimal? _totalWeight; public decimal? TotalWeight { get => _totalWeight; set => _totalWeight = value.HasValue ? Decimal.Truncate(value.Value) : null; } }
之后不管是在Add还是其他操作中赋值TotalWeight,都会自动截断。
3. EF ValueConverter全局配置
在DbContext的OnModelCreating方法中配置值转换器,让EF在映射数据库时自动处理截断:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Recipe>() .Property(r => r.TotalWeight) .HasConversion( // 写入数据库时截断 v => v.HasValue ? Decimal.Truncate(v.Value) : v, // 读取数据库时直接返回 v => v ); }
这种方式不需要修改实体类,也不用每次手动处理截断逻辑。
二、解决Decimal范围/溢出错误
1. 理解SQL Server Decimal精度规则
SQL Server中decimal(p,s)的p是总位数(整数+小数),s是小数位数。比如你改成的decimal(38,30),意味着整数部分最多只能有38-30=8位。如果API返回的Decimal值整数部分超过8位,就会触发「Conversion overflow」错误。
2. 调整数据库Decimal精度
根据业务场景调整精度,比如总重量一般不会有超大整数位,可改为decimal(18,2)(总位数18,小数位2,整数位最多16位),完全满足日常需求:
ALTER TABLE [dbo].[Recipes] ALTER COLUMN [TotalWeight] DECIMAL(18,2) NULL;
同时在EF实体类中确保属性类型匹配,或者通过Fluent API配置:
modelBuilder.Entity<Recipe>() .Property(r => r.TotalWeight) .HasPrecision(18, 2);
3. 截断时额外校验范围
如果必须保留decimal(38,30)的精度,可在截断后添加范围校验,避免溢出:
var rawWeight = hit.recipe.totalWeight; var truncatedWeight = Decimal.Truncate(rawWeight); // 检查整数部分是否超过8位(38-30) if (truncatedWeight >= Math.Pow(10, 8) || truncatedWeight <= -Math.Pow(10, 8)) { // 处理超出范围的情况,比如设为null、抛出业务异常或取最大值 truncatedWeight = null; } myContext.Recipes.Add(new() { TotalWeight = truncatedWeight });
内容的提问来源于stack exchange,提问作者dev-113
相关产品推荐
相关产品推荐

