实体框架在使用左联接DefaultIfEmpty()语法时强制内部联接

本文关键字:语法 内部 DefaultIfEmpty 框架 实体 | 更新日期: 2023-09-27 18:27:00

我正试图用Oracle EF框架(Oracle.ManagedDataAccess.EntityFramework nuget包,版本12.1.2400)创建一个相当复杂的查询,该框架使用.DefaultIfEmpty()语法创建大量左联接:

from i in dbHR.Identities
join p in dbHR.Personals
    on new {
        key1 = i.ID,
        key2 = true,
        key3 = true
    } equals new {
        key1 = p.EID_ID,
        key2 = (DbFunctions.TruncateTime(p.EFFECTIVE).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) <= 0),
        key3 = (DbFunctions.TruncateTime(p.EXPIRY).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) >= 0)
    }
    into pj    from p in pj.DefaultIfEmpty()
join s in dbHR.States on p.DSP_ID_ADDRESS equals s.ID into sj from s in sj.DefaultIfEmpty()
join e in dbHR.Employments
    on new {
            key1 = i.ID,
            key2 = true
        }
        equals new {
            key1 = e.EID_ID,
            key2 = (!e.TERMINATION_DATE.HasValue || DbFunctions.TruncateTime(e.TERMINATION_DATE.Value).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) >= 0)
        }
    into ej from e in ej.DefaultIfEmpty()
join a in dbHR.Assignments
    on new {
        key1 = e.ID,
        key2 = true
    } equals new {
        key1 = a.EEM_ID,
        key2 = ((!a.ASSIGNMENT_END_DATE.HasValue || DbFunctions.TruncateTime(a.ASSIGNMENT_END_DATE.Value).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) >= 0)
            && (DbFunctions.TruncateTime(a.ASSIGNMENT_START_DATE).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) <= 0)
            && (a.PRIME_ASSIGNMENT != null))
    }
    into aj from a in aj.DefaultIfEmpty()
join ad in dbHR.AssignmentDetails
    on
        new { key1 = a.ID, key2 = true }
    equals
        new
        {
            key1 = ad.EAS_ID,
            key2 = (
                DbFunctions.TruncateTime(ad.EXPIRY).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) >= 0
                && DbFunctions.TruncateTime(ad.EFFECTIVE).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) <= 0
                )
        }
    into adj from ad in adj.DefaultIfEmpty()
join j in dbHR.Jobs 
    on ad.DJB_ID equals j.ID 
    into jj from j in jj.DefaultIfEmpty()
join jd in dbHR.JobDetails 
    on new { 
        key1 = j.ID, key2 = true 
    } equals new {
        key1 = jd.DJB_ID,
        key2 =
            (
            DbFunctions.TruncateTime(jd.EFFECTIVE).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) <= 0
            && DbFunctions.TruncateTime(jd.EXPIRY).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) >= 0
            )
    }
    into jdj from jd in jdj.DefaultIfEmpty()
join d in dbHR.Departments 
    on ad.DDP_ID equals d.ID 
    into dj from d in dj.DefaultIfEmpty()
join dd in dbHR.DepartmentDetails 
    on new { 
        key1 = d.ID, key2 = true 
    } equals new {
        key1 = dd.DDP_ID,
        key2 =
            (
            DbFunctions.TruncateTime(dd.EFFECTIVE).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) <= 0
            && DbFunctions.TruncateTime(dd.EXPIRY).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) >= 0
            )
    } into ddj from dd in ddj.DefaultIfEmpty()
select new HREmployeeRecord
{
    i=i,
    p= p,
    s=s,
    e=e,
    a=a,
    ad=ad,
    j=j,
    jd=jd,
    d=d,
    dd=dd
}

EF生成的SQL在大多数表上都保留了联接,但其中3个表仍在恢复为内部联接:

FROM          "DB"."DB_HR_IDENTITIES" "Extent1"
LEFT OUTER JOIN "DB"."DB_HR_PERSONALS" "Extent2" ON ("Extent1"."ID" = "Extent2"."EID_ID") AND [...datetime comparison removed for brevity...]
LEFT OUTER JOIN "DB"."DB_CM_STATE_PROVINCES" "Extent3" ON "Extent2"."DSP_ID_ADDRESS" = "Extent3"."ID"
LEFT OUTER JOIN "DB"."DB_HR_EMPLOYMENTS" "Extent4" ON ("Extent1"."ID" = "Extent4"."EID_ID") AND [...datetime comparison removed for brevity...]
LEFT OUTER JOIN "DB"."DB_HR_ASSIGNMENTS" "Extent5" ON ("Extent4"."ID" = "Extent5"."EEM_ID") AND [...datetime comparison removed for brevity...]
INNER JOIN "DB"."DB_HR_ASSIGNMENT_DETAILS" "Extent6" ON ("Extent5"."ID" = "Extent6"."EAS_ID") AND ((TRUNC("Extent6"."EXPIRY")) >= (TRUNC(LOCALTIMESTAMP))) AND ((TRUNC("Extent6"."EFFECTIVE")) <= (TRUNC(LOCALTIMESTAMP)))
INNER JOIN "DB"."DB_CM_JOBS" "Extent7" ON "Extent6"."DJB_ID" = "Extent7"."ID"
LEFT OUTER JOIN "DB"."DB_CM_JOB_DETAILS" "Extent8" ON ("Extent7"."ID" = "Extent8"."DJB_ID") AND [...datetime comparison removed for brevity...]
INNER JOIN "DB"."DB_CM_DEPARTMENTS" "Extent9" ON "Extent6"."DDP_ID" = "Extent9"."ID"
LEFT OUTER JOIN "DB"."DB_CM_DEPARTMENT_DETAILS" "Extent10" ON ("Extent9"."ID" = "Extent10"."DDP_ID") AND [...datetime comparison removed for brevity...]

我已经尝试用以下内容替换一些联接,但不幸的是,它导致了相同的输出:

from ad in dbHR.AssignmentDetails.Where(x=>
    a.ID == x.EAS_ID &&
    (
        DbFunctions.TruncateTime(x.EXPIRY).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) >= 0
        && DbFunctions.TruncateTime(x.EFFECTIVE).Value.CompareTo(DbFunctions.TruncateTime(DateTime.Now).Value) <= 0
    )
).DefaultIfEmpty()

我开始拔头发了。

我在这个答案中读到:

EF似乎使用INNER JOIN来包含必需的导航属性,而LEFT OUTER JOIN则用于包含可选的导航属性。

虽然这是有道理的,但我不认为它适用于我的AssignmentDetails表,因为它是一对多(AssignmentDetails)关系的多(FK)侧,其中可能没有AssignmentDetails记录。

然而,对于Departments和Jobs表来说,这可能是真的,因为它们是外键引用的主键表——应该总是有一个记录。然而,这仍然会破坏结果,因为如果在联接中,FK所在的表由于左联接而为NULL,那么整行将由于INNER联接而被删除。

如有任何协助,我们将不胜感激!

实体框架在使用左联接DefaultIfEmpty()语法时强制内部联接

我的错误。我将这个查询封装在一个名为GetEmployees()的函数中,该函数返回了一个iQueryable列表,并对其进行了过滤:

results = HRFunctions.GetEmployees().Where(x => x.ad != null);

由于添加了一个假设AD不应为null的WHERE子句,EntityFramework智能地将LEFT JOIN更改为INNER JOIN。

删除.Where()后,AssignmentDetails现在是左联接。