在LINQ中转换OrderBy Case语句
本文关键字:Case 语句 OrderBy 转换 LINQ | 更新日期: 2023-09-27 18:25:23
我正在尝试将以下SQL从Oracle转换为Linq-to-Entity查询。
ORDER by
case when(e.prev_co = 'ABC' and(nvl(co_seniority, '1-jan-2099') < to_date('10-apr-2001')))
then '2001-04-01'
else to_char(nvl(co_seniority, '1-jan-2099'), 'YYYY-MM-DD') end,
nvl(co_seniority, '1-jan-2099'),
birth_dt
我希望我可以使用一个函数来传递一些参数,并让它返回正确的日期。我尝试创建一个名为SortDate的新属性,然后在页面上创建了一个函数,该函数将接收参数并返回正确的日期,但没有成功。我得到一个异常,说"LINQ to Entities不识别GetSortDate方法"。
Model
SortByDate = GetSortDate(e.PREV_CO, e.CO_SENIORITY),
Function
public static DateTime GetSortDate(string PreviousCo, DateTime? CoSeniorityDate)
{
//set variable to default date
DateTime sortDate = System.DateTime.Parse("2001-04-01");
//set default date if NULL
if (CoSeniorityDate == null)
{
CoSeniorityDate = System.DateTime.Parse("2099-01-01");
}
if (PreviousCo == "ABC" && (CoSeniorityDate < System.DateTime.Parse("2001-04-10")))
{
sortDate = System.DateTime.Parse("2001-04-01");
}
else
{
sortDate = System.DateTime.Parse(CoSeniorityDate.ToString());
}
return sortDate;
}
这是我完整的EF
using (DataContext db = new DataContext())
{
db.Configuration.AutoDetectChangesEnabled = false; //no changes needed so turn off for performance.
var workStatus = new string[] { "1", "3" };
var company = new string[] { "EX", "SM" };
var eventReason = new string[] { "21", "22", "23" };
data = (from e in db.EMPLOYEE
where workStatus.Contains(e.WORKSTAT)
&& company.Contains(e.CO.Substring(0, 2))
&& ((e.EVENT_TYP != "35") || (e.EVENT_TYP == "35" && !eventReason.Contains(e.EVENT_RSN)))
select new Employee
{
Co = e.CO,
CityCode = e.CITY_CODE,
EmployeeNumber = e.EMP,
LastName = e.LAST_NAME,
FirstName = e.FIRST_NAME,
Position = e.ABV_POSITION_TITLE,
EmploymentType = e.PART_TIME_IND == "X" ? "PT" : "FT",
SeniorityDate = e.CO_SENIORITY == null ? DateTime.MaxValue : e.CO_SENIORITY,
BirthDate = e.BIRTH_DT,
SortByDate = GetSortDate(e.PREV_CO, e.CO_SENIORITY),
PreviousCo = e.PREV_CO
}).OrderBy(o => o.SortByDate).ThenBy(o => o.SeniorityDate).ThenBy(o => o.BirthDate).ToList();
}
有人对我如何转换此OrderBy有什么建议吗?
更新的问题
目前,我通过使用如@Markus所示的辅助SELECT使查询正常工作。第一个查询只提取数据,然后提取方法的所有格式和调用,以获得正确的SortByDate。
然而,我的经理更喜欢在DB中进行排序,而不是在内存中进行排序。他放弃了这个,因为很少有人打电话给这个资历表,而且每个月只有一次。
出于学习目的,我想看看我是否可以让DB完成下面@IvanStoev显示的所有排序。所以,回到那个路线,我无法让OrderBy完全像它应该的那样工作。
如果你查看原始SQL,我试图转换它,首先查看此人以前是否有"ABC"公司,如果有,然后查看SeniorityDate(如果为NULL,则设置默认日期),并将其与收购日期进行比较。如果不满足这个条件,那么只需使用他们的SeniorityDate(如果为NULL,则设置为默认值)。狡猾……我知道。
使用LinqPad中建议的OrderBy,然后查看返回的SQL,我可以看到OrderBy的第一部分查看前一家公司,然后查看SeniorityDate并设置一个值。然后查看收购日期。我需要以某种方式对一些条件进行分组,首先看看我不知道这是可能的。
SELECT t0.ABV_POSITION_TITLE, t0.BIRTH_DT, t0.CITY_CODE, t0.CO, t0.CO_SENIORITY, t0.EMP, t0.FIRST_NAME, t0.LAST_NAME, t0.PART_TIME_IND, t0.PREV_CO, t0.WORKSTAT
FROM SAP_EMPLOYEE t0
WHERE ((((t0.WORKSTAT IN (:p0, :p1) AND (t0.PERS_SUB_AREA = :p2)) AND SUBSTR(t0.CO, 0 + 1, 2) IN (:p3, :p4)) AND (t0.CO <> :p5)) AND ((t0.EVENT_TYP <> :p6) OR ((t0.EVENT_TYP = :p6) AND NOT t0.EVENT_RSN IN (:p7, :p8, :p9))))
ORDER BY (CASE WHEN ((t0.PREV_CO = :p10) AND (t0.CO_SENIORITY IS NULL)) THEN :p11 WHEN (t0.CO_SENIORITY < :p12) THEN :p13 ELSE COALESCE(t0.CO_SENIORITY, :p11) END), COALESCE(t0.CO_SENIORITY, :p11), t0.BIRTH_DT
-- p0 = [1]
-- p1 = [3]
-- p2 = [200A]
-- p3 = [EX]
-- p4 = [SM]
-- p5 = [EXGS]
-- p6 = [35]
-- p7 = [21]
-- p8 = [22]
-- p9 = [23]
-- p10 = [ABC]
-- p11 = [1/1/2099 12:00:00 AM]
-- p12 = [4/10/2001 12:00:00 AM]
-- p13 = [4/1/2001 12:00:00 AM]
我需要想出一些类似的东西
ORDER BY (CASE WHEN ((t0.PREV_CO = :p10) AND (COALESCE(t0.CO_SENIORITY, :p11) < :p12) THEN :p13 ELSE COALESCE(t0.CO_SENIORITY, :p11) END)
这是我在LinqPad中使用的代码。
void Main()
{
var workStatus = new string[] { "1", "3" };
var company = new string[] { "EX", "SM" };
var eventReason = new string[] { "21", "22", "23" };
var baseDate = new DateTime(2001, 4, 10); // 10-apr-2001
var minDate = new DateTime(2001, 4, 1); // 1-apr-2001
var abcDate = new DateTime(2001, 4, 10); // 10-apr-2001
var maxDate = new DateTime(2099, 1, 1); // 1-jan-2099
var data = (from e in SAP_EMPLOYEE
where workStatus.Contains(e.WORKSTAT)
&& e.PERS_SUB_AREA == "200A"
&& company.Contains(e.CO.Substring(0, 2))
&& e.CO != "EXGS"
&& ((e.EVENT_TYP != "35") || (e.EVENT_TYP == "35" && !eventReason.Contains(e.EVENT_RSN)))
orderby e.PREV_CO == "ABC" && e.CO_SENIORITY == null ? maxDate : e.CO_SENIORITY < abcDate ? minDate : e.CO_SENIORITY ?? maxDate,
e.CO_SENIORITY ?? maxDate,
e.BIRTH_DT
select new Employee
{
Co = e.CO,
CityCode = e.CITY_CODE,
EmployeeNumber = e.EMP,
LastName = e.LAST_NAME,
FirstName = e.FIRST_NAME,
Position = e.ABV_POSITION_TITLE,
EmploymentType = e.PART_TIME_IND == "X" ? "PT" : "FT",
SeniorityDate = e.CO_SENIORITY == null ? maxDate :
e.PREV_CO == "ABC" && e.CO_SENIORITY < twaDate ? maxDate : e.CO_SENIORITY,
LOA = e.WORKSTAT == "1" ? "LOA" : "",
ABC = e.PREV_CO == "ABC" ? "ABC" : "",
BirthDate = e.BIRTH_DT,
PreviousCo = e.PREV_CO
}).ToList();
data.Dump();
}
异常的原因是实体框架在执行SQL查询时生成该查询。在您的情况下,在最后调用ToList()
时会发生这种情况。为了生成SQL查询,实体框架分析查询并将其转换为SQL。由于实体框架不知道您的函数,因此无法为其生成SQL语句
为了解决这个问题,您需要首先执行查询,并在内存中对结果进行排序操作。为了限制传输到客户端的数据量,您应该执行包含where
子句的查询,并告诉EF您感兴趣的字段,以避免SELECT * FROM ...
包含表的所有字段。
您可以大致如下更改查询:
data = (from e in db.EMPLOYEE
where workStatus.Contains(e.WORKSTAT)
&& company.Contains(e.CO.Substring(0, 2))
&& ((e.EVENT_TYP != "35") || (e.EVENT_TYP == "35" && !eventReason.Contains(e.EVENT_RSN)))
select new ()
{
Co = e.CO,
CityCode = e.CITY_CODE,
EmployeeNumber = e.EMP,
LastName = e.LAST_NAME,
FirstName = e.FIRST_NAME,
Position = e.ABV_POSITION_TITLE,
EmploymentType = e.PART_TIME_IND == "X" ? "PT" : "FT",
SeniorityDate = e.CO_SENIORITY,
BirthDate = e.BIRTH_DT,
PreviousCo = e.PREV_CO
}).ToList().Select(x => new Employee()
{
Co = x.Co,
CityCode = x.CityCode,
EmployeeNumber = x.EmployeeNumber,
LastName = x.LastName,
FirstName = x.FirstName,
Position = x.Position,
EmploymentType = x.EmploymentType,
SeniorityDate = x.SeniorityDate ?? DateTime.MaxValue,
BirthDate = x.BirthDate,
SortByDate = GetSortDate(x.PreviousCo, x.SeniorityDate),
PreviousCo = x.PreviousCo
}).OrderBy(o => o.SortByDate)
.ThenBy(o => o.SeniorityDate)
.ThenBy(o => o.BirthDate).ToList();
此查询首先按照where
子句中指定的方式过滤数据,然后使用匿名类型仅检索相关字段,包括后来用作GetSortDate
方法输入的字段及其原始值。在第一个ToList
之后,结果将出现在内存中,您可以首先添加一个新的选择,该选择将创建包括排序日期的Employee
对象。然后按照排序日期等对这些对象进行排序
GetSortDate
方法的一个小提示:将DateTime常量指定为要解析的字符串不是一个好主意,因为解析取决于线程的区域性(如果没有指定区域性)。
// Culture dependent
sortDate = System.DateTime.Parse("2001-04-01");
// Better
sortDate = new DateTime(2001, 04, 01);
正如您已经注意到的(困难的方式),在LINQ to Entities查询中,您不能使用像LINQ to Objects中那样的本地方法。如果希望在数据库中执行整个查询,则需要仅使用支持的构造将逻辑嵌入到查询中。
话虽如此,SQL查询的等价物应该是这样的
var baseDate = new DateTime(2001, 4, 10); // 10-apr-2001
var minDate = new DateTime(2001, 4, 1); // 1-apr-2001
var maxDate = new DateTime(2099, 1, 1); // 1-jan-2099
data = (from e in db.EMPLOYEE
where workStatus.Contains(e.WORKSTAT)
&& company.Contains(e.CO.Substring(0, 2))
&& ((e.EVENT_TYP != "35") || (e.EVENT_TYP == "35" && !eventReason.Contains(e.EVENT_RSN)))
let seniorityDate = e.CO_SENIORITY ?? maxDate
let sortDate =
e.CO_SENIORITY == null ? maxDate :
e.PREV_CO == "ABC" && e.CO_SENIORITY < baseDate ? minDate :
e.CO_SENIORITY
orderby sortDate, seniorityDate, e.BIRTH_DT
select new Employee
{
Co = e.CO,
CityCode = e.CITY_CODE,
EmployeeNumber = e.EMP,
LastName = e.LAST_NAME,
FirstName = e.FIRST_NAME,
Position = e.ABV_POSITION_TITLE,
EmploymentType = e.PART_TIME_IND == "X" ? "PT" : "FT",
SeniorityDate = e.CO_SENIORITY,
BirthDate = e.BIRTH_DT,
PreviousCo = e.PREV_CO
}).ToList();
更新:出于学习目的,我使用let
子句更新了答案。
现在是关于具体的订购。我本可以完全按照你的方式写"排序日期"部分,但我相信我的方式更好。为什么?
这是我在伪代码中对"SortDate"的解释
if (CoSeniorityDate == null)
SortDate = #2099-01-01#
else if (PreviousCo == "ABC" && CoSeniorityDate < #2001-04-10#)
SortDate = #2001-04-01#
else
SortDate = CoSeniorityDate
这是你的功能
if (CoSeniorityDate == null) CoSeniorityDate = #2099-01-01#
if (PreviousCo == "ABC" && CoSeniorityDate < #2001-04-10#)
SortDate = #2001-04-01#
else
SortDate = CoSeniorityDate
设CCD_ 10。然后,根据您的逻辑,让我们替换CoSeniorityDate = #2099-01-01#
:
if (PreviousCo == "ABC" && #2099-01-01# < #2001-04-10#)
SortDate = #2001-04-01#
else
SortDate = #2099-01-01#
由于#2099-01-01# < #2001-04-10#
总是错误,因此它变成了简单的
SortDate = #2099-01-01#
即与我的标准的第一部分完全相同。在其他部分中,我们已经知道CoSeniorityDate
不是null,并且可以检查其他条件。
不管怎样,按照你的方式做就像这个
let sortDate = e.PREV_CO == "ABC" && seniorityDate < baseDate ? minDate : seniorityDate