2011-04-04 250 views
54

如何更改此查詢以便返回所有u.usergroups?實體框架左連接

from u in usergroups 
from p in u.UsergroupPrices 
select new UsergroupPricesList 
{ 
UsergroupID = u.UsergroupID, 
UsergroupName = u.UsergroupName, 
Price = p.Price 
}; 
+1

也許[這]( http://geekswithblogs.net/SudheersBlog/archive/2009/06/11/132758.aspx)可以提供幫助。這是另一個問題在這裏[SO](http://stackoverflow.com/questions/2376701/linq-to-entities-how-to-define-left-join-for-grouping) – Menahem 2011-04-04 12:30:26

回答

94

改編自MSDN,how to left join using EF 4

var query = from u in usergroups 
      join p in UsergroupPrices on u equals p.UsergroupID into gj 
      from x in gj.DefaultIfEmpty() 
      select new { 
       UsergroupID = u.UsergroupID, 
       UsergroupName = u.UsergroupName, 
       Price = (x == null ? String.Empty : x.Price) 
      }; 
+1

我喜歡這個比 更好 其中gj.DefaultIfEmpty() 最後因爲我可以在where或select中使用x! – Gary 2013-01-31 21:12:01

+0

你能解釋一下'from x in gj.DefaultIfEmpty()'這一行嗎? – 2014-01-24 13:42:42

+0

@AlexDresko這個部分從連接中獲取所有結果,對於那些沒有右手價值的部分,給你null(對象的默認值爲null)。 hth – Menahem 2014-01-25 23:53:33

14

這可能是有點矯枉過正,但我​​寫了一個擴展方法,這樣你就可以使用Join語法(至少在方法做LeftJoin電話符號):

persons.LeftJoin(
    phoneNumbers, 
    person => person.Id, 
    phoneNumber => phoneNumber.PersonId, 
    (person, phoneNumber) => new 
     { 
      Person = person, 
      PhoneNumber = (phoneNumber != null) ? phoneNumber.Number : null 
     } 
); 

我的代碼做什麼比加入GroupJoinSelectMany通話更到當前表達式樹。儘管如此,它看起來相當複雜,因爲我必須親自構建表達式並修改用戶在resultSelector參數中指定的表達式樹,以保持LINQ-to-Entities可轉換整個樹。

public static class LeftJoinExtension 
{ 
    public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
     this IQueryable<TOuter> outer, 
     IQueryable<TInner> inner, 
     Expression<Func<TOuter, TKey>> outerKeySelector, 
     Expression<Func<TInner, TKey>> innerKeySelector, 
     Expression<Func<TOuter, TInner, TResult>> resultSelector) 
    { 
     MethodInfo groupJoin = typeof (Queryable).GetMethods() 
               .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] GroupJoin[TOuter,TInner,TKey,TResult](System.Linq.IQueryable`1[TOuter], System.Collections.Generic.IEnumerable`1[TInner], System.Linq.Expressions.Expression`1[System.Func`2[TOuter,TKey]], System.Linq.Expressions.Expression`1[System.Func`2[TInner,TKey]], System.Linq.Expressions.Expression`1[System.Func`3[TOuter,System.Collections.Generic.IEnumerable`1[TInner],TResult]])") 
               .MakeGenericMethod(typeof (TOuter), typeof (TInner), typeof (TKey), typeof (LeftJoinIntermediate<TOuter, TInner>)); 
     MethodInfo selectMany = typeof (Queryable).GetMethods() 
                .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] SelectMany[TSource,TCollection,TResult](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,System.Collections.Generic.IEnumerable`1[TCollection]]], System.Linq.Expressions.Expression`1[System.Func`3[TSource,TCollection,TResult]])") 
                .MakeGenericMethod(typeof (LeftJoinIntermediate<TOuter, TInner>), typeof (TInner), typeof (TResult)); 

     var groupJoinResultSelector = (Expression<Func<TOuter, IEnumerable<TInner>, LeftJoinIntermediate<TOuter, TInner>>>) 
             ((oneOuter, manyInners) => new LeftJoinIntermediate<TOuter, TInner> {OneOuter = oneOuter, ManyInners = manyInners}); 

     MethodCallExpression exprGroupJoin = Expression.Call(groupJoin, outer.Expression, inner.Expression, outerKeySelector, innerKeySelector, groupJoinResultSelector); 

     var selectManyCollectionSelector = (Expression<Func<LeftJoinIntermediate<TOuter, TInner>, IEnumerable<TInner>>>) 
              (t => t.ManyInners.DefaultIfEmpty()); 

     ParameterExpression paramUser = resultSelector.Parameters.First(); 

     ParameterExpression paramNew = Expression.Parameter(typeof (LeftJoinIntermediate<TOuter, TInner>), "t"); 
     MemberExpression propExpr = Expression.Property(paramNew, "OneOuter"); 

     LambdaExpression selectManyResultSelector = Expression.Lambda(new Replacer(paramUser, propExpr).Visit(resultSelector.Body), paramNew, resultSelector.Parameters.Skip(1).First()); 

     MethodCallExpression exprSelectMany = Expression.Call(selectMany, exprGroupJoin, selectManyCollectionSelector, selectManyResultSelector); 

     return outer.Provider.CreateQuery<TResult>(exprSelectMany); 
    } 

    private class LeftJoinIntermediate<TOuter, TInner> 
    { 
     public TOuter OneOuter { get; set; } 
     public IEnumerable<TInner> ManyInners { get; set; } 
    } 

    private class Replacer : ExpressionVisitor 
    { 
     private readonly ParameterExpression _oldParam; 
     private readonly Expression _replacement; 

     public Replacer(ParameterExpression oldParam, Expression replacement) 
     { 
      _oldParam = oldParam; 
      _replacement = replacement; 
     } 

     public override Expression Visit(Expression exp) 
     { 
      if (exp == _oldParam) 
      { 
       return _replacement; 
      } 

      return base.Visit(exp); 
     } 
    } 
} 
+0

