Aracılığıyla paylaş


Join LINQ'de işlemler

Birleştirme, bir veri kaynağındaki nesneleri başka bir veri kaynağındaki ortak özniteliği paylaşan nesnelerle ilişkilendirir.

Önemli

Bu örnekler bir System.Collections.Generic.IEnumerable<T> veri kaynağı kullanır. Veri kaynaklarını ve System.Linq.IQueryProviderkullanan System.Linq.IQueryable<T> veri kaynakları. İfade ağaçlarının izin verilen C# söz diziminde sınırlamaları vardır. Ayrıca EF CoreIQueryProviderher veri kaynağı daha fazla kısıtlama uygulayabilir. Veri kaynağınızın belgelerine bakın.

Birleştirme, birbiriyle ilişkilerini doğrudan izleyememenize neden olan 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çevesi birleştirme yöntemleri sağlar: Join ve GroupJoin. 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 equals dışında, less than operatörü gibi başka birleştirme işleçlerini de 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.

İç/dış gösteren iki örtüşen daire.

Yöntemler

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

Queryable.Join
GroupJoin Anahtar seçici işlevlerine göre iki diziyi birleştirir ve her öğe için sonuçta elde edilen eşleşmeleri gruplar. join … in … on … equals … into … Enumerable.GroupJoin

Queryable.GroupJoin
LeftJoin eşleşen anahtarlara göre iki dizinin öğelerini ilişkilendirir. Mevcut Değil Enumerable.LeftJoin

Queryable.LeftJoin
Sağ Katıl eşleşen anahtarlara göre iki dizinin öğelerini ilişkilendirir. Mevcut Değil Enumerable.RightJoin

Queryable.RightJoin

Not

Bu makaledeki aşağıdaki örneklerde bu alan için ortak veri kaynakları kullanılır.
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.
Örnek veri kümesini kaynak depoda bulabilirsiniz.

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

Not

Standart Sorgu İşleçlerine Genel Bakış makalesinde bu alanın ortak veri kaynaklarına bakabilirsiniz.

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

Aşağıdaki kodda gösterildiği gibi yöntem söz dizimini kullanarak önceki sorguyu ifade edebilirsiniz:

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 dayalı olarak iki diziyi birleştirmek ve her öğe için elde edilen eşleşmeleri 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}");
    }
}

Aşağıdaki örnekte gösterildiği gibi yöntem söz dizimini kullanarak önceki sorguyu ifade edebilirsiniz:

// 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 yöntem, C# içindeki join yan tümcesinin çağırdığı ve bir iç birleşim uygulayan yöntemdir. 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şlemlerini art arda eklediğiniz bir çoklu birleştirme.
  • Grup birleştirme kullanan iç birleşim.

Tek tuşla birleştirme

Aşağıdaki örnek, nesneleri ile eşleşen nesnelerle eşleştirir TeacherDepartment.TeacherIdTeacher 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 kullanın. 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 istediğiniz sayıda birleştirme işlemi ekleyebilirsiniz. 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 DepartmentID öğesine Department göre öğrencilerle ve bölümlerle IDeşleşir. Nesneyi ve Student nesneyi içeren anonim türlerin bir dizisini Department döndürür.

İkinci join koşul, ilk birleştirme tarafından döndürülen anonim türleri, öğretmen kimliği bölüm başkanı kimliğiyle eşleştiğinde Teacher nesnelerle ilişkilendirir. Öğ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, sorgu yalnızca ikinci veri kaynağında eşleşmesi olan ilk veri kaynağındaki nesneleri döndürü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 sorgu, 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ştirmesi kullanarak iç birleştirmenin nasıl uygulandığı gösterilmektedir. Nesne listesiDepartment, eşleşen Student özelliğe göre Department.ID nesne listesine Student.DepartmentID 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}");
}

Aşağıdaki örnekte gösterildiği gibi GroupJoin yöntemini kullanarak aynı sonuçları elde edebilirsiniz.

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ç, join yan tümcesi kullanılarak, into yan tümcesi olmadan iç birleşim gerçekleştirilerek 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 burada gösterildiği gibi tek Join bir yöntemi kullanın:

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 bulunmazsa, bu öğe için bağıntılı öğelerin dizisi boş olur. Bu nedenle sonuç seçici, ilk koleksiyonun her öğesine erişebilir. Bu davranış, ikinci koleksiyonda eşleşmeyen ilk koleksiyondaki öğelere erişemeyen grup dışı bir birleşimdeki 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 örnek, grup birleştirme işleminin nasıl gerçekleştirilacağını gösterir. İkinci örnekte, XML öğeleri oluşturmak için grup birleştirmenin nasıl kullanılacağı gösterilmektedir.

Gruba katılma

Aşağıdaki örnek, Department'nin Student özelliğiyle eşleşmesine dayalı olarak, Department.ID türündeki nesnelerin grup birleştirmesini gerçekleştirir. Her eşleşme için bir çift öğe oluşturan grup dışı birleşimden farklı olarak, grup birleşimi ilk koleksiyonun her öğesi için yalnızca bir sonuç nesnesi üretir. Bu örnekte, ilk koleksiyon 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 Department.Name oluşan Student 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 bu 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);

Dış birleşimler gerçekleştirme

.NET 10, LeftJoin ve RightJoin sınıflarındaki System.Linq.Enumerable ve System.Linq.Queryable yöntemlerini içerir. Bu yöntemler sırasıyla bir dış sol eşlemeli birleşim ve bir dış sağ eşlemeli birleşim gerçekleştirir. Dış sol eş birleşim, ikinci dizi bir eşleşme içermese bile, ilk dizinin her üyesinin çıkış dizisine dahil edildiği bir tür birleşimdir. Sağ dış eşleşmeli birleşim, ilk dizide eşleşme olmasa bile, ikinci dizinin her üyesinin sonuç dizisine dahil edildiği bir birleşimdir.

Sol dış birleşimi taklit et

.NET 10'dan önce, grup birleştirme sonuçları üzerinde DefaultIfEmpty yöntemini çağırarak sol dış birleşim gerçekleştirmek için LINQ kullanın.

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

İ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 Studentkimliğiyle eşleşen bir Department nesnenin DepartmentID 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. Grup birleştirme işlemi ile eşleşen öğelerin her dizisinde DefaultIfEmpty çağırarak bu adımı tamamlarsınız. Bu örnekte, eşleşen DefaultIfEmpty nesnelerin her dizisini Student çağırırsınız. 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, 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}");
}

Ayrıca bkz.