CodeGym /Kurslar /C# SELF /Qrup birləşməsi group join

Qrup birləşməsi group join ilə

C# SELF
Səviyyə , Dərs
Mövcuddur

1. Giriş

Təsəvvür elə klassik bir məsələ: səndə iki kolleksiya var və bunlar məntiqi olaraq bir-biri ilə bağlıdır. Məsələn, məhsul kateqoriyaları siyahısı və məhsulların öz siyahısı. Sənə hər bir kateqoriya üçün o kateqoriyaya aid bütün məhsulları tapmaq lazımdır. Və ya şirkət şöbələrinin siyahısı və işçilərin siyahısı var, hər şöbə üçün bütün işçiləri göstərmək lazımdır.

SQL-də buna "qrup birləşməsi" (GROUP JOIN və ya daha dəqiqi LEFT OUTER JOIN qruplaşdırma ilə) deyilir. LINQ-də bunun üçün xüsusi operator var – GroupJoin. Bu, adi birləşmə (Join), yəni hər sol elementə bir sağ element uyğun gələn və açar üzrə qruplaşdırma arasında bir şeydir. GroupJoin hər bir birinci kolleksiya elementini ikinci kolleksiyadan bütün əlaqəli elementlərlə kolleksiya şəklində bağlayır.

Bənzətmə

Əgər adi Join – bu, "ata və oğul" cütlərini soyad üzrə birləşdirmək kimidirsə, GroupJoin – bu, ağac qurmaq kimidir: hər ataya bütün uşaqlarının siyahısını əlavə edirsən.

Qrafik olaraq


           Kateqoriyalar                   Məhsullar
        +--------------+         +---------------------+
        | Id | Ad      |         | Ad        | CatId   |
        +----+---------+         +-----------+---------+
        | 1  | Çörək   |   --->  | Baton     | 1       |
        | 2  | İçkilər |         | Kolbasa   | 3       |
        | 3  | Ət      |         | Pepsi     | 2       |
        |    |         |         | Çay       | 2       |
        +----+---------+         +-----------+---------+

GroupJoin sonrası:

  • Çörək — [Baton]
  • İçkilər — [Pepsi, Çay]
  • Ət — [Kolbasa]

2. Metodun imzası və əsas anlayışlar

Genişlənmə metodu


public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer,                   // "Xarici" kolleksiya (məsələn, kateqoriyalar)
    IEnumerable<TInner> inner,                        // "Daxili" kolleksiya (məsələn, məhsullar)
    Func<TOuter, TKey> outerKeySelector,              // Xarici elementdən açarı necə almaq
    Func<TInner, TKey> innerKeySelector,              // Daxili elementdən açarı necə almaq
    Func<TOuter, IEnumerable<TInner>, TResult> resultSelector // Nəticə obyektini/sətirini necə qurmaq
)
  • outer: üzərində dövr olunan və elementlər əlavə olunan kolleksiya (məsələn, kateqoriyalar).
  • inner: əlavə olunan elementlərin seçildiyi kolleksiya (məsələn, məhsullar).
  • outerKeySelector: "soldakı" element üçün açar qaytaran lambda.
  • innerKeySelector: "sağdakı" element üçün açar qaytaran lambda.
  • resultSelector: hər cütlük üçün (sol+sağ qrup) nəticənin necə olacağını müəyyən edən funksiya.

3. Praktik nümunə: kateqoriyalar və məhsullar

Tutaq ki, bizdə belə modellər var:


public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Product
{
    public string Name { get; set; }
    public int CategoryId { get; set; }
}

Nümunə üçün kolleksiyalar:


var categories = new List<Category>
{
    new Category { Id = 1, Name = "Çörək" },
    new Category { Id = 2, Name = "İçkilər" },
    new Category { Id = 3, Name = "Ət" }
};

var products = new List<Product>
{
    new Product { Name = "Baton", CategoryId = 1 },
    new Product { Name = "Pepsi", CategoryId = 2 },
    new Product { Name = "Çay", CategoryId = 2 },
    new Product { Name = "Kolbasa", CategoryId = 3 }
};

GroupJoin istifadəsi (Method Syntax)


var groupJoin = categories.GroupJoin(
    products,
    category => category.Id,                    // kateqoriya açarı
    product => product.CategoryId,              // məhsul açarı
    (category, prods) => new                   // nəticəni yerində qururuq
    {
        CategoryName = category.Name,
        Products = prods.Select(p => p.Name).ToList() // bu kateqoriyadakı məhsulların adları siyahısı
    }
);

