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){...}