Join LINQ'teki işlemler

İki veri kaynağının birleşimi, bir veri kaynağındaki nesnelerin başka bir veri kaynağındaki ortak özniteliği paylaşan nesnelerle ilişkisidir.

Joining, birbirleriyle ilişkileri doğrudan izlenemeyen veri kaynaklarını hedefleyen sorgularda önemli bir işlemdir. Nesne odaklı programlamada birleştirme, tek yönlü ilişkinin geriye dönük yönü gibi modellenmemiş nesneler arasında bağıntı anlamına gelebilir. Tek yönlü ilişki örneği, Student birincil öğeyi temsil eden türünde Department bir özelliği olan ancak sınıfın Department nesne koleksiyonu Student olan bir özelliği olmayan bir sınıftır. Nesnelerin bir listesi Department varsa ve her bölümdeki tüm öğrencileri bulmak istiyorsanız, bunları bulmak için birleştirme işlemini kullanabilirsiniz.

LINQ çerçevesinde sağlanan birleştirme yöntemleri ve GroupJoinşeklindedirJoin. Bu yöntemler, anahtarlarının eşitliğine bağlı olarak iki veri kaynağıyla eşleşen eş birleşimler veya birleşimler gerçekleştirir. (Karşılaştırma için Transact-SQL, işleç gibi dışında equalsbirleştirme işleçlerini less than destekler.) İlişkisel veritabanı terimlerinde, Join yalnızca diğer veri kümesinde eşleşmesi olan nesnelerin döndürüldiği bir birleştirme türü olan bir iç birleşim uygular. yönteminin GroupJoin ilişkisel veritabanı terimlerinde doğrudan eşdeğeri yoktur, ancak iç birleşimlerin ve sol dış birleşimlerin üst kümesini uygular. Sol dış birleşim, diğer veri kaynağında bağıntılı öğe olmasa bile ilk (sol) veri kaynağının her öğesini döndüren bir birleşimdir.

Aşağıdaki çizimde, iki kümenin kavramsal görünümü ve bu kümelerdeki iç birleşimde veya sol dış birleşimde yer alan öğeler gösterilmektedir.

Two overlapping circles showing inner/outer.

Yöntemler

Yöntem Adı Açıklama C# Sorgu İfadesi Söz Dizimi Daha Fazla Bilgi
Join Joins anahtar seçici işlevlerini temel alan iki dizi ve değer çiftlerini ayıklar. join … in … on … equals … Enumerable.Join

Queryable.Join
GroupJoin Joins anahtar seçici işlevlerine dayalı iki dizi ve her öğe için sonuçta elde edilen eşleşmeleri gruplandırma. join … in … on … equals … into … Enumerable.GroupJoin

Queryable.GroupJoin

Bu makaledeki aşağıdaki örneklerde bu alan için ortak veri kaynakları kullanılır:

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; }
}

Her Student birinin bir not düzeyi, bir birincil bölüm ve bir dizi puanı vardır. Ayrıca, Teacher öğretmenin ders aldığı kampüsü tanımlayan bir City özelliği de vardır. A'nın Department bir adı ve bölüm başkanı olarak görev yapan bir Teacher kişi için bir referansı vardır.

Aşağıdaki örnek, belirli bir değere göre iki diziyi birleştirmek için yan tümcesini join … in … on … equals … kullanır:

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}");
}

Yukarıdaki sorgu, aşağıdaki kodda gösterildiği gibi yöntem söz dizimi kullanılarak ifade edilebilir:

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}");
}

Aşağıdaki örnek, belirli bir değere göre iki diziyi birleştirmek ve sonuçta elde edilen eşleşmeleri her öğe için gruplandırmak için yan tümcesini kullanır join … in … on … equals … into … :

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}");
    }
}

Yukarıdaki sorgu, aşağıdaki örnekte gösterildiği gibi yöntem söz dizimi kullanılarak ifade edilebilir:

// 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}");
    }
}

İç birleşimler gerçekleştirme

İlişkisel veritabanı terimlerinde iç birleşim, ilk koleksiyonun her öğesinin ikinci koleksiyondaki eşleşen her öğe için bir kez göründüğü bir sonuç kümesi oluşturur. İlk koleksiyondaki bir öğenin eşleşen öğesi yoksa sonuç kümesinde görünmez. Join C# içindeki yan tümcesi join tarafından çağrılan yöntemi bir iç birleşim uygular. Aşağıdaki örneklerde, bir iç birleşimin dört varyasyonunu nasıl gerçekleştirebileceğiniz gösterilmektedir:

  • Basit bir anahtara dayalı olarak iki veri kaynağındaki öğeleri ilişkilendiren basit bir iç birleşim.
  • bileşik anahtara dayalı olarak iki veri kaynağındaki öğeleri ilişkilendiren iç birleşim. Birden fazla değerden oluşan bir anahtar olan bileşik anahtar, birden fazla özelliğe göre öğeleri ilişkilendirmenizi sağlar.
  • Birbirini izleyen birleştirme işlemlerinin birbirine eklendiği çoklu birleştirme.
  • Grup birleştirme kullanılarak uygulanan iç birleşim.

