2011-04-04 24 views
54

¿Cómo cambio esta consulta para que devuelva todos los grupos de usuario?Entity framework left join

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

tal vez [esto] (http://geekswithblogs.net/SudheersBlog/archive/2009/06/11/132758.aspx) puede ayudar. estaba en otra pregunta aquí en [SO] (http://stackoverflow.com/questions/2376701/linq-to-entities-how-to-define-left-join-for-grouping) – Menahem

Respuesta

94

adaptado de 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

Me gusta más que donde gj.DefaultIfEmpty() al final porque puedo usar x en el lugar o seleccionar! – Gary

+0

¿Puedes explicar la línea 'from x in gj.DefaultIfEmpty()'? –

+0

@AlexDresko esta parte toma todos los resultados de la combinación, y para los que no tienen valor a la derecha, le da nulo (el valor predeterminado es que el objeto sea nulo). hth – Menahem

14

Podría ser un poco de una exageración, pero me escribió un método de extensión, por lo que puede hacer un LeftJoin utilizando la sintaxis Join (al menos en el método notación de llamada):

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

Mi código no hace más que añadir un GroupJoin y una llamada SelectMany al árbol de expresión actual. Sin embargo, parece bastante complicado porque tengo que construir las expresiones yo mismo y modificar el árbol de expresiones especificado por el usuario en el parámetro resultSelector para que todo el árbol pueda ser traducido por 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

Gracias por esta extensión fero. – Fergers

0

Pude hacer esto llamando al DefaultIfEmpty() en el modelo principal. Esto me permitió combinación izquierda de entidades cargadas perezosos, parece más fácil de leer para mí:

 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

Aquí, no necesita '.DefaultIfEmpty()' en absoluto: solo afecta lo que sucede cuando 'db.Complains' está vacío. 'db.Complains.Where (...). OrderBy (...). Seleccione (x => nuevo {..., MemberName = x.Member! = null? x.Member.Name: string.Empty,. ..}) ', sin ningún' .DefaultIfEmpty() ', ya realizaría una combinación a la izquierda (suponiendo que la propiedad' Member' está marcada como opcional). – hvd

2

Por favor haga su vida más fácil (no utilizar unirse al grupo):

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

Evitar unirse al grupo es una cuestión de opinión, pero sin duda es una opinión válida. 'Price = ugp.Price' puede fallar si' Price' es una propiedad que no admite nulos y la combinación de la izquierda no da ningún resultado. – hvd

+0

De acuerdo con lo anterior, pero con más de dos tablas, este enfoque es mucho más fácil de leer y mantener. –