我有一个带有实体框架的 .net 8 项目,我想为其构建一个动态查询。
用户可以使用现有表根据自己的选择定义列:
例如:
我有 3 个表:
Divisions
、Industries
、Portfolios
,以及与这 3 个表相关的第四个表 EntryLine
。
在 UI 中,用户有一个包含 3 列的列表,他可以选择:
Division, Industry, Portfolio
。他可以选择这 3 个的任意组合,例如仅其中 2 个、仅一个等等。
根据用户的选择,我需要创建这个:
var entryLines = _context.EntryLines.AsQueryable();
Expression<Func<EntryLine, object>> func;
if(user choosed divizion){
func.Append(x => new { DivisionName = x.EntryLine.Division.Name});
// func = new {x.EntryLine.Division.Name};
}
if (user chose industry) {
func.Append(x => new { IndustryName = x.EntryLine.Industry.Name });
// if user did NOT choose industry - func = new { DivisionName = x.EntryLine.Division.Name};
// if user chose industry -
// func = new {
// DivisionName = x.EntryLine.Division.Name
// IndustryName = x.EntryLine.Industry.Name
// };
}
通话后:
entryLines.GroupBy(func);
等等..
我给你一个简单的例子,但我有更多的列和一个非常复杂的数据库,但主要思想是根据用户在 UI 中选择的内容创建一个匿名对象,并将其传递给 GroupBy linq 并选择数据等等。
我通过反射和函数式编程找到了解决方案
public static class ExpressionHelper
{
public static Func<Tin, object> BuildPredicate<Tin>(params string[] propertyNames)
{
var parameter = Expression.Parameter(typeof(Tin), "x");
List<Expression> propertyExpressions = new List<Expression>();
foreach (var propertyName in propertyNames)
{
PropertyInfo propertyInfo = typeof(Tin).GetProperty(propertyName);
if (propertyInfo == null)
{
throw new InvalidOperationException($"No property with name {propertyName} found.");
}
MemberExpression propertyAccess = Expression.PropertyOrField(parameter, propertyName);
propertyExpressions.Add(Expression.Convert(propertyAccess, typeof(object)));
}
MethodInfo tupleCreateMethod = typeof(ValueTuple).GetMethods()
.First(m => m.Name == "Create" && m.GetParameters().Length == propertyNames.Length);
var genericTupleCreateMethod = tupleCreateMethod.MakeGenericMethod(propertyExpressions.Select(e => e.Type).ToArray());
var tupleCreation = Expression.Call(genericTupleCreateMethod, propertyExpressions);
var boxedTuple = Expression.Convert(tupleCreation, typeof(object));
LambdaExpression lambda = Expression.Lambda(typeof(Func<Tin, object>), boxedTuple, parameter);
return (Func<Tin, object>)lambda.Compile();
}
}
电话将是:
groupByColumns 将是一个字符串列表,例如 ["column1"、column2 等]
var groupByPredicate = ExpressionHelper.BuildPredicate<EntryLineDetailedDto>(groupByColumns);
var groupedEntryLines = entryLinesQuery.GroupBy(groupByPredicate)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize).ToList();