Tek tuşla birleştirme

Aşağıdaki örnek, nesneleri ile eşleşen nesnelerle eşleştirir TeacherTeacher.DeparmentTeacherId select C# içindeki yan tümcesi, sonuçta elde edilen nesnelerin nasıl görüneceğini tanımlar. Aşağıdaki örnekte, elde edilen nesneler departman adından ve bölüme liderlik eden öğretmenin adından oluşan anonim türlerdir.

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}");
}

Yöntem söz dizimini Join kullanarak aynı sonuçları elde edebilirsiniz:

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}");
}

Bölüm başkanı olmayan öğretmenler nihai sonuçlarda görünmez.

Bileşik anahtar birleştirme

Öğeleri tek bir özelliğe göre ilişkilendirmek yerine, birden çok özelliğe göre öğeleri karşılaştırmak için bileşik anahtar kullanabilirsiniz. Karşılaştırmak istediğiniz özelliklerden oluşan anonim bir tür döndürmek için her koleksiyon için anahtar seçici işlevini belirtin. Özellikleri etiketlerseniz, her anahtarın anonim türünde aynı etikete sahip olmaları gerekir. Özelliklerin de aynı sırada görünmesi gerekir.

Aşağıdaki örnek, hangi öğretmenlerin Teacher de öğrenci olduğunu belirlemek için nesnelerin listesini ve nesnelerin listesini Student kullanır. Bu türlerin her ikisi de her bir kişinin adını ve aile adını temsil eden özelliklere sahiptir. Her listenin öğelerinden birleştirme anahtarlarını oluşturan işlevler, özelliklerden oluşan anonim bir tür döndürür. Birleştirme işlemi bu bileşik anahtarları eşitlik için karşılaştırır ve her listeden hem adın hem de aile adının eşleştiği nesne çiftlerini döndürür.

// 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);

Aşağıdaki örnekte gösterildiği gibi yöntemini kullanabilirsiniz Join :

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);
}

Birden çok birleşim

Birden çok birleştirme gerçekleştirmek için herhangi bir sayıda birleştirme işlemi birbirine eklenebilir. C# içindeki her join yan tümce, belirtilen bir veri kaynağını önceki birleştirmenin sonuçlarıyla ilişkilendirmektedir.

birinci join yan tümce, bir Student nesnenin bir nesnenin ile eşleşen Department öğesine DepartmentID göre öğrencilerle ve bölümlerle IDeşleşir. Nesneyi ve Department nesneyi içeren anonim türlerin bir dizisini Student döndürür.

İkinci join yan tümce, ilk birleşim tarafından döndürülen anonim türleri, bölüm baş kimliğiyle eşleşen öğretmenin kimliğine göre nesnelerle Teacher ilişkilendirmektedir. Öğrencinin adını, bölüm adını ve bölüm liderinin adını içeren anonim türlerden oluşan bir dizi döndürür. Bu işlem bir iç birleşim olduğundan, yalnızca ikinci veri kaynağında eşleşmesi olan ilk veri kaynağındaki nesneler döndürülür.

// 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}".""");
}

Birden çok Join yöntem kullanan eşdeğer, anonim türle aynı yaklaşımı kullanır:

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}".""");
}

Gruplandırılmış birleştirme kullanarak iç birleşim

Aşağıdaki örnekte, grup birleştirme kullanarak iç birleştirmenin nasıl uygulanacağınız gösterilmektedir. Nesne listesiDepartment, eşleşen Student.DepartmentID özelliğe göre Department.ID nesne listesine Student grup olarak katılır. Grup birleştirme, her grubun bir Department nesneden ve eşleşen Student nesne dizilerinden oluştuğu bir ara grup koleksiyonu oluşturur. İkinci from yan tümce, bu dizi dizisini birleştirir (veya düzleştirir) daha uzun bir dizide. select yan tümcesi, son dizideki öğelerin türünü belirtir. Bu tür, öğrencinin adından ve eşleşen bölüm adından oluşan anonim bir türdür.

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}");
}

Yöntem kullanılarak GroupJoin aşağıdaki gibi aynı sonuçlar elde edilebilir:

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}");
}

Sonuç, iç birleşim gerçekleştirmek için yan tümcesi olmadan yan tümcesi joininto kullanılarak elde edilen sonuç kümesine eşdeğerdir. Aşağıdaki kod bu eşdeğer sorguyu gösterir:

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}");
}

Zincirlemeden kaçınmak için tek Join yöntem burada gösterildiği gibi kullanılabilir:

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}");
}

Gruplanmış birleşimler gerçekleştirme

Grup birleştirme, hiyerarşik veri yapıları oluşturmak için kullanışlıdır. İlk koleksiyondaki her öğeyi ikinci koleksiyondaki bağıntılı öğeler kümesiyle eşleştiriyor.

Not

İlk koleksiyonun her öğesi, bağıntılı öğelerin ikinci koleksiyonda bulunup bulunmadığına bakılmaksızın grup birleştirmenin sonuç kümesinde görünür. Bağıntılı öğe bulunamazsa, bu öğe için bağıntılı öğelerin dizisi boş olur. Bu nedenle sonuç seçici, ilk koleksiyonun her öğesine erişebilir. Bu, ikinci koleksiyonda eşleşmeyen ilk koleksiyondaki öğelere erişemeyen grup dışı birleştirmedeki sonuç seçiciden farklıdır.

