0

Backstory:

So I have a little demo application I'm working on which is looking through songs and trying to find duplicates based on tags the user specifies. Initially I was just using the below query which I'm trying to generalize.

var query = MusicFiles.GroupBy(x => 
        new { x?.Tag.FirstArtist, x?.Tag.Title }
    ).Where(g => g.Count() > 1)
    .ToList();

Now though I want to have the user specify these properties so I used reflection to get all properties of the TagLib.Tag class and now allow the user to specify the tags they are interested in.

Question:

How can I use this new list of strings representing the properties of the class and group by all of these properties alternatively how can I generalize the above line of code.

My initial train of thought and what I've tried has been to try and create an anonymous object like above and use reflection to get each property, but since I'm iterating over a list of string properties I end up with an anonymous object for each property rather than an anonymous object of all properties selected

Dai
  • 141,631
  • 28
  • 261
  • 374
Mike Rinos
  • 43
  • 1
  • 9

2 Answers2

0

I just built something like this for a pivot table style report, that I can't share with you right now. My recommendation would be to start with a template group by expression that contains all of the possible grouping fields. Then create an ExpressionVisitor to find the MemberInitExpression, and remove any MemberBinding that the user hasn't selected. This way, you still have a strongly typed group by expression, that can use any feature of EF Core without needing to reimplement it.

Expression<Func<X,TagGroup>> template = x => 
        new TagGroup { x?.Tag.FirstArtist, x?.Tag.Title, ... };

public Filter : ExpressionVisitor{
    public Filter(... selection){ ... }
    protected override Expression VisitMemberInit(MemberInitExpression node){
        return node.Update(node.NewExpression, node.Bindings.Where( ... ));
    }
}

var groupBy = new Filter(selection).Visit(template);

Filling in the blanks left as an exercise.

Jeremy Lakeman
  • 9,515
  • 25
  • 29
0

Use the Dynamic-Linq library which provides Linq extension-methods that accept string arguments to specify object members instead of Func<...> and Expression<Func<...>>, then follow the advice in this QA: How to use GroupBy using Dynamic LINQ

It's available on NuGet via a third-party "maintained" version of Microsoft's original official System.Linq.Dynamic.dll assembly as Install-Package System.Linq.Dynamic.Core or Install-Package System.Linq.Dynamic and mirrored on GitHub here: https://github.com/StefH/System.Linq.Dynamic.Core/wiki/Dynamic-Expressions (an older version is available here: https://github.com/kahanu/System.Linq.Dynamic )

In the more recent version for .NET Standard, the extension method you want is GroupBy( this IQueryable source, String keySelector, params Object\[\] args ).

Then your query would become:

using System.Linq.Dynamic.Core;

[...]

IReadOnlyList<String> userSelectedPropertyList = ...
// e.g. userSelectedPropertyList = new[] { "FirstArtist", "Title" };

String dynamicLinqGroupByKeySelector = "new (" + String.Join( ", ", userSelectedPropertyList ) + ")";
// so the GroupBy keySelector looks like "new (FirstArtist, Title)".

var query = MusicFiles
    .GroupBy( dynamicLinqGroupByKeySelector )
    .Where( g => g.Count() > 1 )
    .ToList();
Dai
  • 141,631
  • 28
  • 261
  • 374