Cómo hacer combinaciones en LINQ en múltiples campos en combinación única

244

Necesito hacer una consulta LINQ2DataSet que haga una unión en más de un campo (como

var result = from x in entity
join y in entity2 
       on x.field1 = y.field1 
and 
          x.field2 = y.field2

Todavía he encontrado una solución adecuada (puedo agregar las restricciones adicionales a una cláusula where, pero esto está lejos de ser una solución adecuada, o usar esta solución, pero eso supone un equijoin).

¿Es posible en LINQ unirse en múltiples campos en una sola combinación?

EDITAR

var result = from x in entity
             join y in entity2
             on new { x.field1, x.field2 } equals new { y.field1, y.field2 }

es la solución a la que me referí como asumir un equijoin arriba.

EDITAR más

Para responder a las críticas de que mi ejemplo original era un equijoin, reconozco que, mi requisito actual es un equijoin y ya he empleado la solución que mencioné anteriormente.

Sin embargo, estoy tratando de entender qué posibilidades y mejores prácticas tengo / debo emplear con LINQ. Voy a necesitar hacer una consulta de rango de fechas unirme con una ID de tabla pronto, y solo estaba adelantando ese problema, parece que tendré que agregar el rango de fechas en la cláusula where.

Gracias, como siempre, por todas las sugerencias y comentarios dados.

johnc
fuente
48
Solo un FYI para cualquiera que lea esto, si realiza una unión de múltiples campos en clases annon, DEBE nombrar los campos en ambas clases annon de la misma manera, de lo contrario obtendrá errores de compilación.
Mark
66
O más bien, debe asegurarse de que tengan nombres coincidentes. es decir, puede nombrar los campos de uno de los tipos anon para que coincidan con el otro.
Tom Ferguson el
1
Presta atención a esta respuesta stackoverflow.com/a/34176502/1704458
TS
Usé tuplas para ambos lados de los iguales en lugar de objetos y parecía funcionar también.
GHZ

Respuestas:

89

La solución con el tipo anónimo debería funcionar bien. LINQ solo puede representar equijoins (con cláusulas de unión, de todos modos), y de hecho eso es lo que ha dicho que desea expresar de todos modos en función de su consulta original.

Si no le gusta la versión con el tipo anónimo por alguna razón específica, debe explicar esa razón.

Si desea hacer algo diferente a lo que originalmente solicitó, por favor, dé un ejemplo de lo que realmente quiere hacer.

EDITAR: respondiendo a la edición en la pregunta: sí, para hacer una unión de "intervalo de fechas", debe usar una cláusula where en su lugar. En realidad, son semánticamente equivalentes, por lo que es solo una cuestión de las optimizaciones disponibles. Los equijoins proporcionan una optimización simple (en LINQ to Objects, que incluye LINQ to DataSets) mediante la creación de una búsqueda basada en la secuencia interna; considérelo como una tabla hash de clave a secuencia de entradas que coinciden con esa clave.

Hacer eso con los rangos de fechas es algo más difícil. Sin embargo, dependiendo de exactamente lo que quiere decir con una "unión de rango de fechas", puede hacer algo similar , si planea crear "bandas" de fechas (por ejemplo, una por año) de modo que dos entradas que ocurran en el el mismo año (pero no en la misma fecha) debería coincidir, entonces puede hacerlo simplemente usando esa banda como clave. Si es más complicado, por ejemplo, un lado de la combinación proporciona un rango, y el otro lado de la combinación proporciona una sola fecha, que coincide si cae dentro de ese rango, que se manejaría mejor con una wherecláusula (después de un segundofromcláusula) OMI. Podrías hacer algo de magia particularmente funky ordenando a un lado u otro que busque coincidencias de manera más eficiente, pero eso sería mucho trabajo: solo haría ese tipo de cosas después de verificar si el rendimiento es un problema.

Jon Skeet
fuente
Gracias, sí, el rendimiento es mi principal preocupación al usar la cláusula where. Supongo que una cláusula where después de la unión realizaría un filtro en un conjunto de datos más grande que podría haberse reducido al introducir el segundo parámetro de unión. Me gusta la idea de ordenar para probar si puedo obtener ganancias de eficiencia
johnc
¿Cuántos registros tendrás? No olvides que ordenar los resultados para comenzar tomará una cierta cantidad de tiempo para comenzar ...
Jon Skeet
"Realmente son semánticamente equivalentes". ¿Necesitamos la palabra 'realmente' allí? Tal vez quisiste decir, "Son muy semánticamente equivalentes" :)
onedaywhen
136
var result = from x in entity
   join y in entity2 on new { x.field1, x.field2 } equals new { y.field1, y.field2 }
KristoferA
fuente
Esto es lo que estaba buscando, ya que las 101 muestras de Linq no tenían esto, o al menos eso vi.
Chris Marisic
1
@PeterX de hecho puede, vea mi respuesta aquí: stackoverflow.com/a/22176658/595157
niieani
13
El código anterior no funcionó. Después de agregarlo on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 } funcionó
Ravi Ram
@Ravi Ram .. Gracias ... su comentario ayudó
NMathur
80
var result = from x in entity1
             join y in entity2
             on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }

Debe hacer esto si los nombres de las columnas son diferentes en dos entidades.

RealNapster
fuente
66
Gracias por mencionar los diferentes nombres de columna. Esto arregló mi mala expresión.
Gaʀʀʏ
1
Esto funcionó para mí también. Si los nombres de columna no coinciden, obtendrá este error: "El tipo de una de las expresiones en la cláusula de unión es incorrecto. La inferencia de tipo falló en la llamada a 'GroupJoin'".
humbads
Gracias por alias las variables clave.
Thomas.Benz
Recibí el error que @humbads mencionó cuando no nombré todas las propiedades de int 'new {}'. Así que para tu información, si nombras uno, también debes nombrar el resto.
Ethan Melamed
MUCHAS GRACIAS
Charly H
51

Solo para completar esto con una sintaxis de cadena de método equivalente:

entity.Join(entity2, x => new {x.Field1, x.Field2},
                     y => new {y.Field1, y.Field2}, (x, y) => x);

Si bien el último argumento (x, y) => xes lo que selecciona (en el caso anterior seleccionamos x).

niieani
fuente
31

Creo que una opción más legible y flexible es usar la función Where:

var result = from x in entity1
             from y in entity2
                 .Where(y => y.field1 == x.field1 && y.field2 == x.field2)

Esto también permite cambiar fácilmente de la unión interna a la unión izquierda agregando .DefaultIfEmpty ().

Alexei
fuente
Como usuario de lambda desde hace mucho tiempo (a diferencia de cuando hice la pregunta), tendría que estar de acuerdo
johnc
¿Sería esto más lento?
AlfredBr
1
Creo que debería tener el mismo rendimiento que la nueva { ... } equals new { ... }sintaxis. LinqPad es una gran herramienta para ver cómo se comportan las expresiones (script SQL si se usa LINQ2SQL, árboles de expresiones, etc.)
Alexei
Hasta donde noté, está produciendo CROSS JOIN en lugar de INNER JOIN
Mariusz
@Mariusz Sí, tiene sentido generar CROSS JOIN + WHERE en lugar de INNER JOIN. Sin embargo, para consultas simples, espero que el analizador genere una muy similar.
Alexei
10
var result = from x in entity
             join y in entity2
             on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }
             select new 
             {
               /// Columns
              };
usuario3966657
fuente
8

podrías hacer algo como (abajo)

var query = from p in context.T1

        join q in context.T2

        on

        new { p.Col1, p.Col2 }

        equals

         new { q.Col1, q.Col2 }

        select new {p...., q......};
Codificador perpetuo
fuente
Como mencioné en la pregunta, eso requiere un equijoin
johnc
7

Usando el operador de unión solo puede realizar equijoins. Se pueden construir otros tipos de combinaciones utilizando otros operadores. No estoy seguro de si la unión exacta que está intentando hacer sería más fácil usando estos métodos o cambiando la cláusula where. La documentación sobre la cláusula de unión se puede encontrar aquí . MSDN también tiene un artículo sobre operaciones de combinación con múltiples enlaces a ejemplos de otras combinaciones.

tvanfosson
fuente
3

Si el nombre del campo es diferente en las entidades

var result = from x in entity
   join y in entity2 on 
          new {
                field1=   x.field1,
               field2 =  x.field2 
             } 
          equals
         new { 
                field1= y.field1,
                field2=  y.myfield
              }
select new {x,y});
Mahesh
fuente
Gracias. La coincidencia de nombres era la pieza que me faltaba.
Brett
2

Como una cadena de método completa que se vería así:

lista.SelectMany(a => listb.Where(xi => b.Id == a.Id && b.Total != a.Total),
                (a, b) => new ResultItem
                {
                    Id = a.Id,
                    ATotal = a.Total,
                    BTotal = b.Total
                }).ToList();
Adam Garner
fuente
-2
from d in db.CourseDispatches
                             join du in db.DispatchUsers on d.id equals du.dispatch_id
                             join u in db.Users on du.user_id equals u.id
                             join fr in db.Forumreports on (d.course_id + '_' + du.user_id)  equals  (fr.course_id + '_'+ fr.uid)

esto funciona para mi

usuario2745564
fuente
Esto es para la unión múltiple, quiere hacer una unión con múltiples campos en una sola unión
theLaw
-3

Declare una clase (tipo) para contener los elementos que desea unir. En el siguiente ejemplo, declare JoinElement

 public class **JoinElement**
{
    public int? Id { get; set; }
    public string Name { get; set; }

}

results = from course in courseQueryable.AsQueryable()
                  join agency in agencyQueryable.AsQueryable()
                   on new **JoinElement**() { Id = course.CourseAgencyId, Name = course.CourseDeveloper } 
                   equals new **JoinElement**() { Id = agency.CourseAgencyId, Name = "D" } into temp1
Ven
fuente
1
Esto ya ha sido respondido hace 9 años ... ¿qué valor aporta esta respuesta?
Maciej Jureczko