简体   繁体   English

如何让用户在运行时指定在 Linq-to-Objects GroupBy 查询表达式中使用哪些实体属性?

[英]How can I let users specify which entity properties are used in a Linq-to-Objects GroupBy query expression at runtime?

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.现在虽然我想让用户指定这些属性,所以我使用反射来获取TagLib.Tag类的所有属性,现在允许用户指定他们感兴趣的标签。

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我最初的思路和我尝试过的方法是尝试创建一个像上面这样的匿名对象并使用反射来获取每个属性,但是由于我正在遍历字符串属性列表,因此我最终得到了一个匿名对象每个属性而不是所有选定属性的匿名对象

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.然后创建一个ExpressionVisitor以查找MemberInitExpression ,并删除用户尚未选择的任何MemberBinding This way, you still have a strongly typed group by expression, that can use any feature of EF Core without needing to reimplement it.这样,您仍然有一个强类型的 group by 表达式,它可以使用 EF Core 的任何功能而无需重新实现它。

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.填写剩下的空白作为练习。

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使用Dynamic-Linq库,它提供接受字符串参数的 Linq 扩展方法来指定对象成员而不是Func<...>Expression<Func<...>> ,然后按照此 QA 中的建议进行操作: 如何使用GroupBy 使用动态 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 )它可以通过 Microsoft 原始官方System.Linq.Dynamic.dll程序集的第三方“维护”版本作为Install-Package System.Linq.Dynamic.CoreInstall-Package System.Linq.Dynamic在 NuGet 上获得,并在此处的 GitHub 上进行镜像: https : //github.com/StefH/System.Linq.Dynamic.Core/wiki/Dynamic-Expressions (这里有一个旧版本: 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 ) .在 .NET Standard 的最新版本中,您想要的扩展方法是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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM