建立通用表达树.NET Core [英] Build a Generic Expression Tree .NET Core
问题描述
您好,我知道这可能是重复的社区. p>
如何动态创建表达式< ; Func< MyClass,bool>>表达式< Func< MyClass,字符串>> ;?
中的谓词如何在直到运行时才知道类型时创建一个Expression.Lambda?
显然资源太多.
我还是很困惑. 有人可以提供下面代码中发生的情况的更清晰的图片. 下面,我提供了一些评论以帮助我理解.
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;
}
以上显然不起作用.
将其返回到linq查询,例如IEnumerable<SomeModel>.Where(ParseParametersToFilter.Compile())
我知道我的错误是强制性错误. 我该如何解决?
在@Jeremy Lakeman回答之后,我更新了代码,使其看起来像这样.虽然我使用的ViewModel非常复杂.最后,我提供了一个小预览.
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
Expression<Func<T, bool>> finalExpression = t => true;
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
Expression body = Expression.Constant(true);
foreach (var param in paramArray)
{
var parsedParameter = ParseParameter(param);
if (parsedParameter.operation == Operation.None)
continue; // this means we parsed incorrectly 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);
switch (parsedParameter.operation)
{ //Enum
case Operation.Equals:
body = Expression.AndAlso(body, Expression.Equal(nameProperty, value));
break;
//goes on for NotEquals, GreaterThan etc
}
body = Expression.AndAlso(body, argParam);
}
return Expression.Lambda<Func<T, bool>>(body, argParam);
}
private (string propertyName, Operation operation, string value) ParseParameter(string parameter){...}
但是现在我得到以下异常
当我将状态"作为属性参数传递时:
未为类型'model.StatusEnum'和'System.String'定义二进制运算符Equal.
当我传递User.FriendlyName参数时:
未为类型"model.ReportViewModel"定义实例属性"User.FriendlyName" 参数名称:propertyName
这是我的视图模型的样子!
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){...}
到目前为止,效果很好.
Hello Community i am aware of this might be a possible duplicate.
How to create a Expression.Lambda when a type is not known until runtime?
Creating expression tree for accessing a Generic type's property
There are obviously too many resources.
I am still confused though. Could someone provide a clearer picture of what is happening in the below code. Below i have provided some comments to help my understanding.
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;
}
The above obviously is not working.
This is returned to linq query like this IEnumerable<SomeModel>.Where(ParseParametersToFilter.Compile())
I understand my mistake is a casting mistake. How could i fix this?
After @Jeremy Lakeman answer i updated my code to look like this. Although the ViewModel i am using is quite complex. I have provided a small preview at the end.
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
Expression<Func<T, bool>> finalExpression = t => true;
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
Expression body = Expression.Constant(true);
foreach (var param in paramArray)
{
var parsedParameter = ParseParameter(param);
if (parsedParameter.operation == Operation.None)
continue; // this means we parsed incorrectly 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);
switch (parsedParameter.operation)
{ //Enum
case Operation.Equals:
body = Expression.AndAlso(body, Expression.Equal(nameProperty, value));
break;
//goes on for NotEquals, GreaterThan etc
}
body = Expression.AndAlso(body, argParam);
}
return Expression.Lambda<Func<T, bool>>(body, argParam);
}
private (string propertyName, Operation operation, string value) ParseParameter(string parameter){...}
But now i get the following Exceptions
When i pass the Status as property parameter:
The binary operator Equal is not defined for the types 'model.StatusEnum' and 'System.String'.
When i pass the User.FriendlyName parameter:
Instance property 'User.FriendlyName' is not defined for type 'model.ReportViewModel' Parameter name: propertyName
Here is how my view model looks like!
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;}
}
Here is what i came up with and works pretty well, from my tests today. Some refactoring may be needed. I am open to suggestions.
Please make sure to check the comments inside the code.
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){...}
This worked very good so far.
这篇关于建立通用表达树.NET Core的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!