Linq для лиц, оставленных присоединиться - PullRequest
5 голосов
/ 01 октября 2009

Я хочу добиться следующего в 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);

Спасибо Марка

Ответы [ 5 ]

10 голосов
/ 01 августа 2012

В 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"), я обнаружил, что предложение self-join МОЖЕТ фактически выполняться как в INNER JOIN - ДАЖЕ ЕСЛИ ИСПОЛЬЗУЕТСЯ ВЫШЕ СИНТАКС! 1004 *

Разрешение на что? Как ни сумасшедший, и, по словам MS, он звучит неправильно, но я решил эту проблему, изменив порядок предложений объединения. Если самозаверяющим предложением LEFT JOIN было 1-е соединение Linq Group, SQL Profiler сообщал о INNER JOIN. Если самозаверяющим условием LEFT JOIN было LAST Linq Group Join, SQL Profiler сообщал о LEFT JOIN.

6 голосов
/ 01 октября 2009

Сделайте это:

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 () сопоставляется с EXISTS в SQL, поэтому намного эффективнее, чем Count () в некоторых случаях.

3 голосов
/ 01 октября 2009

Спасибо, ребята, за вашу помощь. В конце концов я выбрал этот вариант, но ваши решения помогли расширить мои знания.

IQueryable<Enquiry> query = Context.EnquirySet;

query = query.Except(from e in query
                     from a in e.Applications
                     where a.Status == 4
                     select e);
1 голос
/ 14 января 2014

Еще один момент, который следует учитывать, если вы напрямую ссылаетесь на какие-либо свойства в предложении where из группы с левым соединением (используя синтаксис in) без проверки на нулевое значение, Entity Framework все равно преобразует ваше левое соединение в соединение INNER.

Чтобы избежать этого, отфильтруйте часть вашего запроса "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 в анонимном типе будет иметь тип NULL, а сгенерированный запрос будет LEFT JOIN.

1 голос
/ 01 октября 2009

Из-за глупого (нестандартного) способа работы с внешними источниками Linq вы должны использовать DefaultIfEmpty ().

То, что вы будете делать, - это выполнить запрос Linq-To-Entities в двух IEnumerables, а затем ВЛЕВО присоединиться к ним с помощью 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.

(Примечание: прежде чем кто-то меня опровергнет ... да, я знаю, что есть более изящные способы сделать это. Я просто пытаюсь сделать это понятным. Это важная концепция, верно?)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...