Nəticəni necə gəzmək olar:


foreach (var group in groupJoin)
{
    Console.WriteLine($"Kateqoriya: {group.CategoryName}");
    foreach (var product in group.Products)
    {
        Console.WriteLine($"  - {product}");
    }
}

Çıxış:

Kateqoriya: Çörək
  - Baton
Kateqoriya: İçkilər
  - Pepsi
  - Çay
Kateqoriya: Ət
  - Kolbasa

4. GroupJoin: Sorğu sintaksisi (query syntax)

LINQ-də SQL-ə bənzər sintaksis dəstəklənir. group join üçün join ... into ... açar sözləri istifadə olunur və bu sorğu yuxarıdakı nümunəyə çox bənzəyir.


var groupJoin2 = from c in categories
                 join p in products on c.Id equals p.CategoryId into prodGroup
                 select new
                 {
                     CategoryName = c.Name,
                     Products = prodGroup.Select(p => p.Name).ToList()
                 };

Bu, LEFT OUTER JOIN ... GROUP BY ilə SQL sorğusuna çox bənzəyir.

Vizual sxem: GroupJoin necə işləyir


[Kateqoriya]         [Məhsul]           Qruplaşdırma (GroupJoin)

  Çörək   -------->   Baton          =>   Çörək:    [Baton]
  İçkilər -------->  Pepsi           =>   İçkilər: [Pepsi, Çay]
  İçkilər -------->  Çay
  Ət      -------->   Kolbasa        =>   Ət:    [Kolbasa]

Hər bir kateqoriya öz “cibi”nə (IEnumerable<Product>) həmin kateqoriyaya aid bütün məhsulları alır.

5. Xüsusiyyətlər və tələlər

GroupJoin və adi Join fərqi

Adi JoinGroupJoin arasındakı fərq nəticələrin sayındadır. Join hər uyğunluq üçün bir cüt qaytarır, GroupJoin isə xarici kolleksiyadakı hər element üçün bir nəticə qaytarır, içində isə uyğun gələn elementlərin kolleksiyası olur.

Əgər sxemimizdə məhsulu olmayan bir kateqoriya olsa, GroupJoin ilə o da çıxacaq, sadəcə məhsullar kolleksiyası boş olacaq. Bu davranış SQL-dəki LEFT OUTER JOIN (sol xarici birləşmə) kimidir.

Budur məhsulsuz kateqoriya nümunəsi:


categories.Add(new Category { Id = 4, Name = "Pendir" });

var groupJoin3 = categories.GroupJoin(
    products,
    c => c.Id,
    p => p.CategoryId,
    (c, prods) => new
    {
        CategoryName = c.Name,
        Products = prods.Select(p => p.Name).ToList()
    });

foreach (var group in groupJoin3)
{
    Console.WriteLine($"Kateqoriya: {group.CategoryName}");
    if (group.Products.Count == 0)
        Console.WriteLine("  (Məhsul yoxdur)");
    else
        foreach (var product in group.Products)
            Console.WriteLine($"  - {product}");
}
Kateqoriya: Çörək
  - Baton
Kateqoriya: İçkilər
  - Pepsi
  - Çay
Kateqoriya: Ət
  - Kolbasa
Kateqoriya: Pendir
  (Məhsul yoxdur)

Belə ssenari biznes tətbiqlərində tez-tez lazımdır: bütün kateqoriyaları (və ya qrupları) göstərmək lazımdır, hətta bəzilərində heç bir element olmasa belə.

GroupBy ilə etmək? Yox!

Çox adam GroupBy ilə ikiqat qruplaşdırma edərək GroupJoin-u “emulyasiya” etməyə çalışır. Etmə – GroupJoin məhz bu iş üçün yaradılıb və “sol birləşmə”ni nativ şəkildə edir.

6. Real datalarla tətbiq

Əvvəlki dərslərə əlavə olaraq, gəlin tədris tətbiqimizə belə bir hesabat çıxışı əlavə edək: “Hər kateqoriya üzrə — onun məhsullarının siyahısı”. Bu tapşırıq tez-tez onlayn mağazalarda, CRM, uçot və hesabat sistemlərində lazım olur.