Uyarı

Enumerable.GroupJoin geleneksel ilişkisel veritabanı terimlerinde doğrudan eşdeğeri yoktur. Ancak bu yöntem iç birleşimlerin ve sol dış birleşimlerin üst kümesini uygular. Bu işlemlerin her ikisi de gruplandırılmış birleştirme açısından yazılabilir. Daha fazla bilgi için bkz . Entity Framework Core, GroupJoin.

Bu makaledeki ilk örnekte grup birleştirme işleminin nasıl gerçekleştirebileceğiniz gösterilmektedir. İkinci örnekte, XML öğeleri oluşturmak için grup birleştirmenin nasıl kullanılacağı gösterilmektedir.

Grup birleştirme

Aşağıdaki örnek, türündeki Department nesnelerin grup birleştirmesini ve Student eşleşen Student.DepartmentID özelliğine göre Deoartment.ID gerçekleştirir. Her eşleşme için bir öğe çifti oluşturan grup dışı bir birleşimden farklı olarak, grup birleşimi ilk koleksiyonun her öğesi için yalnızca bir sonuç nesnesi oluşturur ve bu örnekte bir Department nesnedir. Bu örnekte Student nesneler olan ikinci koleksiyona karşılık gelen öğeler bir koleksiyonda gruplandırılır. Son olarak sonuç seçici işlevi, her eşleşme için ve bir nesne koleksiyonundan Student oluşan Department.Name anonim bir tür oluşturur.

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}");
    }
}

Yukarıdaki örnekte değişken, query her öğenin bölümün adını ve o bölümde okuyan öğrenci koleksiyonunu içeren anonim bir tür olduğu bir liste oluşturan sorguyu içerir.

Yöntem söz dizimini kullanan eşdeğer sorgu aşağıdaki kodda gösterilir:

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}");
    }
}

XML oluşturmak için grup birleştirme

Grup birleştirmeleri, LINQ to XML kullanarak XML oluşturmak için idealdir. Aşağıdaki örnek önceki örneğe benzer, ancak sonuç seçici işlevi anonim türler oluşturmak yerine birleştirilen nesneleri temsil eden XML öğeleri oluşturur.

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);

Yöntem söz dizimini kullanan eşdeğer sorgu aşağıdaki kodda gösterilir:

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);

Sol dış birleşimler gerçekleştirme

Sol dış birleşim, ikinci koleksiyonda herhangi bir bağıntılı öğe olup olmadığına bakılmaksızın, ilk koleksiyonun her öğesinin döndürüldiği bir birleşimdir. LinQ kullanarak grup birleştirmenin sonuçları üzerinde yöntemini çağırarak DefaultIfEmpty sol dış birleşim gerçekleştirebilirsiniz.

Aşağıdaki örnekte, sol dış birleşim gerçekleştirmek için grup birleştirmenin sonuçlarında yönteminin nasıl kullanılacağı DefaultIfEmpty gösterilmektedir.

İki koleksiyonun sol dış birleşimini oluşturmanın ilk adımı, grup birleştirmesi kullanarak iç birleşim gerçekleştirmektir. (Bkz.Bu işlemin açıklaması için iç birleşimler gerçekleştirin.) Bu örnekte, nesne listesiDepartment, öğrencinin DepartmentIDkimliğiyle eşleşen bir Department nesnenin Student kimliğine göre nesne listesiyle içsel olarak birleştirilir.

İkinci adım, ilk (sol) koleksiyonun her öğesini, bu öğenin doğru koleksiyonda eşleşmesi olmasa bile sonuç kümesine eklemektir. Bu, grup birleşiminden eşleşen öğelerin her dizisinde çağrılarak DefaultIfEmpty gerçekleştirilir. Bu örnekte, DefaultIfEmpty eşleşen Student nesnelerin her dizisinde çağrılır. yöntemi, eşleşen Student nesnelerin dizisi herhangi Department bir nesne için boşsa tek bir varsayılan değer içeren bir koleksiyon döndürür ve her Department nesnenin sonuç koleksiyonunda temsil edilmesini sağlar.

Not

Bir başvuru türü için varsayılan değerdir null; bu nedenle örnek, her Student koleksiyonun her öğesine erişmeden önce null başvuru olup olmadığını denetler.

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}");
}

Yöntem söz dizimini kullanan eşdeğer sorgu aşağıdaki kodda gösterilir:

var query = students.GroupJoin(departments, student => student.DepartmentID, department => department.ID,
    (student, department) => new { student, subgroup = department.DefaultIfEmpty() })
    .Select(gj => new
    {
        gj.student.FirstName,
        gj.student.LastName,
        Department = gj.subgroup?.FirstOrDefault()?.Name ?? string.Empty
    });

foreach (var v in query)
{
    Console.WriteLine($"{v.FirstName:-15} {v.LastName:-15}: {v.Department}");
}

Ayrıca bkz.