4

I have following linq expression:

Func<Entity, object> groupQuery = item => 
     new { a = item.Attributes["name"], item = item.Attributes["number"] };
var result = target.Collection.Entities.GroupBy(groupQuery).ToList();

But if i don't know, how much columns i will group (for example 3 instead of 2),and names of the Attributes stored in List Names, How should i change my groupQuery object? My first idea was to create dynamic object like this but it don't work

dynamic groupQuery= new ExpandoObject();
IDictionary<string, object> dictionary = (IDictionary<string, object>)groupQuery;

foreach (string str in Names)
{
   dictionary.Add(str, str);
}
Anton Kozlovsky
  • 203
  • 1
  • 15
  • a generic algorithm is best for unknown / unlimited set of possibilities.. i bet that if you think it over you will see that you have around 50~ different possibilities.. it is better to store **specifically** every **implementation** (groupQuery) in a dictionary and call it with a key (semi-automatic) – ymz Jan 21 '16 at 08:04
  • *"But if i don't know, how much columns i will group (for example 3 instead of 2),and names of the Attributes stored in List Names"* What **do** you know then? There must be something you know, right? for instance list of attribute names to group by or ? – Ivan Stoev Jan 21 '16 at 10:09
  • you can use [`DynamicLinq` library](https://www.nuget.org/packages/System.Linq.Dynamic.Library/) – Grundy Jan 21 '16 at 10:47

2 Answers2

2

Instead of returning an object from groupQuery you can return a string. This string will be constructed from properties of objects that you want to group. Depending on the configuration it can be generated in different ways i.e. based on different properties. Here is a code that shows an idea:

public class A
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public string Property3 { get; set; }
}

public enum GroupByuMode
{
    GroupBy1,
    GroupBy2,
    GroupBy3,
}

...

var list = new List<A>();
for (int i = 0; i < 10; ++i)
    for (int j = 0; j < 10; ++j)
        for (int k = 0; k < 10; ++k)
            list.Add(new A { Property1 = i.ToString(), Property2 = j.ToString(), Property3 = k.ToString() });

var mode = GroupByuMode.GroupBy1;

Func<A, object> func = a =>
{
    if (mode == GroupByuMode.GroupBy1)
        return a.Property1;
    else if (mode == GroupByuMode.GroupBy2)
        return String.Format("{0}_{1}", a.Property1, a.Property2);
    else if (mode == GroupByuMode.GroupBy3)
        return String.Format("{0}_{1}_{2}", a.Property1, a.Property2, a.Property3);

    return null;
};

var res = list.GroupBy(func).ToList();
Console.WriteLine(res.Count);

mode = GroupByuMode.GroupBy2;

res = list.GroupBy(func).ToList();
Console.WriteLine(res.Count);

It works witch LINQ to Objects as shown above. You have to check if it works with LINQ to Entities or another implementation of LINQ.

Michał Komorowski
  • 6,198
  • 1
  • 20
  • 24
1

answer in question C# LINQ - How to build Group By clause dynamically

IEnumerable<string> columnsToGroupBy = new[] { Names.First()};
            Names.RemoveAt(0);
            Names.Aggregate(columnsToGroupBy, (current, query) => current.Concat(new[] {query}));
            GroupQuery = r => new NTuple<object>(from column in columnsToGroupBy select r[column]);
///////
    using System;
    using System.Collections.Generic;
    using System.Linq;

    namespace WBTCB.AggregationService.Models.Helpers
    {
        public class NTuple<T> : IEquatable<NTuple<T>>
        {
            public NTuple(IEnumerable<T> values)
            {
                Values = values.ToArray();
            }

            public readonly T[] Values;

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(this, obj))
                    return true;
                if (obj == null)
                    return false;
                return Equals(obj as NTuple<T>);
            }

            public bool Equals(NTuple<T> other)
            {
                if (ReferenceEquals(this, other))
                    return true;
                if (other == null)
                    return false;
                var length = Values.Length;
                if (length != other.Values.Length)
                    return false;
                for (var i = 0; i < length; ++i)
                    if (!Equals(Values[i], other.Values[i]))
                        return false;
                return true;
            }

            public override int GetHashCode()
            {
                return Values.Aggregate(17, (current, value) => current*37 + (!ReferenceEquals(value, null) ? value.GetHashCode() : 0));
            }
        }
    }
Community
  • 1
  • 1
Anton Kozlovsky
  • 203
  • 1
  • 15