DrGriff
DrGriff

Reputation: 4916

How to dynamically GroupBy using Linq

There are several similar sounding posts, but none that do exactly what I want.

Okay, so imagine that I have the following data structure (simplified for this LinqPad example)

public class Row
{
    public List<string> Columns { get; set; }
}

public List<Row> Data
       => new List<Row>
       {
              new Row { Columns = new List<string>{ "A","C","Field3"}},
              new Row { Columns = new List<string>{ "A","D","Field3"}},
              new Row { Columns = new List<string>{ "A","C","Field3"}},
              new Row { Columns = new List<string>{ "B","D","Field3"}},
              new Row { Columns = new List<string>{ "B","C","Field3"}},
              new Row { Columns = new List<string>{ "B","D","Field3"}},
       };

For the property "Data", the user will tell me which column ordinals to GroupBy; they may say "don't group by anything", or they may say "group by Column[1]" or "group by Column[0] and Column[1]".

If I want to group by a single column, I can use:

var groups = Data.GroupBy(d => d.Columns[i]);

And if I want to group by 2 columns, I can use:

var groups = Data.GroupBy(d => new { A = d.Columns[i1], B = d.Columns[i2] });

However, the number of columns is variable (zero -> many); Data could contain hundreds of columns and the user may want to GroupBy dozens of columns.

So the question is, how can I create this GroupBy at runtime (dynamically)?

Thanks

Griff

Upvotes: 1

Views: 552

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205889

With that Row data structure what are you asking for is relatively easy.

Start by implementing a custom IEqualityComparer<IEnumerable<string>>:

public class ColumnEqualityComparer : EqualityComparer<IEnumerable<string>>
{
    public static readonly ColumnEqualityComparer Instance = new ColumnEqualityComparer();
    private ColumnEqualityComparer() { }
    public override int GetHashCode(IEnumerable<string> obj)
    {
        if (obj == null) return 0;
        // You can implement better hash function
        int hashCode = 0;
        foreach (var item in obj)
            hashCode ^= item != null ? item.GetHashCode() : 0;
        return hashCode;
    }
    public override bool Equals(IEnumerable<string> x, IEnumerable<string> y)
    {
        if (x == y) return true;
        if (x == null || y == null) return false;
        return x.SequenceEqual(y);
    }
}

Now you can have a method like this:

public IEnumerable<IGrouping<IEnumerable<string>, Row>> GroupData(IEnumerable<int> columnIndexes = null)
{
    if (columnIndexes == null) columnIndexes = Enumerable.Empty<int>();
    return Data.GroupBy(r => columnIndexes.Select(c => r.Columns[c]), ColumnEqualityComparer.Instance);
}

Note the grouping Key type is IEnumerable<string> and contains the selected row values specified by the columnIndexes parameter, that's why we needed a custom equality comparer (otherwise they will be compared by reference, which doesn't produce the required behavior).

For instance, to group by columns 0 and 2 you could use something like this:

var result = GroupData(new [] { 0, 2 });

Passing null or empty columnIndexes will effectively produce single group, i.e. no grouping.

Upvotes: 2

hamid_reza hobab
hamid_reza hobab

Reputation: 929

you can use a Recursive function for create dynamic lambdaExpression. but you must define columns HardCode in the function.

Upvotes: 0

Related Questions