Linq 到实体左连接
Posted
技术标签:
【中文标题】Linq 到实体左连接【英文标题】:Linq to entities Left Join 【发布时间】:2010-12-03 00:23:22 【问题描述】:我想在 Linq to Entities 中实现以下目标:
获取所有没有应用程序或应用程序状态为 != 4(已完成)的查询
select e.*
from Enquiry enq
left outer join Application app
on enq.enquiryid = app.enquiryid
where app.Status <> 4 or app.enquiryid is null
之前有没有人在不使用 DefaultIfEmpty() 的情况下完成此操作,Linq to Entities 不支持该功能?
我正在尝试向 IQueryable 查询添加过滤器,如下所示:
IQueryable<Enquiry> query = Context.EnquirySet;
query = (from e in query
where e.Applications.DefaultIfEmpty()
.Where(app=>app.Status != 4).Count() >= 1
select e);
谢谢 标记
【问题讨论】:
EF .NET 4.0 中包含 DefaultIfEmpty。 感谢 Damien - 期待 .NET 4 中的 EF 增强功能 【参考方案1】:由于 Linq 处理外部的愚蠢(阅读非标准)方式,您必须使用 DefaultIfEmpty()。
您要做的是将 Linq-To-Entities 查询运行到两个 IEnumerable 中,然后使用 DefaultIfEmpty() 向左加入它们。它可能看起来像:
IQueryable enq = Enquiry.Select();
IQueryable app = Application.Select();
var x = from e in enq
join a in app on e.enquiryid equals a.enquiryid
into ae
where e.Status != 4
from appEnq in ae.DefaultIfEmpty()
select e.*;
仅仅因为你不能用 Linq-To-Entities 来做这并不意味着你不能用原始 Linq 来做。
(注意:在有人反对我之前......是的,我知道有更优雅的方法可以做到这一点。我只是想让它易于理解。重要的是这个概念,对吧?)
【讨论】:
当我过滤 IQueryable这样做:
IQueryable<Enquiry> query = Context.EnquirySet;
query = (from e in query
where (!e.Applications.Any())
|| e.Applications.Any(app => app.Status != 4)
select e);
我根本没有发现 LINQ 对 SQL“愚蠢”中的“外部联接”问题的处理。理解它的关键是考虑具有可为空属性的对象图而不是表格结果集。
Any() 映射到 SQL 中的 EXISTS,因此在某些情况下它比 Count() 效率远。
【讨论】:
【参考方案3】:感谢大家的帮助。我最终选择了这个选项,但您的解决方案有助于拓宽我的知识。
IQueryable<Enquiry> query = Context.EnquirySet;
query = query.Except(from e in query
from a in e.Applications
where a.Status == 4
select e);
【讨论】:
【参考方案4】:在 EF 4.0+ 中,LEFT JOIN 语法略有不同,并呈现出一种疯狂的怪癖:
var query = from c1 in db.Category
join c2 in db.Category on c1.CategoryID equals c2.ParentCategoryID
into ChildCategory
from cc in ChildCategory.DefaultIfEmpty()
select new CategoryObject
CategoryID = c1.CategoryID,
ChildName = cc.CategoryName
如果您在 SQL Server Profiler 中捕获此查询的执行,您将看到它确实执行了 LEFT OUTER JOIN。但是,如果您的 Linq-to-Entity 查询中有多个 LEFT JOIN ("Group Join") 子句,我发现自联接子句实际上可能像在 INNER JOIN 中一样执行 - 即使使用了上述语法!
解决方案?根据 MS 的说法,这听起来很疯狂,而且听起来是错误的,但我通过更改连接子句的顺序解决了这个问题。如果自引用 LEFT JOIN 子句是第一个 Linq Group Join,SQL Profiler 报告一个 INNER JOIN。如果自引用 LEFT JOIN 子句是 LAST Linq Group Join,SQL Profiler 会报告一个 LEFT JOIN。
【讨论】:
这个答案真的应该得到更多的业力【参考方案5】:要考虑的另一件事是,如果您直接从左连接组(使用 into 语法)引用 where 子句中的任何属性而不检查 null,实体框架仍会将您的 LEFT JOIN 转换为 INNER JOIN。
为避免这种情况,请过滤查询的“from x in leftJoinedExtent”部分,如下所示:
var y = from parent in thing
join child in subthing on parent.ID equals child.ParentID into childTemp
from childLJ in childTemp.Where(c => c.Visible == true).DefaultIfEmpty()
where parent.ID == 123
select new
ParentID = parent.ID,
ChildID = childLJ.ID
;
匿名类型中的 ChildID 将是可为空的类型,并且由此生成的查询将是 LEFT JOIN。
【讨论】:
以上是关于Linq 到实体左连接的主要内容,如果未能解决你的问题,请参考以下文章