如何从数据库获取波斯历格式DateTime并解决LINQ翻译报错
需求背景
需要从数据库中获取波斯历(太阳历/Shamsi Date)格式的DateTime,与当前月份对比后统计当月收入。已设置应用文化信息:
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("fa-AF"); System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("fa-AF");
编写了自定义扩展方法ToPersianDate用于转换日期:
public static class DateConverter { #region Static Methods public static DateTime ToPersianDate(this DateTime? dt) { try { DateTime dateTime = dt ?? DateTime.Now; PersianCalendar persianCalendar = new PersianCalendar(); string year = persianCalendar.GetYear(dateTime).ToString(); string month = persianCalendar.GetMonth(dateTime).ToString() .PadLeft(2, '0'); string day = persianCalendar.GetDayOfMonth(dateTime).ToString() .PadLeft(2, '0'); string hour = dateTime.Hour.ToString().PadLeft(2, '0'); string minute = dateTime.Minute.ToString().PadLeft(2, '0'); string second = dateTime.Second.ToString().PadLeft(2, '0'); return DateTime.Parse(String.Format("{0}/{1}/{2} {3}:{4}:{5}", year, month, day, hour, minute, second)); } catch { return DateTime.Now; } } #endregion }
问题重现
使用以下LINQ查询统计当月收入时触发错误:
long mntRevenue = (long)db.StudentFees .Where(f => DateConverter.ToPersianDate(f.Date).Month == DateTime.Now.Month).Sum(s => s.Pay);
错误信息:
System.InvalidOperationException: '无法翻译LINQ表达式“DbSet().Where(s => (DateTime?)s.Date.ToPersianDate().Month == DateTime.Now.Month)”。附加信息:方法“SchoolViewModel.ViewModels.DateConverter.ToPersianDate”的翻译失败。请将查询重写为可翻译的形式,或通过插入“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”显式切换到客户端评估。'
错误原因
EF Core无法将自定义的ToPersianDate方法转换为对应的SQL语句,因为它无法识别自定义逻辑,导致查询无法在数据库服务器端执行。
解决方案
方案一:客户端评估(适用于小数据量)
先将数据加载到内存,再用自定义方法过滤。优点是实现简单,缺点是大数据量时会占用较多内存:
long mntRevenue = (long)db.StudentFees .AsEnumerable() // 切换到客户端处理 .Where(f => DateConverter.ToPersianDate(f.Date).Month == DateTime.Now.Month) .Sum(s => s.Pay);
方案二:服务器端直接计算波斯历年月(推荐)
直接在LINQ中使用PersianCalendar的原生方法,让EF Core能转换为SQL执行:
// 先获取当前波斯历的年和月 var persianCal = new PersianCalendar(); DateTime now = DateTime.Now; int currentPersianYear = persianCal.GetYear(now); int currentPersianMonth = persianCal.GetMonth(now); // 执行查询 long mntRevenue = (long)db.StudentFees .Where(f => persianCal.GetYear(f.Date) == currentPersianYear && persianCal.GetMonth(f.Date) == currentPersianMonth) .Sum(s => s.Pay);
注:EF Core 5及以上版本支持PersianCalendar的核心方法转换为SQL,旧版本可能需要额外处理。
方案三:映射数据库自定义函数(适用于复杂复用场景)
将波斯历转换逻辑封装为数据库函数,再在EF Core中注册映射:
- 在数据库中创建自定义函数(以SQL Server为例):
CREATE FUNCTION dbo.GetPersianMonth(@date DATETIME) RETURNS INT AS BEGIN -- 实现波斯历月份计算的SQL逻辑 DECLARE @persianMonth INT -- 此处根据波斯历算法编写转换逻辑 RETURN @persianMonth END
- 在EF Core上下文注册函数:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDbFunction(() => DateConverter.GetPersianMonth(default(DateTime))); }
- 编写对应静态方法并在LINQ中使用:
public static class DateConverter { public static int GetPersianMonth(DateTime date) { var persianCal = new PersianCalendar(); return persianCal.GetMonth(date); } } // 查询代码 long mntRevenue = (long)db.StudentFees .Where(f => DateConverter.GetPersianMonth(f.Date) == currentPersianMonth) .Sum(s => s.Pay);
内容的提问来源于stack exchange,提问作者Omeri

