Tengo una lista de identificación de la gente y su nombre, y una lista de identificación de la gente y su apellido. Algunas personas no tienen un nombre y otras no tienen un apellido; Me gustaría hacer una combinación externa completa en las dos listas.
Entonces las siguientes listas:
ID FirstName
-- ---------
1 John
2 Sue
ID LastName
-- --------
1 Doe
3 Smith
Debe producir:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
3 Smith
Soy nuevo en LINQ (así que perdónenme si soy cojo) y he encontrado bastantes soluciones para 'LINQ Outer Joins', que parecen bastante similares, pero realmente parecen quedar unidas por fuera.
Mis intentos hasta ahora son algo como esto:
private void OuterJoinTest()
{
List<FirstName> firstNames = new List<FirstName>();
firstNames.Add(new FirstName { ID = 1, Name = "John" });
firstNames.Add(new FirstName { ID = 2, Name = "Sue" });
List<LastName> lastNames = new List<LastName>();
lastNames.Add(new LastName { ID = 1, Name = "Doe" });
lastNames.Add(new LastName { ID = 3, Name = "Smith" });
var outerJoin = from first in firstNames
join last in lastNames
on first.ID equals last.ID
into temp
from last in temp.DefaultIfEmpty()
select new
{
id = first != null ? first.ID : last.ID,
firstname = first != null ? first.Name : string.Empty,
surname = last != null ? last.Name : string.Empty
};
}
}
public class FirstName
{
public int ID;
public string Name;
}
public class LastName
{
public int ID;
public string Name;
}
Pero esto vuelve:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
¿Qué estoy haciendo mal?
c#
.net
linq
outer-join
full-outer-join
ninjaPixel
fuente
fuente
Respuestas:
No sé si esto cubre todos los casos, lógicamente parece correcto. La idea es tomar una unión externa izquierda y una unión externa derecha y luego tomar la unión de los resultados.
Esto funciona como está escrito, ya que está en LINQ to Objects. Si LINQ to SQL u otro, el procesador de consultas podría no admitir navegación segura u otras operaciones. Tendría que usar el operador condicional para obtener condicionalmente los valores.
es decir,
fuente
AsEnumerable()
antes de realizar la unión / concatenación. Pruebe eso y vea cómo va eso. Si esta no es la ruta que desea tomar, no estoy seguro de que pueda serle de más ayuda.Actualización 1: proporcionar un método de extensión verdaderamente generalizado
FullOuterJoin
Actualización 2: opcionalmente aceptar un personalizado
IEqualityComparer
para el tipo de claveActualización 3 : esta implementación se ha convertido recientemente en parte de
MoreLinq
- ¡Gracias chicos!Editar agregado
FullOuterGroupJoin
( ideone ). Reutilicé laGetOuter<>
implementación, haciendo que esta sea una fracción menos eficaz de lo que podría ser, pero estoy apuntando a un código de 'alto nivel', no optimizado, en este momento.Véalo en vivo en http://ideone.com/O36nWc
Imprime la salida:
También puede proporcionar valores predeterminados: http://ideone.com/kG4kqO
Impresión:
Explicación de los términos utilizados:
Unirse es un término tomado del diseño de una base de datos relacional:
a
tantas veces como haya elementosb
con la clave correspondiente (es decir: nada sib
estuviera vacío). La jerga de la base de datos llama a estoinner (equi)join
.a
para los que no existe ningún elemento correspondienteb
. (es decir: incluso los resultados sib
estuvieran vacíos). Esto generalmente se conoce comoleft join
.a
, así comob
si no existe ningún elemento correspondiente en el otro. (es decir, incluso los resultados sia
estuvieran vacíos)Algo que generalmente no se ve en RDBMS es una unión grupal [1] :
a
múltiples correspondientesb
, agrupa los registros con las teclas correspondientes. Esto suele ser más conveniente cuando desea enumerar a través de registros 'unidos', basados en una clave común.Consulte también GroupJoin, que también contiene algunas explicaciones generales de antecedentes.
[1] (Creo que Oracle y MSSQL tienen extensiones propietarias para esto)
Código completo
Una clase de extensión 'drop-in' generalizada para esto
fuente
FullOuterJoin
método de extensión proporcionadoa.GroupBy(selectKeyA).ToDictionary();
comoa.ToLookup(selectKeyA)
yadict.OuterGet(key)
comoalookup[key]
. Sin embargo, obtener la colección de llaves es un poco más complicado:alookup.Select(x => x.Keys)
.Creo que hay problemas con la mayoría de estos, incluida la respuesta aceptada, porque no funcionan bien con Linq sobre IQueryable, ya sea por hacer demasiados viajes de ida y vuelta al servidor y demasiados retornos de datos, o por hacer demasiada ejecución del cliente.
Para IEnumerable no me gusta la respuesta de Sehe o similar porque tiene un uso excesivo de memoria (una simple prueba 10000000 de dos listas ejecutó a Linqpad sin memoria en mi máquina de 32 GB).
Además, la mayoría de los demás en realidad no implementan una unión externa completa adecuada porque están utilizando una unión con una unión derecha en lugar de una concat con una unión anti derecha semi, que no solo elimina las filas duplicadas de unión interna del resultado, sino que también cualquier duplicado adecuado que existiera originalmente en los datos izquierdo o derecho.
Así que aquí están mis extensiones que manejan todos estos problemas, generan SQL e implementan la unión en LINQ to SQL directamente, ejecutándose en el servidor, y es más rápido y con menos memoria que otros en Enumerables:
La diferencia entre un Anti-Semi-Join correcto es principalmente discutible con Linq to Objects o en la fuente, pero hace una diferencia en el lado del servidor (SQL) en la respuesta final, eliminando un innecesario
JOIN
.La codificación manual de
Expression
manejar la fusión de unExpression<Func<>>
en un lambda podría mejorarse con LinqKit, pero sería bueno si el lenguaje / compilador hubiera agregado alguna ayuda para eso. Las funcionesFullOuterJoinDistinct
yRightOuterJoin
se incluyen para completar, pero no volví a implementarFullOuterGroupJoin
.Escribí otra versión de una combinación externa completa para
IEnumerable
para los casos en que la clave es ordenable, que es aproximadamente un 50% más rápida que la combinación de la combinación externa izquierda con la combinación anti derecha, al menos en pequeñas colecciones. Revisa cada colección después de ordenar solo una vez.También agregué otra respuesta para una versión que funciona con EF al reemplazarla
Invoke
con una expansión personalizada.fuente
TP unusedP, TC unusedC
? ¿Están literalmente sin usar?TP
,TC
,TResult
para crear el correctoExpression<Func<>>
. Supuse que podría reemplazarlos con_
,__
,___
en su lugar, pero que no parece más claro hasta que C # tiene un comodín parámetro adecuado para usar en su lugar.The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
. ¿Hay alguna restricción con este código? Quiero realizar una UNIÓN COMPLETA sobre IQueryablesInvoke
con una personalizadaExpressionVisitor
para alinearla,Invoke
por lo que debería funcionar con EF. ¿Puedes probarlo?Aquí hay un método de extensión que hace eso:
fuente
Union
elimina los duplicados, por lo que si hay filas duplicadas en los datos originales, no estarán en el resultado.Supongo que el enfoque de @ sehe es más fuerte, pero hasta que lo entiendo mejor, me encuentro saltando de la extensión de @ MichaelSander. Lo modifiqué para que coincida con la sintaxis y el tipo de retorno del método incorporado Enumerable.Join () descrito aquí . Agregué el sufijo "distinto" con respecto al comentario de @ cadrell0 bajo la solución de @ JeffMercado.
En el ejemplo, lo usarías así:
En el futuro, a medida que aprenda más, tengo la sensación de que migraré a la lógica de @ sehe dada su popularidad. Pero incluso entonces tendré que tener cuidado, porque creo que es importante tener al menos una sobrecarga que coincida con la sintaxis del método ".Join ()" existente si es posible, por dos razones:
Todavía soy nuevo con genéricos, extensiones, declaraciones Func y otras características, por lo que los comentarios son bienvenidos.
EDITAR: No me llevó mucho tiempo darme cuenta de que había un problema con mi código. Estaba haciendo un .Dump () en LINQPad y mirando el tipo de retorno. Era simplemente IEnumerable, así que traté de igualarlo. Pero cuando realmente hice un .Where () o .Select () en mi extensión, recibí un error: "'System Collections.IEnumerable' no contiene una definición para 'Seleccionar' y ...". Así que al final pude hacer coincidir la sintaxis de entrada de .Join (), pero no el comportamiento de retorno.
EDITAR: Se agregó "TResult" al tipo de retorno para la función. Perdí eso al leer el artículo de Microsoft, y por supuesto tiene sentido. Con esta solución, ahora parece que el comportamiento de retorno está en línea con mis objetivos después de todo.
fuente
Como has encontrado, Linq no tiene una construcción de "unión externa". Lo más cercano que puede obtener es una combinación externa izquierda utilizando la consulta que indicó. Para esto, puede agregar cualquier elemento de la lista de apellidos que no esté representado en la unión:
fuente
Me gusta la respuesta de sehe, pero no utiliza la ejecución diferida (las llamadas a ToLookup enumeran con entusiasmo las secuencias de entrada). Entonces, después de mirar las fuentes .NET para LINQ-to-objects , se me ocurrió esto:
Esta implementación tiene las siguientes propiedades importantes:
Estas propiedades son importantes, porque son lo que alguien nuevo en FullOuterJoin pero experimentado con LINQ esperará.
fuente
Decidí agregar esto como una respuesta separada, ya que no estoy seguro de que se haya probado lo suficiente. Esta es una re-implementación del
FullOuterJoin
método usando esencialmente una versión simplificada y personalizada deLINQKit
Invoke
/Expand
forExpression
para que funcione el Entity Framework. No hay mucha explicación, ya que es más o menos lo mismo que mi respuesta anterior.fuente
base.Visit(node)
No debería lanzar una excepción, ya que solo se repite en el árbol. Puedo acceder a casi cualquier servicio de código compartido, pero no configurar una base de datos de prueba. Sin embargo, ejecutarlo contra mi prueba LINQ to SQL parece funcionar bien.Guid
clave y unaGuid?
clave externa?Realiza una enumeración de transmisión en memoria en ambas entradas e invoca el selector para cada fila. Si no hay correlación en la iteración actual, uno de los argumentos del selector será nulo .
Ejemplo:
Requiere un IComparer para el tipo de correlación, utiliza el Comparer.Default si no se proporciona.
Requiere que 'OrderBy' se aplique a los enumerables de entrada
fuente
OrderBy
en ambas proyecciones clave.OrderBy
amortigua toda la secuencia, por las razones obvias .Mi solución limpia para la situación de esa clave es única en ambos enumerables:
entonces
salidas:
fuente
Unión externa completa para dos o más tablas: primero extraiga la columna a la que desea unir.
Luego use la unión externa izquierda entre la columna extraída y las tablas principales.
fuente
Escribí esta clase de extensiones para una aplicación hace aproximadamente 6 años, y la he estado usando desde entonces en muchas soluciones sin problemas. Espero eso ayude.
editar: noté que algunos podrían no saber cómo usar una clase de extensión.
Para usar esta clase de extensión, solo haga referencia a su espacio de nombres en su clase agregando la siguiente línea usando joinext;
^ esto debería permitirle ver la inteligencia de las funciones de extensión en cualquier colección de objetos IEnumerable que utilice.
Espero que esto ayude. Avíseme si aún no está claro, y espero escribir un ejemplo de ejemplo sobre cómo usarlo.
Ahora aquí está la clase:
fuente
SelectMany
no se puede convertir en un árbol de expresión digno de LINQ2SQL.Creo que la cláusula de unión de LINQ no es la solución correcta para este problema, porque el propósito de la cláusula de unión no es acumular datos de la manera requerida para esta solución de tarea. El código para combinar colecciones separadas creadas se vuelve demasiado complicado, tal vez esté bien para fines de aprendizaje, pero no para aplicaciones reales. Una de las formas de resolver este problema es en el siguiente código:
Si las colecciones reales son grandes para la formación de HashSet, en lugar de los bucles foreach se puede usar el siguiente código:
fuente
¡Gracias a todos por las publicaciones interesantes!
Modifiqué el código porque en mi caso necesitaba
Para los interesados, este es mi código modificado (en VB, lo siento)
fuente
Sin embargo, otra unión externa completa
Como no estaba tan contento con la simplicidad y la legibilidad de las otras proposiciones, terminé con esto:
No tiene la pretensión de ser rápido (aproximadamente 800 ms para unirse a 1000 * 1000 en una CPU 2020m: 2.4 ghz / 2 núcleos). Para mí, es solo una combinación externa completa compacta y casual.
Funciona igual que un SQL FULL OUTER JOIN (conservación duplicada)
Salud ;-)
La idea es
Aquí hay una prueba sucinta que la acompaña:
Coloque un punto de interrupción al final para verificar manualmente que se comporta como se esperaba
}
fuente
Realmente odio estas expresiones linq, por eso existe SQL:
Cree esto como vista sql en la base de datos e impórtelo como entidad.
Por supuesto, la unión (distinta) de las uniones izquierda y derecha también lo hará, pero es estúpido.
fuente