usar una tipo sintaxis puede metodos metodo matematicas funciones extensión extension expresiones expresion delegado cómo convertir consulta anonimas c# linq-to-sql lambda

una - metodos de extension c#



¿Cómo se realiza una unión externa izquierda utilizando métodos de extensión linq? (7)

Al convertir la respuesta de Marc Gravell en un método de extensión, hice lo siguiente.

internal static IEnumerable<Tuple<TLeft, TRight>> LeftJoin<TLeft, TRight, TKey>( this IEnumerable<TLeft> left, IEnumerable<TRight> right, Func<TLeft, TKey> selectKeyLeft, Func<TRight, TKey> selectKeyRight, TRight defaultRight = default(TRight), IEqualityComparer<TKey> cmp = null) { return left.GroupJoin( right, selectKeyLeft, selectKeyRight, (x, y) => new Tuple<TLeft, IEnumerable<TRight>>(x, y), cmp ?? EqualityComparer<TKey>.Default) .SelectMany( x => x.Item2.DefaultIfEmpty(defaultRight), (x, y) => new Tuple<TLeft, TRight>(x.Item1, y)); }

Suponiendo que tengo una unión externa izquierda como tal:

from f in Foo join b in Bar on f.Foo_Id equals b.Foo_Id into g from result in g.DefaultIfEmpty() select new { Foo = f, Bar = result }

¿Cómo expresaría la misma tarea usando métodos de extensión? P.ej

Foo.GroupJoin(Bar, f => f.Foo_Id, b => b.Foo_Id, (f,b) => ???) .Select(???)


El método de unión en grupo no es necesario para lograr la unión de dos conjuntos de datos.

Unir internamente:

var qry = Foos.SelectMany ( foo => Bars.Where (bar => foo.Foo_id == bar.Foo_id), (foo, bar) => new { Foo = foo, Bar = bar } );

Para unir a la izquierda solo agrega DefaultIfEmpty ()

var qry = Foos.SelectMany ( foo => Bars.Where (bar => foo.Foo_id == bar.Foo_id).DefaultIfEmpty(), (foo, bar) => new { Foo = foo, Bar = bar } );

EF se transforma correctamente a SQL. Para LINQ a los objetos, es mejor unirse usando GroupJoin ya que internamente usa Lookup, pero si está consultando DB, entonces omitir GroupJoin es AFAIK como ejecutante.

Personlay para mí de esta manera es más legible en comparación con GroupJoin (). SelectMany ()


Mejorando la respuesta de Ocelot20, si tiene una tabla, se le permite unirla con un solo lugar donde quiera que sea 0 o 1 filas de la misma, pero podría tener varias, debe ordenar su tabla combinada:

var qry = Foos.GroupJoin( Bars.OrderByDescending(b => b.Id), foo => foo.Foo_Id, bar => bar.Foo_Id, (f, bs) => new { Foo = f, Bar = bs.FirstOrDefault() });

De lo contrario, la fila que obtenga en la unión será aleatoria (o, más específicamente, lo que el db encuentre primero).


Puedes crear un método de extensión como:

public static IEnumerable<TResult> LeftOuterJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source, IEnumerable<TInner> other, Func<TSource, TKey> func, Func<TInner, TKey> innerkey, Func<TSource, TInner, TResult> res) { return from f in source join b in other on func.Invoke(f) equals innerkey.Invoke(b) into g from result in g.DefaultIfEmpty() select res.Invoke(f, result); }


Si bien la respuesta aceptada funciona y es buena para Linq to Objects, me fastidió que la consulta SQL no sea solo una unión externa izquierda directa.

El siguiente código se basa en el Proyecto LinkKit que le permite pasar expresiones e invocarlas a su consulta.

static IQueryable<TResult> LeftOuterJoin<TSource,TInner, TKey, TResult>( this IQueryable<TSource> source, IQueryable<TInner> inner, Expression<Func<TSource,TKey>> sourceKey, Expression<Func<TInner,TKey>> innerKey, Expression<Func<TSource, TInner, TResult>> result ) { return from a in source.AsExpandable() join b in inner on sourceKey.Invoke(a) equals innerKey.Invoke(b) into c from d in c.DefaultIfEmpty() select result.Invoke(a,d); }

Puede ser utilizado como sigue

Table1.LeftOuterJoin(Table2, x => x.Key1, x => x.Key2, (x,y) => new { x,y});


Ya que esta parece ser la pregunta SO de facto para las combinaciones externas izquierdas utilizando la sintaxis del método (extensión), pensé que agregaría una alternativa a la respuesta actualmente seleccionada que (al menos en mi experiencia) ha sido más comúnmente lo que soy. después

// Option 1: Expecting either 0 or 1 matches from the "Right" // table (Bars in this case): var qry = Foos.GroupJoin( Bars, foo => foo.Foo_Id, bar => bar.Foo_Id, (f,bs) => new { Foo = f, Bar = bs.SingleOrDefault() }); // Option 2: Expecting either 0 or more matches from the "Right" table // (courtesy of currently selected answer): var qry = Foos.GroupJoin( Bars, foo => foo.Foo_Id, bar => bar.Foo_Id, (f,bs) => new { Foo = f, Bars = bs }) .SelectMany( fooBars => fooBars.Bars.DefaultIfEmpty(), (x,y) => new { Foo = x.Foo, Bar = y });

Para mostrar la diferencia utilizando un conjunto de datos simple (suponiendo que nos unamos a los valores en sí mismos):

List<int> tableA = new List<int> { 1, 2, 3 }; List<int?> tableB = new List<int?> { 3, 4, 5 }; // Result using both Option 1 and 2. Option 1 would be a better choice // if we didn''t expect multiple matches in tableB. { A = 1, B = null } { A = 2, B = null } { A = 3, B = 3 } List<int> tableA = new List<int> { 1, 2, 3 }; List<int?> tableB = new List<int?> { 3, 3, 4 }; // Result using Option 1 would be that an exception gets thrown on // SingleOrDefault(), but if we use FirstOrDefault() instead to illustrate: { A = 1, B = null } { A = 2, B = null } { A = 3, B = 3 } // Misleading, we had multiple matches. // Which 3 should get selected (not arbitrarily the first)?. // Result using Option 2: { A = 1, B = null } { A = 2, B = null } { A = 3, B = 3 } { A = 3, B = 3 }

La opción 2 es fiel a la definición típica de la combinación externa izquierda, pero como mencioné anteriormente, a menudo es innecesariamente compleja según el conjunto de datos.


var qry = Foo.GroupJoin( Bar, foo => foo.Foo_Id, bar => bar.Foo_Id, (x,y) => new { Foo = x, Bars = y }) .SelectMany( x => x.Bars.DefaultIfEmpty(), (x,y) => new { Foo=x.Foo, Bar=y});