Linq Grouping Dynamically

LINQ Grouping dynamically

If you're not working with a database you can use Reflection:

private static object GetPropertyValue(object obj, string propertyName)
{
return obj.GetType().GetProperty(propertyName).GetValue(obj, null);
}

Used as:

var grouped = enumeration.GroupBy(x => GetPropertyValue(x, columnName));

This is a pretty raw solution, a better way should be to use Dynamic LINQ:

var grouped = enumeration.GroupBy(columnName, selector);

EDIT Dynamic LINQ maybe needs some explanations. It's not a technology, a library or a brand new framework. It's just a convenient name for a couple (2000 LOC) of helpers methods that let you write such queries. Just download their source (if you don't have VS samples installed) and use them in your code.

How to use GroupBy using Dynamic LINQ

There is default it defined, you can use it to return matched elements:

var mydataGrouped = mydata.GroupBy("Shop", "it");  

To iterate through results you should additionally Select elements to name it and use dynamics:

var mydataGrouped = mydata.GroupBy("Shop", "it").Select("new (it.Key as Shop, it as Albums)");

foreach (dynamic group in mydataGrouped)
{
foreach (dynamic album in group.Albums)
{
Console.WriteLine(album.Author);
}
}

C# LINQ, dynamic grouping by [Key] attributes

Somebody had posted a valid answer and removed it later for some reason. Here it is:

Combined key class:

class CombinedKey<T> : IEquatable<CombinedKey<T>>
{
readonly object[] _keys;

public bool Equals(CombinedKey<T> other)
{
return _keys.SequenceEqual(other._keys);
}

public override bool Equals(object obj)
{
return obj is CombinedKey<T> key && Equals(key);
}

public override int GetHashCode()
{
int hash = _keys.Length;
foreach (object o in _keys)
{
if (o != null)
{
hash = hash * 13 + o.GetHashCode();
}
}
return hash;
}

readonly Lazy<Func<T, object[]>> lambdaFunc = new Lazy<Func<T, object[]>>(() =>
{
Type type = typeof(T);
var paramExpr = Expression.Parameter(type);
var arrayExpr = Expression.NewArrayInit(
typeof(object),
type.GetProperties()
.Where(p => (Attribute.GetCustomAttribute(p, typeof(KeyAttribute)) != null))
.Select(p => Expression.Convert(Expression.Property(paramExpr, p), typeof(object)))
.ToArray()
);

return Expression.Lambda<Func<T, object[]>>(arrayExpr, paramExpr).Compile();
}, System.Threading.LazyThreadSafetyMode.PublicationOnly);

public CombinedKey(T instance)
{
_keys = lambdaFunc.Value(instance);
}
}

Caller function and the actual usage:

public static class MyClassWithLogic
{
//Caller to CombinedKey class
private static CombinedKey<Q> NewCombinedKey<Q>(Q instance)
{
return new CombinedKey<Q>(instance);
}

//Extension method for IEnumerables
public static IEnumerable<T> DistinctByPrimaryKey<T>(this IEnumerable<T> entries) where T : class
{
return entries.AsQueryable().GroupBy(NewCombinedKey)
.Select(r => r.First());
}
}

Yes, it is relatively slow, so if it is a problem, then Klaus Gütter's solutions are the way to go.

LINQ GroupBy with a dynamic group of columns


var results=items
.Select(i=>
new {
variety=includevariety?t.variety:null,
category=includecategory?t.category:null,
...
})
.GroupBy(g=>
new { variety, category, ... }, g=>g.quantity)
.Select(i=>new {
variety=i.Key.variety,
category=i.Key.category,
...
quantity=i.Sum()
});

shortened:

var results=items
.GroupBy(g=>
new {
variety=includevariety?t.variety:null,
category=includecategory?t.category:null,
...
}, g=>g.quantity)
.Select(i=>new {
variety=i.Key.variety,
category=i.Key.category,
...
quantity=i.Sum()
});

Linq Dynamic GroupBy Clause

I can suggest 2 approaches to make your GroupBy dynamic:

  1. Use string variable in your query:
var column1 = "COL1"; // user input
var column2 = "COL2"; // user input

// the querying ...

.GroupBy(i => new
{
ID = i.Field<decimal>("ID")
, COL1 = i.Field<string>(column1)
, COL2 = i.Field<string>(column2)
}
)

  1. Use System.Dynamic.Linq package for dynamic querying

Which makes it more complex! Here is an example of it here.


I prefer the first approach because it's easy and not require extra efforts and libraries.

Dynamic Group By clause in linq query

This yields the same output as your hardcoded list, but I'm not sure if it's what you want (hint: giving us your expected output will help a lot in that regard).

It simply creates a distinct list of the cities, then builds a predicate delegate that counts the occurrence of each city, and adds that predicate to a list. When the actual grouping is performed, each predicate in the list is invoked with the result of the grouping.

The notation Func<IGrouping<string, Person>, int> simply says "this is a delegate that takes an IGrouping<string, Person> object (the result of calling GroupBy) and returns an int (the result of calling Count)".

class Program
{
static void Main(string[] args)
{
var people = new List<Person>()
{
new Person(1, "Paris", "carpenter"),
new Person(2, "Paris", "bricklayer"),
new Person(3, "London", "video game critic"),
};

var distinctCities = people.Select(p => p.City).Distinct();
var groupPredicates = new List<Func<IGrouping<string, Person>, int>>();

foreach (var city in distinctCities)
{
groupPredicates.Add(g => g.Count(p => p.City == city));
}

var professions = people
.GroupBy(p => p.Profession)
.Select(g =>
new
{
name = g.Key,
data = groupPredicates.Select(gp => gp(g)),
});

foreach (var profession in professions)
{
Console.WriteLine(profession.name + " =>");

foreach (var count in profession.data)
{
Console.WriteLine(" " + count);
}
}

Console.ReadKey(true);
}
}

struct Person
{
public int Id { get; set; }
public string City { get; set; }
public string Profession { get; set; }

public Person(int id, string city, string profession)
{
Id = id;
City = city;
Profession = profession;
}
}


Related Topics



Leave a reply



Submit