如何在.NET中动态创建匿名对象?

How to create an anonymous object dynamically in .NET?

提问人:Alex 提问时间:10/26/2023 最后编辑:marc_sAlex 更新时间:11/16/2023 访问量:49

问:

我有一个带有 Entity Framework 的 .net 8 项目,我想为它生成一个动态查询。

用户可以使用现有表根据自己的选择定义列:

例如:

我有 3 个表:、和第四个表,它与这 3 个表有关。DivisionsIndustriesPortfoliosEntryLine

在 UI 中,用户有一个包含 3 列的列表,他可以选择: .他可以选择这 3 个的任意组合,例如只有 2 个,只有一个,等等。Division, Industry, Portfolio

根据用户的选择,我需要创建这个:

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,并选择数据等。

.NET 对象 entity-framework-core 函数编程 匿名类型

评论


答:

0赞 Alex 11/16/2023 #1

我找到了反射和功能编程的解决方案

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 etc]

var groupByPredicate = ExpressionHelper.BuildPredicate<EntryLineDetailedDto>(groupByColumns);

var groupedEntryLines = entryLinesQuery.GroupBy(groupByPredicate)
                                        .Skip((pageNumber - 1) * pageSize)
                                        .Take(pageSize).ToList();