Demo tətbiqimizə kod əlavə edirik:


// Tutaq ki, bizdə Category və Product sinifləri və kolleksiyalar artıq var

Console.WriteLine("KATEQORİYALAR VƏ MƏHSULLAR ÜZRƏ HESABAT:");
var categoryReport = categories.GroupJoin(
    products,
    cat => cat.Id,
    prod => prod.CategoryId,
    (cat, prods) => new
    {
        cat.Name,
        ProductNames = prods.Select(p => p.Name).ToList()
    });

foreach (var row in categoryReport)
{
    Console.WriteLine($"Kateqoriya: {row.Name}");
    if (row.ProductNames.Count == 0)
        Console.WriteLine("  (Məhsul yoxdur)");
    else
        foreach (var prodName in row.ProductNames)
            Console.WriteLine($"  - {prodName}");
}

7. İç-içə qruplaşdırmalar və aqreqatlarla işləmək

GroupJoin ilə aqreqat funksiyaları birləşdirib daha mürəkkəb hesabatlar düzəltmək olar.

Nümunə: Hər kateqoriyada neçə məhsul var, saymaq


var reportWithCount = categories.GroupJoin(
    products,
    category => category.Id,
    product => product.CategoryId,
    (category, prods) => new
    {
        Category = category.Name,
        Count = prods.Count()   // Aqreqat funksiya!
    });

foreach (var rec in reportWithCount)
{
    Console.WriteLine($"{rec.Category}: {rec.Count} məhsul");
}

Tutaq ki, categoriesproducts kolleksiyaları belədir:


var categories = new[]
{
    new { Id = 1, Name = "Meyvələr" },
    new { Id = 2, Name = "Tərəvəzlər" },
    new { Id = 3, Name = "Süd məhsulları" }
};

var products = new[]
{
    new { Id = 1, Name = "Alma", CategoryId = 1 },
    new { Id = 2, Name = "Banan", CategoryId = 1 },
    new { Id = 3, Name = "Kök", CategoryId = 2 }
};

Konsolda çıxış belə olacaq:

Kateqoriya: Meyvələr — 2 məhsul
Kateqoriya: Tərəvəzlər — 1 məhsul
Kateqoriya: Süd məhsulları — 0 məhsul

8. GroupJoin və yeni başlayanların tipik səhvləri

Tez-tez edilən səhv — GroupJoin nəticəsinin sadə cütlüklər cədvəli olacağını gözləməkdir, yəni adi Join kimi. Burada "sadə" dedikdə, hər sətrin bir cütlük olduğu struktur nəzərdə tutulur: bir xarici element və bir uyğun daxili element (sanki SQL-də INNER JOIN sonrası cədvəl kimi).

Bu, xüsusilə bir az database ilə işləyənləri çaşdırır: onlar gözləyir ki, GroupJoin LEFT JOIN kimi işləyəcək, amma cütlükləri sətirlərdə yox, qruplarda qaytaracaq. Amma GroupJoin xarici kolleksiyadan bir elementi əlaqəli daxili elementlərin kolleksiyasını qaytarır — yəni iç-içə struktur.

Lazım olanda iç-içə kolleksiyaları "açmağı" unutma — məsələn, adi cütlüklər ardıcıllığı almaq üçün SelectMany istifadə et.

Başqa bir tipik səhv — uyğunluğu olmayan elementlər üçün alt qrupun sadəcə boş siyahı olacağını unutmaqdır. Bu, default davranışdır, səhv deyil — amma bunu yadında saxla ki, çıxışda "heç nə baş vermir" deyə təəccüblənməyəsən.

GroupJoin-u nə vaxt istifadə etməli, nə vaxt yox?

GroupJoin istifadə et:

  • İki datasetin varsa (məsələn, şöbələr və işçilər, kateqoriyalar və məhsullar) və onları iyerarxik göstərmək lazımdır: hər “valideyn” üçün bütün “uşaqları”.
  • Mürəkkəb hesabatlar quranda, əsas elementlərin hamısını göstərmək vacibdirsə, hətta “uşaqları” olmasa belə.
  • SQL-dəki LEFT OUTER JOIN və açar üzrə qruplaşdırma analoqu lazım olanda.

Sadəcə kolleksiyaları kəsişdirmək və ya hər uyğunluq üçün bir cüt almaq lazımdırsa — adi Join istifadə et, GroupJoin yox.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION