Migaroez
Migaroez

Reputation: 35

How to do a grouped distinct in linq

I have something working in plain simpel loops but I want to know how to do it with LINQ, if possible.

I don't even know how to describe what i want to do. But stack overflow wants me to do so in words instead of with an example, so this is me tricking it...

The classes (stripped down)

public class Product
{

    public int ProductId { get; set; }
    public string Name { get; set; }
    public List<CategoryGroup> CategoryGroups { get; set; }
}

public class CategoryGroup
{
    public int CategoryGroupId { get; set; }
    public string Name { get; set; }
    public List<Category> Categories { get; set; }
}

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

What I want to do

From

Product 1
    CategoryGroup 1
        Category 11
        Category 12
        Category 13
    CategoryGroup 2
        Category 21
        Category 22

Product 2
    Category Group 1
        Category 11
        Category 14
    Category Group 2
        Category 21
    Category Group 3
        Category 31

To

Category Group 1
    Category 11
    Category 12
    Category 13
    Category 14
Category Group 2
    Category 21
    Category 22
Category Group 3
    Category 31

The working code

var categoryGroups = new List<CategoryGroup>();
foreach (var product in products)
{
    foreach (var categoryGroup in product.CategoryGroups)
    {
        var group = categoryGroups.SingleOrDefault(cg => cg.CategoryGroupId == categoryGroup.CategoryGroupId);
        if (group == null)
        {
            group = new CategoryGroup {
                Categories = new List<Category>(),
                CategoryGroupId = categoryGroup.CategoryGroupId,
                Name = categoryGroup.Name
            };
            categoryGroups.Add(group);
        }

        foreach (var category in categoryGroup.Categories)
        {
            if (group.Categories.Any(c => c.CategoryId == category.CategoryId))
            {
                continue;
            }
            group.Categories.Add(category);
        }
    }
}

Upvotes: 1

Views: 87

Answers (2)

Bhagyesh Patel
Bhagyesh Patel

Reputation: 171

Here is the query to get all the different category groups

   var categoryGroups = products.SelectMany(g => g.CategoryGroups)
                                     .GroupBy(x => x.CategoryGroupId)
                                     .Select(y => new { CategoryGroupId = y.Key,
                                                    Categories = y.SelectMany(category => category.Categories).Distinct().ToList()                                          });

To eliminate duplicate categories you will have to modify your Category object to implement the IEquatable interface as follows

 public class Category : IEquatable<Category>
{
    public int CategoryId { get; set; }
    public string Name { get; set; }

    public bool Equals(Category other)
    {
        //Check whether the compared object is null. 
        if (Object.ReferenceEquals(other, null)) return false;

        //Check whether the compared object references the same data. 
        if (Object.ReferenceEquals(this, other)) return true;

        //Check whether the products' properties are equal. 
        return CategoryId.Equals(other.CategoryId) && Name.Equals(other.Name);
    }

    // If Equals() returns true for a pair of objects  
    // then GetHashCode() must return the same value for these objects. 

    public override int GetHashCode()
    {

        //Get hash code for the Name field if it is not null. 
        int hashProductName = Name == null ? 0 : Name.GetHashCode();

        //Get hash code for the Code field. 
        int hashProductCode = CategoryId.GetHashCode();

        //Calculate the hash code for the product. 
        return hashProductName ^ hashProductCode;
    }
}

Implementing the interface allows the Distinct() call in LINQ to compare the list of objects and eliminate duplicates.

Hope that helps.

Upvotes: 0

eocron
eocron

Reputation: 7526

You can do many subselections through SelectMany:

var result = products
            .SelectMany(x => x.CategoryGroups)
            .GroupBy(x => x.CategoryGroupId)
            .Select(x => new CategoryGroup
                         {
                             Categories = x.SelectMany(y => y.Categories)
                                           .Distinct(y => y.CategoryId)
                                           .OrderBy(y => y.CategoryId)
                                           .ToList(),
                             CategoryGroupId = x.Key,
                             Name = x.Values.First().Name
                         })
            .OrderBy(x => x.CategoryGroupId)
            .ToList();

Upvotes: 3

Related Questions