使用波斯日历的 Linq to entities 过滤查询

Linq to entities filtering query using persian calendar

我想使用波斯历月份过滤我的查询。

我的原始查询:

var getNews =
    from gn
    in db.NewsTables
    where gn.Show
    select gn;

波斯历对象:

PersianCalendar pc = new PersianCalendar();

并像这样过滤原始查询:

getNews = getNews.Where(nm => pc.GetMonth(nm.InsertionDate) == 9 && pc.GetYear(nm.InsertionDate) == pc.GetYear(DateTime.Now));

例外情况:

base {System.SystemException} = {"LINQ to Entities does not recognize the method 'Int32 GetMonth(System.DateTime)' method, and this method cannot be translated into a store expression."}

有人知道吗?

简单(但可能不好*)的答案是:

getNews = getNews.ToList();
getNews = getNews.Where(nm => pc.GetMonth(nm.InsertionDate) == 9 && pc.GetYear(nm.InsertionDate) == pc.GetYear(DateTime.Now));

正如Jcl所说,错误的原因是LINQ to Entities只支持有限数量的函数(可以翻译成底层提供者可以支持的表达式)。您的 pc.GetMonth 函数不是它能理解的函数。

通过执行 ToList(),您将强制在服务器上执行查询并 return 结果。现在 getNews 只是本地内存中的一个集合,任何进一步的 LINQ 操作都将使用 LINQ to Objects 完成,没有上述限制。

*如果过滤前数据集很小,这只是一个很好的解决方案,因为getNews查询的整个结果集必须是returned 并存储在内存中,然后才能对其进行过滤。如果它是 20 行,我不会担心。如果是2000,你应该想办法重构。

Linq to Entities 仅支持 canonical functions 查询。所以很遗憾,这不受支持。

如果数据集不是太大,你可以完全检索它(例如使用ToList()),然后使用Linq to Objects过滤它,只是要考虑到整个数据集必须被检索(如果数据库服务器不在本地,则被传输),这可能会对内存和性能产生影响。

或者,如果数据库记录存储在公历日期(如评论中所述),您可以事先进行转换...按照您的代码:

var gregorianDate = new DateTime(DateTime.Now.Year, 9, 1, new PersianCalendar());
getNews = getNews.Where(nm => nm.InsertionDate.Month == gregorianDate.Month  
                           && nm.InsertionDate.Year == gregorianDate.Year);

实体将您的代码翻译成 sql 语言,在这种情况下它无法将您的函数 GetMonth 翻译成 SQL。快速解决方法是

var getNews =
    (from gn
    in db.NewsTables
    where gn.Show
    select gn).ToList();

但这意味着您将从内存中的数据库中获取所有记录并在 select 之后应用过滤器。

如果您向我们展示您的功能代码,我们可能会帮助您直接在数据库中进行过滤。

有两种不同的读法:

  1. 您想获取一个月内的所有记录
  2. 存储的日期与机器日期的日历不同

第一部分可以通过将查询转换为月初和月末之间的范围查询来修复:

var startDate=DateTime.Today.AddDays(1-DateTime.Today.Day);
var endDate=startDate.AddMonths(1);

var getNews = from gn in db.NewsTables
              where gn.Show 
                && gn.InsertionDate >=startDate
                && gn.InsertionDate <endDate
              select gn;

DateTime.TodayDateTime.Now 总是 return 公历日期所以我假设搜索日期是由用户输入的或通过其他方式作为波斯日期输入的,数据存储在公历。

在这种情况下,您可以使用PersianCalendar方法来计算范围。 DateTime 值始终采用公历,这意味着您不需要 执行任何操作即可转换为公历:

var calendar =  new PersianCalendar();
var startDate= calendar.AddDays(searchDate,1- calendar.GetDayOfMonth(searchDate));
var endDate=calendar.AddMonths(startDate,1);

var getNews = from gn in db.NewsTables
              where gn.Show 
                && gn.InsertionDate >=startDate
                && gn.InsertionDate <endDate
              select gn;

我建议您使用像 Jon Skeet 的 Noda Time 这样的库,它可以识别日历并且不会假设所有当地时间都是公历