C# 构建泛型表达式树.NET核心

C# 构建泛型表达式树.NET核心,c#,generics,.net-core,lambda,expression-trees,C#,Generics,.net Core,Lambda,Expression Trees,你好社区我知道这可能是一个重复 资源显然太多了 不过我还是很困惑。 有没有人能在下面的代码中提供一个更清晰的画面。 下面我提供了一些帮助我理解的评论 private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters) { Expression<Func<T, bool>> finalExpressi

你好社区我知道这可能是一个重复

资源显然太多了

不过我还是很困惑。 有没有人能在下面的代码中提供一个更清晰的画面。 下面我提供了一些帮助我理解的评论

private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
        {
            Expression<Func<T, bool>> finalExpression = Expression.Constant(true); //Casting error

            if (string.IsNullOrEmpty(parameters))
                return finalExpression;

            string[] paramArray = parameters.Split(","); //parameters is one string splitted with commas
     ParameterExpression argParam = Expression.Parameter(typeof(T), "viewModel"); //Expression Tree

            foreach (var param in paramArray)
        {
            var parsedParameter = ParseParameter(param);
            if (parsedParameter.operation == Operation.None)
                continue; // this means we parsed incorrectly we TODO: Better way for error handling

            //Property might be containment property e.g T.TClass.PropName
            Expression nameProperty = Expression.Property(argParam, parsedParameter.propertyName);
            //Value to filter against
            var value = Expression.Constant(parsedParameter.value);
            Expression comparison;
            switch (parsedParameter.operation)
            {   //Enum
                case Operation.Equals:
                    comparison = Expression.Equal(nameProperty, value);
                    break;
                    //goes on for NotEquals, GreaterThan etc
            }
            finalExpression = Expression.Lambda(comparison, argParam);// Casting error
        }

        return finalExpression;
    }

所以您试图将类似于
“a==1,b==3”的内容转换为
viewModel=>viewModel.a==1&&viewModel.b==3

我认为您已经非常接近了,您只需要添加
&&
(或
| |
),并始终创建一个lambda

专用表达式ParseParametersToFilter(字符串参数)
{
ParameterExpression argParam=Expression.Parameter(typeof(T),“viewModel”);//表达式树
表达式体=表达式常数(真);
如果(!string.IsNullOrEmpty(参数)){
正文=参数。拆分(“,”)
.选择(参数=>{
var parsedParameter=ParseParameter(param);
//…如上所述,将param转换为比较表达式。。。
收益比较;
})
.aggreage((l,r)=>表达式AndAlso(l,r));
}
返回表达式.Lambda(body,argParam);
}

如果这是为了传递给实体框架,不要编译它,否则您只能在客户端对它进行评估。

这是我从今天的测试中得出的结论,并且效果很好。 可能需要进行一些重构。我愿意听取建议

请确保检查代码中的注释

private void ConvertValuePropertyType(Type type, string value, out dynamic converted)
        {
            // Here i convert the value to filter to the necessary type
            // All my values come as strings.
            if (type.IsEnum)
                converted = Enum.Parse(type, value);
            else if (type == typeof(DateTime))
                converted = DateTime.Parse(value);
            else if (type is object)
                converted = value;
            else
                throw new InvalidCastException($"Value was not converted properly {nameof(value)} {nameof(type)}");
        }

private MemberExpression GetContainmentMember(ParameterExpression parameterExpression, string propertyName)
        {
            //propertName looks like this User.FriendlyName
            //So we have to first take T.User from the root type
            // Then the Name property.
            // I am not sure how to make this work for any depth.
            var propNameArray = propertyName.Split(".");
            if (propNameArray.Length > 1)
            {
                MemberExpression member = Expression.Property(parameterExpression, propNameArray[0]);
                return Expression.PropertyOrField(member, propNameArray[1]);
            }
            else
            { //This needs to make sure we retrieve containment
                return Expression.Property(parameterExpression, propertyName);
            }
        }
// ***************************************************************
// This is the core method!
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
        {
            Expression body = Expression.Constant(true);
            ParameterExpression argParam = Expression.Parameter(typeof(T), nameof(T));

            if (string.IsNullOrEmpty(parameters))
                return Expression.Lambda<Func<T, bool>>(body, argParam); // return empty filter

            string[] paramArray = parameters.Split(","); //parameters is one string splitted with commas

            foreach (var param in paramArray)
            {
                var parsedParameter = ParseParameter(param);
                if (parsedParameter.operation == Operation.None)
                    continue; // this means we parsed incorrectly, do not fail continue

                //Get model
                //Get property name
                //Property might be containment property e.g T.TClass.PropName
                //Value to filter against
                MemberExpression nameProperty = GetContainmentMember(argParam, parsedParameter.propertyName);

                //Convert property value according to property name
                Type propertyType = GetPropertyType(typeof(T), parsedParameter.propertyName);

                ConvertValuePropertyType(propertyType, parsedParameter.value, out object parsedValue);

                var value = Expression.Constant(parsedValue);

                switch (parsedParameter.operation)
                {
                    //What operation did the parser retrieve
                    case Operation.Equals:
                        body = Expression.AndAlso(body, Expression.Equal(nameProperty, value)); 
                        break;
                    //goes on for NotEquals, GreaterThan etc

                    default:
                        break;
                }
            }

            return Expression.Lambda<Func<T, bool>>(body, argParam);
        }

