Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Una combinación asocia objetos de un origen de datos a objetos que comparten un atributo común en otro origen de datos.
Importante
Estos ejemplos usan un origen de datos System.Collections.Generic.IEnumerable<T>. Los orígenes de datos basados en System.Linq.IQueryProvider usanSystem.Linq.IQueryable<T> orígenes de datos y árboles de expresión . Los árboles de expresión tienen limitaciones en la sintaxis de C# permitida. Además, cada origen de datos IQueryProvider, como EF Core puede imponer más restricciones. Compruebe la documentación del origen de datos.
La unión es una operación importante en las consultas que tienen como destino los orígenes de datos cuyas relaciones entre sí no se pueden seguir directamente. En la programación orientada a objetos, una combinación podría significar una correlación entre objetos que no está modelada, como el sentido contrario de una relación unidireccional. Un ejemplo de una relación unidireccional es una clase Student que tiene una propiedad de tipo Department que representa su área de especialización, pero la clase Department no tiene una propiedad que sea una colección de objetos Student. Si tiene una lista de objetos Department y desea encontrar todos los alumnos de cada departamento, puede usar una operación de unión para encontrarlos.
El marco LINQ proporciona métodos de combinación: Join y GroupJoin. Estos métodos efectúan combinaciones de igualdad, o combinaciones que hacen corresponder dos orígenes de datos en función de la igualdad de sus claves. Para la comparación, Transact-SQL admite operadores de combinación distintos de equals, como el less than operador . En términos de base de datos relacionales, Join implementa una combinación interna, un tipo de combinación en el que solo se devuelven los objetos que tienen una coincidencia en el otro conjunto de datos. El método GroupJoin no tiene equivalente directo en términos de bases de datos relacionales; pero implementa un superconjunto de combinaciones internas y combinaciones externas izquierdas. Una combinación externa izquierda es una combinación que devuelve cada elemento del primer origen de datos (izquierda), aunque no tenga elementos correlacionados en el otro origen de datos.
En la siguiente ilustración se muestra una vista conceptual de dos conjuntos y los elementos de esos conjuntos que se incluyen en una unión interna o una unión externa izquierda.
Métodos
| Nombre del método | Descripción | Sintaxis de la expresión de consulta de C# | Más información |
|---|---|---|---|
| Join | Combina dos secuencias según las funciones de selector de claves y extrae pares de valores. | join … in … on … equals … |
Enumerable.Join Queryable.Join |
| GroupJoin | Combina dos secuencias según las funciones de selector de claves y agrupa los resultados coincidentes para cada elemento. | join … in … on … equals … into … |
Enumerable.GroupJoin Queryable.GroupJoin |
| LeftJoin | Correlaciona los elementos de dos secuencias en función de las claves coincidentes. | N/A | Enumerable.LeftJoin Queryable.LeftJoin |
| RightJoin | Correlaciona los elementos de dos secuencias en función de las claves coincidentes. | N/A | Enumerable.RightJoin Queryable.RightJoin |
Nota:
En los ejemplos siguientes de este artículo se usan los orígenes de datos comunes para esta área.
Cada Student tiene un nivel académico, un departamento principal y una serie de puntuaciones. Un Teacher también tiene una propiedad City que identifica el campus donde el profesor imparte clases. Un Department tiene un nombre y una referencia a un Teacher que actúa como jefe del departamento.
Puede encontrar el conjunto de datos de ejemplo en el repositorio de origen.
public enum GradeLevel
{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};
public class Student
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required int ID { get; init; }
public required GradeLevel Year { get; init; }
public required List<int> Scores { get; init; }
public required int DepartmentID { get; init; }
}
public class Teacher
{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}
public class Department
{
public required string Name { get; init; }
public int ID { get; init; }
public required int TeacherID { get; init; }
}
Nota:
Puede consultar los orígenes de datos comunes de esta área en el artículo Información general sobre operadores de consulta estándar .
En el ejemplo siguiente se usa la join … in … on … equals … cláusula para combinar dos secuencias en función de un valor específico:
var query = from student in students
join department in departments on student.DepartmentID equals department.ID
select new { Name = $"{student.FirstName} {student.LastName}", DepartmentName = department.Name };
foreach (var item in query)
{
Console.WriteLine($"{item.Name} - {item.DepartmentName}");
}
Puede expresar la consulta anterior mediante la sintaxis del método , como se muestra en el código siguiente:
var query = students.Join(departments,
student => student.DepartmentID, department => department.ID,
(student, department) => new { Name = $"{student.FirstName} {student.LastName}", DepartmentName = department.Name });
foreach (var item in query)
{
Console.WriteLine($"{item.Name} - {item.DepartmentName}");
}
En el ejemplo siguiente se usa la join … in … on … equals … into … cláusula para combinar dos secuencias en función de un valor específico y agrupar las coincidencias resultantes para cada elemento:
IEnumerable<IEnumerable<Student>> studentGroups = from department in departments
join student in students on department.ID equals student.DepartmentID into studentGroup
select studentGroup;
foreach (IEnumerable<Student> studentGroup in studentGroups)
{
Console.WriteLine("Group");
foreach (Student student in studentGroup)
{
Console.WriteLine($" - {student.FirstName}, {student.LastName}");
}
}
Puede expresar la consulta anterior mediante la sintaxis del método , como se muestra en el ejemplo siguiente:
// Join department and student based on DepartmentId and grouping result
IEnumerable<IEnumerable<Student>> studentGroups = departments.GroupJoin(students,
department => department.ID, student => student.DepartmentID,
(department, studentGroup) => studentGroup);
foreach (IEnumerable<Student> studentGroup in studentGroups)
{
Console.WriteLine("Group");
foreach (Student student in studentGroup)
{
Console.WriteLine($" - {student.FirstName}, {student.LastName}");
}
}
Realizar combinaciones internas
En términos de base de datos relacionales, una combinación interna genera un conjunto de resultados en el que cada elemento de la primera colección aparece una vez por cada elemento coincidente de la segunda colección. Si un elemento de la primera colección no tiene ningún elemento coincidente, no aparece en el conjunto de resultados. El método Join, al que llama la cláusula join en C#, implementa un join interno. En los ejemplos siguientes se muestra cómo realizar cuatro variaciones de una unión interna:
- Combinación interna simple que correlaciona elementos de dos orígenes de datos en función de una clave simple.
- Unión interna que correlaciona elementos de dos fuentes de datos basándose en una clave compuesta de . Una clave compuesta, que es una clave formada por más de un valor, permite correlacionar elementos en función de más de una propiedad.
- Combinación múltiple en la que se anexan operaciones sucesivas de combinación entre sí.
- Combinación interna que usa una combinación de grupo.
Combinación de clave única
En el ejemplo siguiente se comparan objetos Teacher con objetos Department cuyos elementos TeacherId coinciden con esos objetos Teacher. La cláusula select de C# define el aspecto que tendrán los objetos resultantes. En el ejemplo siguiente, los objetos resultantes son tipos anónimos que constan del nombre del departamento y el nombre del profesor que dirige el departamento.
var query = from department in departments
join teacher in teachers on department.TeacherID equals teacher.ID
select new
{
DepartmentName = department.Name,
TeacherName = $"{teacher.First} {teacher.Last}"
};
foreach (var departmentAndTeacher in query)
{
Console.WriteLine($"{departmentAndTeacher.DepartmentName} is managed by {departmentAndTeacher.TeacherName}");
}
Para lograr los mismos resultados, use la sintaxis del Join método :
var query = teachers
.Join(departments, teacher => teacher.ID, department => department.TeacherID,
(teacher, department) =>
new { DepartmentName = department.Name, TeacherName = $"{teacher.First} {teacher.Last}" });
foreach (var departmentAndTeacher in query)
{
Console.WriteLine($"{departmentAndTeacher.DepartmentName} is managed by {departmentAndTeacher.TeacherName}");
}
Los profesores que no son jefes de departamento no aparecen en los resultados finales.
Combinación de clave compuesta
En lugar de correlacionar elementos basados en una sola propiedad, use una clave compuesta para comparar elementos basados en varias propiedades. Especifique la función del selector de claves de cada colección para que devuelva un tipo anónimo que conste de las propiedades que quiere comparar. Si etiqueta las propiedades, deben tener la misma etiqueta de tipo anónimo en cada clave. Las propiedades también deben aparecer en el mismo orden.
En el ejemplo siguiente se usa una lista de objetos Teacher y una lista de objetos Student para determinar qué profesores son también alumnos. Ambos tipos tienen propiedades que representan el nombre y el apellido de cada persona. Las funciones que crean las claves de combinación a partir de los elementos de cada lista devuelven un tipo anónimo que consta de las propiedades. La operación de unión compara estas claves compuestas para determinar la igualdad y devuelve pares de objetos de cada lista donde coinciden tanto el nombre como el apellido.
// Join the two data sources based on a composite key consisting of first and last name,
// to determine which employees are also students.
IEnumerable<string> query =
from teacher in teachers
join student in students on new
{
FirstName = teacher.First,
LastName = teacher.Last
} equals new
{
student.FirstName,
student.LastName
}
select teacher.First + " " + teacher.Last;
string result = "The following people are both teachers and students:\r\n";
foreach (string name in query)
{
result += $"{name}\r\n";
}
Console.Write(result);
Puede usar el método Join, tal y como se muestra en el siguiente ejemplo:
IEnumerable<string> query = teachers
.Join(students,
teacher => new { FirstName = teacher.First, LastName = teacher.Last },
student => new { student.FirstName, student.LastName },
(teacher, student) => $"{teacher.First} {teacher.Last}"
);
Console.WriteLine("The following people are both teachers and students:");
foreach (string name in query)
{
Console.WriteLine(name);
}
Combinación múltiple
Puede anexar cualquier número de operaciones de combinación para realizar una combinación múltiple. Cada cláusula join de C# correlaciona un origen de datos especificado con los resultados de la combinación anterior.
La primera cláusula join correlaciona los alumnos y los departamentos en función de la coincidencia del valor Student de un objeto DepartmentID con el valor Department de un objeto ID. Devuelve una secuencia de tipos anónimos que contienen los objetos Student y Department.
La segunda cláusula join correlaciona los tipos anónimos devueltos por la primera unión con objetos Teacher, basándose en que el identificador del profesor coincida con el identificador del jefe del departamento. Devuelve una secuencia de tipos anónimos que contienen el nombre del alumno, el nombre del departamento y el nombre del jefe del departamento. Dado que esta operación es una combinación interna, la consulta devuelve solo los objetos del primer origen de datos que tienen una coincidencia en el segundo origen de datos.
// The first join matches Department.ID and Student.DepartmentID from the list of students and
// departments, based on a common ID. The second join matches teachers who lead departments
// with the students studying in that department.
var query = from student in students
join department in departments on student.DepartmentID equals department.ID
join teacher in teachers on department.TeacherID equals teacher.ID
select new {
StudentName = $"{student.FirstName} {student.LastName}",
DepartmentName = department.Name,
TeacherName = $"{teacher.First} {teacher.Last}"
};
foreach (var obj in query)
{
Console.WriteLine($"""The student "{obj.StudentName}" studies in the department run by "{obj.TeacherName}".""");
}
La consulta equivalente que usa varios Join métodos usa el mismo enfoque con el tipo anónimo:
var query = students
.Join(departments, student => student.DepartmentID, department => department.ID,
(student, department) => new { student, department })
.Join(teachers, commonDepartment => commonDepartment.department.TeacherID, teacher => teacher.ID,
(commonDepartment, teacher) => new
{
StudentName = $"{commonDepartment.student.FirstName} {commonDepartment.student.LastName}",
DepartmentName = commonDepartment.department.Name,
TeacherName = $"{teacher.First} {teacher.Last}"
});
foreach (var obj in query)
{
Console.WriteLine($"""The student "{obj.StudentName}" studies in the department run by "{obj.TeacherName}".""");
}
Combinación interna mediante combinación agrupada
En el ejemplo siguiente se muestra cómo implementar una combinación interna mediante una combinación de grupo. La lista de objetos Department forma una combinación agrupada con la lista de objetos Student según el Department.ID que coincide con la propiedad Student.DepartmentID. La combinación de grupo crea una colección de grupos intermedios, donde cada grupo consta de un objeto Department y una secuencia de objetos Student coincidentes. La segunda cláusula from combina (o acopla) esta secuencia de secuencias en una secuencia más larga. La cláusula select especifica el tipo de elementos de la secuencia final. Ese tipo es un tipo anónimo que consta del nombre del alumno y del nombre del departamento coincidente.
var query1 =
from department in departments
join student in students on department.ID equals student.DepartmentID into gj
from subStudent in gj
select new
{
DepartmentName = department.Name,
StudentName = $"{subStudent.FirstName} {subStudent.LastName}"
};
Console.WriteLine("Inner join using GroupJoin():");
foreach (var v in query1)
{
Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}
Puede lograr los mismos resultados mediante el GroupJoin método , como se muestra en el ejemplo siguiente:
var queryMethod1 = departments
.GroupJoin(students, department => department.ID, student => student.DepartmentID,
(department, gj) => new { department, gj })
.SelectMany(departmentAndStudent => departmentAndStudent.gj,
(departmentAndStudent, subStudent) => new
{
DepartmentName = departmentAndStudent.department.Name,
StudentName = $"{subStudent.FirstName} {subStudent.LastName}"
});
Console.WriteLine("Inner join using GroupJoin():");
foreach (var v in queryMethod1)
{
Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}
El resultado es equivalente al conjunto de resultados obtenido mediante la cláusula join sin la cláusula into para realizar una combinación interna. En el código siguiente se muestra esta consulta equivalente:
var query2 = from department in departments
join student in students on department.ID equals student.DepartmentID
select new
{
DepartmentName = department.Name,
StudentName = $"{student.FirstName} {student.LastName}"
};
Console.WriteLine("The equivalent operation using Join():");
foreach (var v in query2)
{
Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}
Para evitar el encadenamiento, use el método único Join como se muestra aquí:
var queryMethod2 = departments.Join(students, departments => departments.ID, student => student.DepartmentID,
(department, student) => new
{
DepartmentName = department.Name,
StudentName = $"{student.FirstName} {student.LastName}"
});
Console.WriteLine("The equivalent operation using Join():");
foreach (var v in queryMethod2)
{
Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}
Realizar combinaciones agrupadas
La unión de grupo es útil para generar estructuras de datos jerárquicas. Empareja cada elemento de la primera colección con un conjunto de elementos correlacionados de la segunda colección.
Nota:
Cada elemento de la primera colección aparece en el conjunto de resultados de una combinación de grupo, independientemente de si los elementos correlacionados se encuentran en la segunda colección. Si no se encuentra ningún elemento correlacionado, la secuencia de elementos correlacionados para ese elemento está vacía. Por consiguiente, el selector de resultados tiene acceso a cada uno de los elementos de la primera colección. Este comportamiento difiere del selector de resultados en una combinación no grupal, que no puede acceder a los elementos de la primera colección que no tienen ninguna coincidencia en la segunda colección.
Advertencia
Enumerable.GroupJoin no tiene ningún equivalente directo en términos de base de datos relacional tradicional. Sin embargo, este método implementa un superconjunto de combinaciones internas y combinaciones externas izquierdas. Ambas operaciones se pueden escribir en términos de una combinación agrupada. Para más información, consulte Entity Framework Core, GroupJoin.
En el primer ejemplo de este artículo se muestra cómo realizar una unión a un grupo. En el segundo ejemplo se muestra cómo usar una combinación de grupo para crear elementos XML.
Unirse a un grupo
En el ejemplo siguiente se realiza una combinación de grupo de objetos de tipo Department y Student en función del Department.ID que coincida con la propiedad Student.DepartmentID. A diferencia de una combinación que no es de grupo, que genera un par de elementos para cada coincidencia, la combinación de grupo genera solo un objeto resultante para cada elemento de la primera colección. En este ejemplo, la primera colección es un Department objeto . Los elementos correspondientes de la segunda colección, que en este ejemplo son objetos Student, se agrupan en una colección. Por último, la función de selector de resultados crea un tipo anónimo para cada coincidencia formada por Department.Name y una colección de objetos Student.
var query = from department in departments
join student in students on department.ID equals student.DepartmentID into studentGroup
select new
{
DepartmentName = department.Name,
Students = studentGroup
};
foreach (var v in query)
{
// Output the department's name.
Console.WriteLine($"{v.DepartmentName}:");
// Output each of the students in that department.
foreach (Student? student in v.Students)
{
Console.WriteLine($" {student.FirstName} {student.LastName}");
}
}
En el ejemplo anterior, la query variable contiene la consulta que crea una lista donde cada elemento es un tipo anónimo que contiene el nombre del departamento y una colección de alumnos que estudian en ese departamento.
La consulta equivalente mediante la sintaxis del método se muestra en el código siguiente:
var query = departments.GroupJoin(students, department => department.ID, student => student.DepartmentID,
(department, Students) => new { DepartmentName = department.Name, Students });
foreach (var v in query)
{
// Output the department's name.
Console.WriteLine($"{v.DepartmentName}:");
// Output each of the students in that department.
foreach (Student? student in v.Students)
{
Console.WriteLine($" {student.FirstName} {student.LastName}");
}
}
Unirse a un grupo para crear XML
Las combinaciones agrupadas resultan ideales para crear XML con LINQ to XML. El siguiente ejemplo es similar al anterior, pero en lugar de crear tipos anónimos, la función de selector de resultados crea elementos XML que representan los objetos combinados.
XElement departmentsAndStudents = new("DepartmentEnrollment",
from department in departments
join student in students on department.ID equals student.DepartmentID into studentGroup
select new XElement("Department",
new XAttribute("Name", department.Name),
from student in studentGroup
select new XElement("Student",
new XAttribute("FirstName", student.FirstName),
new XAttribute("LastName", student.LastName)
)
)
);
Console.WriteLine(departmentsAndStudents);
La consulta equivalente mediante la sintaxis del método se muestra en el código siguiente:
XElement departmentsAndStudents = new("DepartmentEnrollment",
departments.GroupJoin(students, department => department.ID, student => student.DepartmentID,
(department, Students) => new XElement("Department",
new XAttribute("Name", department.Name),
from student in Students
select new XElement("Student",
new XAttribute("FirstName", student.FirstName),
new XAttribute("LastName", student.LastName)
)
)
)
);
Console.WriteLine(departmentsAndStudents);
Realizar uniones externas
.NET 10 incluye LeftJoin y RightJoin en las clases System.Linq.Enumerable y System.Linq.Queryable. Estos métodos realizan una equijoína izquierda externa y una equijoína derecha externa, respectivamente. Una equijoína izquierda externa es una combinación en la que todos los miembros de la primera secuencia se incluyen en la secuencia de salida, aunque la segunda secuencia no incluya una coincidencia. Una equijoína derecha externa es una combinación en la que cada miembro de la segunda secuencia se incluye en la secuencia de salida, aunque la primera secuencia no incluya una coincidencia.
Emular un join externo izquierdo
Antes de .NET 10, use LINQ para realizar una unión externa izquierda llamando al método DefaultIfEmpty en los resultados de una unión de grupo.
En el ejemplo siguiente se muestra cómo usar el método DefaultIfEmpty en los resultados de una combinación agrupada para realizar una combinación externa izquierda.
El primer paso para generar una combinación externa izquierda de dos colecciones consiste en realizar una combinación interna usando una combinación agrupada. (Vea Realizar combinaciones internas para obtener una explicación de este proceso). En este ejemplo, la lista de objetos Department está unida mediante combinación interna a la lista de objetos Student basándose en el id. de un objeto Department que coincide con el elemento DepartmentID.
El segundo paso consiste en incluir cada elemento de la primera colección (izquierda) en el conjunto de resultados, incluso cuando no haya coincidencias en la colección derecha. Realice este paso llamando a DefaultIfEmpty en cada secuencia de elementos coincidentes de la unión de grupos. En este ejemplo, se invoca DefaultIfEmpty en cada secuencia de objetos Student coincidentes. El método devuelve una colección que contiene un único, valor predeterminado si la secuencia de objetos Student coincidentes está vacía para cualquier objeto Department, con lo que cada objeto Department se representa en la colección de resultados.
Nota:
El valor predeterminado para un tipo de referencia es null; por consiguiente, el ejemplo busca una referencia NULL antes de tener acceso a cada elemento de cada colección de Student.
var query =
from student in students
join department in departments on student.DepartmentID equals department.ID into gj
from subgroup in gj.DefaultIfEmpty()
select new
{
student.FirstName,
student.LastName,
Department = subgroup?.Name ?? string.Empty
};
foreach (var v in query)
{
Console.WriteLine($"{v.FirstName:-15} {v.LastName:-15}: {v.Department}");
}
La consulta equivalente mediante la sintaxis del método se muestra en el código siguiente:
var query = students
.GroupJoin(
departments,
student => student.DepartmentID,
department => department.ID,
(student, departmentList) => new { student, subgroup = departmentList })
.SelectMany(
joinedSet => joinedSet.subgroup.DefaultIfEmpty(),
(student, department) => new
{
student.student.FirstName,
student.student.LastName,
Department = department?.Name ?? string.Empty
});
foreach (var v in query)
{
Console.WriteLine($"{v.FirstName:-15} {v.LastName:-15}: {v.Department}");
}