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



背景故事:

所以我有一个我正在研究的小演示应用程序,它正在查看歌曲并尝试根据用户指定的标签查找重复项。最初,我只是使用以下我试图概括的查询。

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

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

问题:

我如何使用这个新的字符串列表,通过所有这些属性表示类和组的属性,或者我如何概括上面的代码行。

我最初的想法和我尝试的是尝试创建一个匿名对象,如上所示并使用反射来获取每个属性,但由于我正在迭代字符串属性列表,因此我最终为每个属性创建一个匿名对象,而不是所有选定属性的匿名对象

使用Dynamic-Linq库,该库提供了接受字符串参数的 Linq 扩展方法来指定对象成员,而不是Func<...>Expression<Func<...>>,然后遵循此 QA 中的建议:如何使用 GroupBy 使用动态 LINQ

它通过 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

(在 .NET Standard 的较新版本中,所需的扩展方法是GroupBy( this IQueryable source, String keySelector, params Object[] args )

然后,您的查询将变为:

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();

我刚刚为数据透视表样式的报告构建了这样的东西,我现在无法与您分享。我的建议是从包含所有可能的分组字段的模板分组开始。然后创建一个ExpressionVisitor来查找MemberInitExpression,并删除用户尚未选择的任何MemberBinding。这样,你仍然有一个按表达式划分的强类型组,该组可以使用 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);

填补留下的空白作为练习。

最新更新