private (string propertyName, Operation operation, string value) ParseParameter(string parameter){...}
private void ConvertValuePropertyType(类型类型、字符串值、输出动态转换)
{
//在这里,我将要筛选的值转换为必要的类型
//我所有的值都是字符串。
if(type.IsEnum)
converted=Enum.Parse(类型、值);
else if(type==typeof(DateTime))
converted=DateTime.Parse(值);
else if(类型为object)
转换=价值;
其他的
抛出新的InvalidCastException($”值未正确转换{nameof(Value)}{nameof(type)}”);
}
私有成员表达式GetContainementMember(ParameterExpression ParameterExpression,string propertyName)
{
//propertName看起来像这个User.FriendlyName
//所以我们必须首先从根类型中获取T.User
//然后是Name属性。
//我不知道如何使这项工作有任何深度。
var propNameArray=propertyName.Split(“.”);
如果(propNameArray.Length>1)
{
MemberExpression member=Expression.Property(parameterExpression,propNameArray[0]);
返回表达式.PropertyOrField(成员,propNameArray[1]);
}
其他的
{//这需要确保我们检索到了容器
返回表达式.Property(parameterExpression,propertyName);
}
}
// ***************************************************************
//这是核心方法!
专用表达式ParseParametersToFilter(字符串参数)
{
表达式体=表达式常数(真);
ParameterExpression argParam=表达式。参数(typeof(T),nameof(T));
if(string.IsNullOrEmpty(参数))
返回表达式.Lambda(body,argParam);//返回空筛选器
string[]paramArray=parameters.Split(,“”;//parameters是一个用逗号分隔的字符串
foreach(paramArray中的var param)
{
var parsedParameter=ParseParameter(param);
if(parsedParameter.operation==operation.None)
continue;//这意味着我们的分析不正确,请不要失败继续
//获取模型
//获取属性名
//属性可能是包含属性,例如T.TClass.PropName
//要筛选的值
MemberExpression nameProperty=GetContainementMember(argParam,parsedParameter.propertyName);
//根据属性名称转换属性值
Type propertyType=GetPropertyType(typeof(T),parsedParameter.propertyName);
ConvertValuePropertyType(propertyType,parsedParameter.value,out object parsedValue);
var值=表达式常数(parsedValue);
开关(解析参数。操作)
{
//解析器检索到了什么操作
案例操作。等于:
body=Expression.AndAlso(body,Expression.Equal(nameProperty,value));
打破
//继续进行NotEquals、GreaterThan等
违约:
打破
}
}
返回表达式.Lambda(body,argParam);
}
private(字符串属性名称,操作,字符串值)ParseParameter(字符串参数){…}

到目前为止,这项工作做得很好

你所提供的是非常有希望的。它在一个级别上工作。我得到一个参数异常,它无法转换枚举和包含属性。这应该发生在客户端。不过谢谢你的提示
.Split(“.”).Aggregate(argParam,(l,r)=>Expression.Property(l,r))
?您需要转换数据类型并递归解析参数。我建议您声明一些示例
Expression
,并检查C#编译器是如何构建表达式图的。我该怎么做?我知道出了什么问题,只是不知道如何解决。
public class ReportViewModel
{
    public StatusEnum Status {get;set;}
    public UserViewModel User {get;set;}
}

public enum StatusEnum
{
    Pending,
    Completed
}

public class UserViewModel
{
    public string FriendlyName {get;set;}
}
private void ConvertValuePropertyType(Type type, string value, out dynamic converted)
        {
            // Here i convert the value to filter to the necessary type
            // All my values come as strings.
            if (type.IsEnum)
                converted = Enum.Parse(type, value);
            else if (type == typeof(DateTime))
                converted = DateTime.Parse(value);
            else if (type is object)
                converted = value;
            else
                throw new InvalidCastException($"Value was not converted properly {nameof(value)} {nameof(type)}");
        }

private MemberExpression GetContainmentMember(ParameterExpression parameterExpression, string propertyName)
        {
            //propertName looks like this User.FriendlyName
            //So we have to first take T.User from the root type
            // Then the Name property.
            // I am not sure how to make this work for any depth.
            var propNameArray = propertyName.Split(".");
            if (propNameArray.Length > 1)
            {
                MemberExpression member = Expression.Property(parameterExpression, propNameArray[0]);
                return Expression.PropertyOrField(member, propNameArray[1]);
            }
            else
            { //This needs to make sure we retrieve containment
                return Expression.Property(parameterExpression, propertyName);
            }
        }
// ***************************************************************
// This is the core method!
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
        {
            Expression body = Expression.Constant(true);
            ParameterExpression argParam = Expression.Parameter(typeof(T), nameof(T));

            if (string.IsNullOrEmpty(parameters))
                return Expression.Lambda<Func<T, bool>>(body, argParam); // return empty filter

            string[] paramArray = parameters.Split(","); //parameters is one string splitted with commas

            foreach (var param in paramArray)
            {
                var parsedParameter = ParseParameter(param);
                if (parsedParameter.operation == Operation.None)
                    continue; // this means we parsed incorrectly, do not fail continue

                //Get model
                //Get property name
                //Property might be containment property e.g T.TClass.PropName
                //Value to filter against
                MemberExpression nameProperty = GetContainmentMember(argParam, parsedParameter.propertyName);

                //Convert property value according to property name
                Type propertyType = GetPropertyType(typeof(T), parsedParameter.propertyName);

                ConvertValuePropertyType(propertyType, parsedParameter.value, out object parsedValue);

                var value = Expression.Constant(parsedValue);

                switch (parsedParameter.operation)
                {
                    //What operation did the parser retrieve
                    case Operation.Equals:
                        body = Expression.AndAlso(body, Expression.Equal(nameProperty, value)); 
                        break;
                    //goes on for NotEquals, GreaterThan etc

                    default:
                        break;
                }
            }

            return Expression.Lambda<Func<T, bool>>(body, argParam);
        }

private (string propertyName, Operation operation, string value) ParseParameter(string parameter){...}