1. Giriş
Bəzən bizə iki fərqli kolleksiya ilə işləmək lazım olur və onlar hansısa ümumi əlamətə görə bir-biri ilə bağlıdır. Məsələn, bizdə sifarişlərin siyahısı və ayrıca alıcılar kolleksiyası var. Necə bilək ki, hansı sifariş hansı alıcıya məxsusdur? Bunun üçün hər iki kolleksiya ümumi identifikatorla — məsələn, userId ilə bağlı olmalıdır.
Relational database-lərdə bu işi JOIN əməliyyatı görür, hansı ki, müxtəlif cədvəllərdən olan sətirləri uyğun açarlara görə birləşdirir. LINQ-də də eyni adlı operator var — Join.
Təsəvvür elə iki cədvəl var: birincidə — kitabxananın oxucularının siyahısı və onların identifikatorları, ikincidə isə kitab sifarişlərinin siyahısı, burada hər sifarişdə oxucunun id-si göstərilib. join ilə biz bu cədvəlləri id üzrə "yapışdırırıq" və nəticədə "oxucu + onun sifarişi" cütlərini alırıq.
Yeri gəlmişkən, əgər sən düşünürdün ki, join yalnız "baza üçün" lazımdır, çox şey itirmisən! Proqramlaşdırmada kolleksiyaları birləşdirmək tez-tez lazım olur, xüsusilə də xarici sistemlərlə və ya strukturlaşdırılmış fayllarla (JSON, XML və ya mühasibatdan gələn Excel cədvəlləri) işləyəndə.
2. Join metodunun signaturası və iş prinsipi
Join metodu belə görünür:
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer, // birinci kolleksiya (xarici)
IEnumerable<TInner> inner, // ikinci kolleksiya (daxili)
Func<TOuter, TKey> outerKeySelector, // xarici kolleksiyadan açarı necə almaq
Func<TInner, TKey> innerKeySelector, // daxili kolleksiyadan açarı necə almaq
Func<TOuter, TInner, TResult> resultSelector) // nəticə generasiya edən funksiya (yeni element)
İlk baxışda bir az qarışıq görünə bilər, amma indi hər şeyi rahat başa salacam.
Necə işləyir:
Birinci kolleksiyadakı (outer) hər element üçün LINQ ikinci kolleksiyadan (inner) uyğun (açarı eyni olan) elementləri axtarır. Açarlar üst-üstə düşəndə resultSelector çağırılır və nəticə yekun kolleksiyaya əlavə olunur.
3. Nümunə: Alıcıları və onların sifarişlərini birləşdiririk
Gəlin bir neçə class və kolleksiya yaradaq. Bu ideya — bizim tədris tətbiqimizin məntiqi davamıdır (tutaq ki, kurs boyu hazırladığımız balaca bir mağazadır).
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public string Product { get; set; } = "";
}
// Alıcılar kolleksiyası
var customers = new List<Customer>
{
new Customer { Id = 1, Name = "Vasja" },
new Customer { Id = 2, Name = "Petja" },
new Customer { Id = 3, Name = "Maşa" },
};
// Sifarişlər kolleksiyası
var orders = new List<Order>
{
new Order { Id = 101, CustomerId = 2, Product = "Kitab" },
new Order { Id = 102, CustomerId = 1, Product = "Qələm" },
new Order { Id = 103, CustomerId = 2, Product = "Dəftər" },
new Order { Id = 104, CustomerId = 3, Product = "Pozan" },
};
İndi biz bütün alıcıları onların sifarişləri ilə birlikdə almaq istəyirik. Məsələn, belə çıxarmaq: "Petja sifariş etdi Kitab", "Petja sifariş etdi Dəftər" və s.
Join tətbiq edirik
// Alıcıları sifarişlərlə customer.Id və order.CustomerId üzrə birləşdiririk
var query = customers.Join(
orders,
customer => customer.Id, // Alıcıdan açarı necə almaq
order => order.CustomerId, // Sifarişdən açarı necə almaq
(customer, order) => new // Uyğun cütlərlə nə etmək (yeni obyekt yaradırıq)
{
CustomerName = customer.Name,
Product = order.Product
}
);
// Nəticəni çıxarırıq
foreach (var item in query)
{
Console.WriteLine($"{item.CustomerName} sifariş etdi {item.Product}");
}
Nəticədə çıxacaq:
Vasja sifariş etdi Qələm
Petja sifariş etdi Kitab
Petja sifariş etdi Dəftər
Maşa sifariş etdi Pozan
Diqqət elə: əgər alıcının bir neçə sifarişi varsa, o siyahıda bir neçə dəfə çıxacaq — hər sifariş üçün bir dəfə. Elə belə də olmalıdır!
4. Cədvəl: Join və GroupBy + SelectMany müqayisəsi
| Əməliyyat | Nəticə | İstifadə ssenarisi |
|---|---|---|
|
Cütlüklərin düz siyahısı | Klassik SQL JOIN. Hər cütlük (uyğunluq) — ayrıca sətr. |
|
Qruplar və alt-kolleksiyalar | "Alıcı + bütün sifarişləri" strukturunu "bir — birçox" kimi almaq istəyirsən. |
Yeni başlayanlar tez-tez Join istifadə edirlər, halbuki onlara "alıcı → sifarişlərin siyahısı" strukturu lazımdır. Amma Join sətr-sətr işləyir və məlumatları qruplaşdırmır. Bunun üçün GroupJoin var — bu barədə növbəti dərsdə danışacağıq. Və ya GroupBy istifadə etmək olar.
5. Join Query Syntax-da (LINQ-in SQL-stili)
LINQ iki sintaksisi dəstəkləyir: metod-zənciri (Method Syntax, məsələn Join(...)) və SQL-ə bənzər stil (Query Syntax), hansı ki, vizual olaraq adi SQL sorğularına bənzəyir.
Bəzi proqramçılar üçün — xüsusilə əvvəllər database-lərlə işləyənlər üçün — bu sintaksis daha aydın olur:
var query2 =
from customer in customers
join order in orders
on customer.Id equals order.CustomerId
select new
{
CustomerName = customer.Name,
Product = order.Product
};
foreach (var item in query2)
{
Console.WriteLine($"{item.CustomerName} sifariş etdi {item.Product}");
}
Diqqət elə:
Query Syntax-da equals açar sözü istifadə olunur — == operatoru burada işləmir!
Bu, tez-tez müsahibələrdə tələyə çevrilir, xüsusilə yeni başlayanlar üçün 😉
6. Vacib detallar və gizli tələlər
Bəzən elə olur ki, kolleksiyalardakı bütün elementlər yekun nəticəyə düşmür. Bunun səbəbi odur ki, Join metodu belə adlanan «inner join» həyata keçirir, yəni yalnız açarları üst-üstə düşən elementləri birləşdirir. Əgər hansısa alıcının sifarişi yoxdursa, o yekun siyahıda olmayacaq. Eyni ilə, əgər sifarişdə CustomerId var, amma belə alıcı yoxdur, o sifariş də nəticəyə düşməyəcək.
Bəs necə edək ki, bütün alıcılar çıxsın, hətta sifarişi olmayanlar da? Belə hallarda "sol" join lazımdır, hansı ki, LINQ-də GroupJoin və SelectMany kombinasiyası ilə edilir. Bu barədə növbəti dərsdə danışacağıq. Klassik Join-də isə mütləqdir ki, hər iki tərəfdə uyğun element olsun — yoxsa uyğunluq olmayacaq və element nəticədən çıxacaq.
7. Bir neçə açarla işləmək (composite key)
Bəzən kolleksiyaları bir yox, bir neçə sahə üzrə birləşdirmək lazımdır. Məsələn: məhsulları və onların satışlarını "Məhsul kodu + Satış ili" üzrə birləşdirmək.
LINQ-də bu, açar kimi anonim obyektlər yaratmaqla həll olunur:
var sales = ...; // satışlar
var products = ...; // məhsullar
var query = products.Join(
sales,
prod => new { prod.Code, prod.Year },
sale => new { sale.ProductCode, sale.Year },
(prod, sale) => new { prod.Name, sale.Amount }
);
Vacibdir ki, bu anonim obyektlərdə property-lərin tipləri və adları eyni olsun. Əgər fərqlidirsə, heç bir uyğunluq tapılmayacaq — hətta mənaca eyni olsalar belə.
8. Birləşdirilmiş kolleksiya necə görünür — sxem
Budur, join-in işini göstərən sxem (çox sadələşdirilmiş):
flowchart LR
subgraph Customers
A1["Vasja (Id=1)"]
A2["Petja (Id=2)"]
A3["Maşa (Id=3)"]
end
subgraph Orders
B1["Qələm (CustomerId=1)"]
B2["Kitab (CustomerId=2)"]
B3["Dəftər (CustomerId=2)"]
B4["Pozan (CustomerId=3)"]
end
A1-->|Id=1|B1
A2-->|Id=2|B2
A2-->|Id=2|B3
A3-->|Id=3|B4
Sxemdən görünür ki, hər alıcı bütün sifarişləri ilə birləşir — açarlar uyğun gəlirsə.
9. Qısa olaraq səhvlər və xüsusiyyətlər
Ən çox rast gəlinən səhvlərdən biri — parametrlərin sırasını qarışdırmaqdır, xüsusilə də hər iki kolleksiyada açarların tipləri eynidirsə. Bu halda nə compiler, nə də LINQ səhv verməyəcək, amma nəticə boş və ya gözlənilməz ola bilər.
Daha bir yayılmış tələ — Join ilə "sol" birləşmə etməyə çalışmaqdır, yəni nəticədə bütün alıcılar qalsın, hətta sifarişi olmayanlar da. Amma klassik Join yalnız uyğun cütlüklərlə işləyir və uyğunluğu olmayan elementləri daxil etmir.
GO TO FULL VERSION