感謝您的延伸fero。 – Fergers 2016-08-10 10:52:10

0

我可以通過調用主模型上的DefaultIfEmpty()來做到這一點。這讓我留在加盟懶加載實體,似乎更可讀的對我說:

 var complaints = db.Complaints.DefaultIfEmpty() 
      .Where(x => x.DateStage1Complete == null || x.DateStage2Complete == null) 
      .OrderBy(x => x.DateEntered) 
      .Select(x => new 
      { 
       ComplaintID = x.ComplaintID, 
       CustomerName = x.Customer.Name, 
       CustomerAddress = x.Customer.Address, 
       MemberName = x.Member != null ? x.Member.Name: string.Empty, 
       AllocationName = x.Allocation != null ? x.Allocation.Name: string.Empty, 
       CategoryName = x.Category != null ? x.Category.Ssl_Name : string.Empty, 
       Stage1Start = x.Stage1StartDate, 
       Stage1Expiry = x.Stage1_ExpiryDate, 
       Stage2Start = x.Stage2StartDate, 
       Stage2Expiry = x.Stage2_ExpiryDate 
      }); 
+1

這裏,根本不需要'.DefaultIfEmpty()':它隻影響'db.Complains'爲空時發生的情況。 'db.Complains.Where(...)。OrderBy(...)。Select(x => new {...,MemberName = x.Member!= null?x.Member.Name:string.Empty,。 ..}),沒有任何'.DefaultIfEmpty()',已經執行了一個左連接(假設'Member'屬性被標記爲可選的)。 – hvd 2017-11-06 15:43:26

2

請讓您的生活更輕鬆(不使用加入到組):

var query = from ug in UserGroups 
      from ugp in UserGroupPrices.Where(x => x.UserGroupId == ug.Id).DefaultIfEmpty() 
      select new 
      { 
       UserGroupID = ug.UserGroupID, 
       UserGroupName = ug.UserGroupName, 
       Price = ugp.Price 
      }; 
+1

避免加入團體是一個意見問題,但它無疑是一個有效的觀點。 'Price = ugp.Price'可能會失敗,如果'Price'是一個不可爲空的屬性,並且左連接不會給出任何結果。 – hvd 2017-11-06 15:45:21

+0

同意上面的內容,但有兩個以上的表格,這種方法更容易閱讀和維護。 – 2017-11-06 15:49:49