Proiecția datelor
Proiecția permite transformarea unui obiect de un anumit tip într-un obiect de alt tip. Pentru proiecție se folosește operatorul select. Să presupunem că avem un set de obiecte ale următoarei clase, care reprezintă un utilizator:
record class Person(string Name, int Age);
Dar, să presupunem că avem nevoie doar de proprietatea Name a obiectului:
var people = new List<Person>
{
new Person ("Tom", 23),
new Person ("Bob", 27),
new Person ("Sam", 29),
new Person ("Alice", 24)
};
var names = from p in people select p.Name;
foreach (string n in names)
Console.WriteLine(n);
Rezultatul expresiei LINQ va reprezenta un set de șiruri, deoarece expresia select p.Name alege în selecția rezultată doar valorile proprietății Name.
Tom
Bob
Sam
Alice
Alternativ, am putea folosi metoda de extensie Select():
Select(Func<TSource,TResult> selector)
Această metodă primește o funcție de transformare sub forma unui delegat Func<TSource,TResult>. Funcția de transformare primește fiecare obiect din selecția de tip TSource și cu ajutorul acestuia creează un obiect TResult. Metoda Select returnează o colecție de obiecte transformate.
Rescriem exemplul anterior folosind metoda Select:
var people = new List<Person>
{
new Person ("Tom", 23),
new Person ("Bob", 27),
new Person ("Sam", 29),
new Person ("Alice", 24)
};
var names = people.Select(u => u.Name);
foreach (string n in names)
Console.WriteLine(n);
În mod similar, putem crea obiecte de alt tip, inclusiv anonim:
var people = new List<Person>
{
new Person ("Tom", 23),
new Person ("Bob", 27)
};
var personel = from p in people
select new
{
FirstName = p.Name,
Year = DateTime.Now.Year - p.Age
};
foreach (var p in personel)
Console.WriteLine($"{p.FirstName} - {p.Year}");
record class Person(string Name, int Age);
Aici, operatorul select creează un obiect de tip anonim, folosind obiectul curent Person. Și acum, rezultatul va conține un set de obiecte de tip anonim, în care sunt definite două proprietăți: FirstName și Year (anul nașterii). Afișarea pe consolă a programului:
Tom - 1999
Bob - 1995
Alternativ, am putea folosi metoda de extensie Select():
// proiecția pe obiecte de tip anonim
var personel = people.Select(p => new
{
FirstName = p.Name,
Year = DateTime.Now.Year - p.Age
});
Variabile în interogări și operatorul let
Uneori apare necesitatea de a efectua calcule intermediare suplimentare în interogările LINQ. Pentru aceste scopuri, putem defini variabilele noastre în interogări folosind operatorul let:
var people = new List<Person>
{
new Person ("Tom", 23),
new Person ("Bob", 27)
};
var personnel = from p in people
let name = $"Mr. {p.Name}"
let year = DateTime.Now.Year - p.Age
select new
{
Name = name,
Year = year
};
foreach (var p in personnel)
Console.WriteLine($"{p.Name} - {p.Year}");
record class Person(string Name, int Age);
În acest caz, se creează două variabile. Variabila name, a cărei valoare este Mr. {p.Name}.
Posibilitatea de a defini variabile este probabil unul dintre principalele avantaje ale operatorilor LINQ în comparație cu metodele de extensie.
Selecție din mai multe surse
În LINQ putem selecta obiecte nu doar dintr-o singură sursă, ci și din mai multe surse. De exemplu, să luăm clasele:
record class Course(string Title); // curs de studiu
record class Student(string Name); // student
Clasa Course reprezintă un curs de studiu și stochează titlul acestuia. Clasa Student reprezintă un student și stochează numele acestuia.
Să presupunem că trebuie să obținem dintr-o listă de cursuri și o listă de studenți un set de perechi student-curs (să zicem entitatea care reprezintă studiul studentului la cursul respectiv):
var courses = new List<Course> { new Course("C#"), new Course("Java") };
var students = new List<Student> { new Student("Tom"), new Student("Bob") };
var enrollments = from course in courses // selectăm câte un curs
from student in students // selectăm câte un student
select new { Student = student.Name, Course = course.Title}; // combinăm fiecare student cu fiecare curs
foreach (var enrollment in enrollments)
Console.WriteLine($"{enrollment.Student} - {enrollment.Course}");
record class Course(string Title); // curs de studiu
record class Student(string Name); // student
Afișarea pe consolă:
Tom - C#
Bob - C#
Tom - Java
Bob - Java
Astfel, la selecția din două surse, fiecare element din prima sursă va fi combinat cu fiecare element din a doua sursă. Adică, vom obține 4 perechi.
SelectMany și unificarea obiectelor
Metoda SelectMany permite unificarea unui set de colecții într-o singură colecție. Are mai multe versiuni suprascrise. Să luăm una dintre ele:
SelectMany(Func<TSource, IEnumerable<TResult>> selector);
SelectMany(Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource,TCollection,TResult> resultSelector);
Prima versiune a metodei primește o funcție de transformare sub forma unui delegat Func<TSource,IEnumerable<TResult>>. Funcția de transformare primește fiecare obiect din selecție de tip TSource și cu ajutorul acestuia creează un set de obiecte TResult. Metoda SelectMany returnează o colecție de obiecte transformate.
A doua versiune primește o funcție de transformare sub forma unui delegat Func<TSource,IEnumerable<TResult>>. Funcția de transformare primește fiecare obiect din selecție de tip TSource și returnează o colecție intermediară de tip TCollection.
Al doilea parametru este tot o funcție de transformare sub forma unui delegat Func<TSource,TCollection,TResult>, care primește doi parametri - fiecare element al selecției curente și fiecare element al colecției intermediare și pe baza acestora creează un obiect de tip TResult.
Să analizăm următorul exemplu:
var companies = new List<Company>
{
new Company("Microsoft", new List<Person> {new Person("Tom"), new Person("Bob")}),
new Company("Google", new List<Person> {new Person("Sam"), new Person("Mike")}),
};
var employees = companies.SelectMany(c => c.Staff);
foreach (var emp in employees)
Console.WriteLine($"{emp.Name}");
record class Company(string Name, List<Person> Staff);
record class Person(string Name);
Aici avem o listă de companii, fiecare companie având un set de angajați sub forma unei liste de obiecte Person. Și la ieșire obținem o listă de angajați din toate companiile, adică, de fapt, o colecție de obiecte Person. Afișarea pe consolă:
Tom
Bob
Sam
Mike
Un exemplu similar folosind operatori LINQ:
var companies = new List<Company>
{
new Company("Microsoft", new List<Person> {new Person("Tom"), new Person("Bob")}),
new Company("Google", new List<Person> {new Person("Sam"), new Person("Mike")}),
};
var employees = from c in companies
from emp in c.Staff
select emp;
foreach (var emp in employees)
Console.WriteLine($"{emp.Name}");
record class Company(string Name, List<Person> Staff);
record class Person(string Name);
Acum să adăugăm la angajați și compania lor:
var companies = new List<Company>
{
new Company
("Microsoft", new List<Person> {new Person("Tom"), new Person("Bob")}),
new Company("Google", new List<Person> {new Person("Sam"), new Person("Mike")}),
};
var employees = companies.SelectMany(c => c.Staff,
(c, emp)=> new { Name = emp.Name, Company = c.Name });
foreach (var emp in employees)
Console.WriteLine($"{emp.Name} - {emp.Company}");
record class Company(string Name, List<Person> Staff);
record class Person(string Name);
Aici se folosește o altă versiune a metodei SelectMany. Primul delegat sub forma c => c.Staff creează o colecție intermediară - de fapt, doar returnează setul de angajați ai fiecărei companii.
Al doilea delegat - (c, emp) => new { Name = emp.Name, Company = c.Name } primește fiecare companie și fiecare element al colecției intermediare - obiectul Person și pe baza acestora creează un obiect anonim cu două proprietăți Name și Company. Afișarea pe consolă a programului:
Tom - Microsoft
Bob - Microsoft
Sam - Google
Mike - Google
Un exemplu similar folosind operatori de interogare:
var companies = new List<Company>
{
new Company("Microsoft", new List<Person> {new Person("Tom"), new Person("Bob")}),
new Company("Google", new List<Person> {new Person("Sam"), new Person("Mike")}),
};
var employees = from c in companies
from emp in c.Staff
select new { Name = emp.Name, Company = c.Name };
foreach (var emp in employees)
Console.WriteLine($"{emp.Name} - {emp.Company}");
record class Company(string Name, List<Person> Staff);
record class Person(string Name);