5

I want to achieve the following in Linq to Entities:

Get all Enquires that have no Application or the Application has a status != 4 (Completed)

select e.*
from Enquiry enq
left outer join Application app
 on enq.enquiryid = app.enquiryid
where app.Status <> 4 or app.enquiryid is null

Has anyone done this before without using DefaultIfEmpty(), which is not supported by Linq to Entities?

I'm trying to add a filter to an IQueryable query like this:

IQueryable<Enquiry> query = Context.EnquirySet; 

query = (from e in query 
         where e.Applications.DefaultIfEmpty()
                             .Where(app=>app.Status != 4).Count() >= 1 
         select e);

Thanks Mark

4

5 回答 5

10

在 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。

于 2012-08-01T15:39:15.790 回答
6

做这个:

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() 更有效

于 2009-10-01T15:23:50.973 回答
3

谢谢大家帮助。我最终选择了这个选项,但您的解决方案有助于拓宽我的知识。

IQueryable<Enquiry> query = Context.EnquirySet;

query = query.Except(from e in query
                     from a in e.Applications
                     where a.Status == 4
                     select e);
于 2009-10-01T15:50:05.713 回答
1

Because of Linq's goofy (read non-standard) way of handling outers, you have to use DefaultIfEmpty().

What you'll do is run your Linq-To-Entities query into two IEnumerables, then LEFT Join them using DefaultIfEmpty(). It may look something like:

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.*;

Just because you can't do it with Linq-To-Entities doesn't mean you can't do it with raw Linq.

(Note: before anyone downvotes me ... yes, I know there are more elegant ways to do this. I'm just trying to make it understandable. It's the concept that's important, right?)

于 2009-10-01T14:14:59.213 回答
1

要考虑的另一件事是,如果您直接从左连接组(使用 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。

于 2014-01-13T22:16:17.183 回答