C# 如何创建一个泛型方法来遍历对象';s字段并将其用作Where谓词?
我正在构建一个通用接口,将选定的字符串属性从类中公开出来,然后我要在每个字段中搜索文本,以检查它是否匹配 这是我的C# 如何创建一个泛型方法来遍历对象';s字段并将其用作Where谓词?,c#,linq,linq-to-sql,expression-trees,iqueryable,C#,Linq,Linq To Sql,Expression Trees,Iqueryable,我正在构建一个通用接口,将选定的字符串属性从类中公开出来,然后我要在每个字段中搜索文本,以检查它是否匹配 这是我的IFieldExposer界面: using System; using System.Collections.Generic; public interface IFieldExposer<T> { IEnumerable<Func<T, string>> GetFields(); } 我还为IFieldExposer创建了扩展方法,因为
IFieldExposer
界面:
using System;
using System.Collections.Generic;
public interface IFieldExposer<T>
{
IEnumerable<Func<T, string>> GetFields();
}
我还为IFieldExposer
创建了扩展方法,因为我希望保持它的简单性,并且能够在代码中的任何地方调用obj.Match(text,ignoreCase)
。这个方法应该告诉我我的对象是否与我的文本匹配。以下是扩展类
的代码,该类无法按预期工作:
using System;
using System.Linq.Expressions;
using System.Reflection;
public static class ExtensionClass
{
public static bool Match<T>(this IFieldExposer<T> obj, string text, bool ignoreCase)
{
Func<bool> expression = Expression.Lambda<Func<bool>>(obj.CreateExpressionTree(text, ignoreCase)).Compile();
return expression();
}
private static Expression CreateExpressionTree<T>(this IFieldExposer<T> obj, string text, bool ignoreCase)
{
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
var exposedFields = obj.GetFields();
if (ignoreCase)
{
// How should I do convert these to lower too?
// exposedFields = exposedFields.Select(e => e.???.ToLower());
text = text.ToLower();
}
Expression textExp = Expression.Constant(text);
Expression orExpressions = Expression.Constant(false);
foreach (var field in exposedFields)
{
//How should I call the contains method on the string field?
Expression fieldExpression = Expression.Lambda<Func<string>>(Expression.Call(Expression.Constant(obj), field.Method)); //this doesn't work
Expression contains = Expression.Call(fieldExpression, containsMethod, textExp);
orExpressions = Expression.Or(orExpressions, contains);
}
return orExpressions;
}
}
编辑
我忘了提到我的dataList
应该是IQueryable
,我想在SQL中执行我的代码,这就是我自己尝试构建表达式树的原因。因此,我的示例代码应该是:
var dataList = new List<DataClass> { data };
var query = dataList.AsQueryable();
var results = query.Where(ExtensionClass.Match<DataClass>("lorem dolor"));
然后,我似乎必须用Expression.Lambda
友好地调用selectedFieldsExp
。我想到了:
Expression.Lambda(selectedFieldsExp, parameter).Compile();
这是可行的,但我不知道如何正确地为lambda表达式调用DynamicInvoke()
它抛出参数计数不匹配。
如果我在没有参数的情况下调用它,并且类型为'System.Linq.Expressions.TypedParameterExpression'的对象无法转换为类型'DataClass'。
如果我调用DynamicInvoke(参数)。
有什么想法吗?动态创建表达式并不复杂,因为您可以直接调用Func委托:
public interface IFieldExposer<T>
{
IEnumerable<Func<T,string>> SelectedFields { get; }
}
public static class FieldExposerExtensions
{
public static IEnumerable<Func<T,string>> MatchIgnoreCase<T>( this IEnumerable<Func<T,string>> stringProperties, T source, string matchText)
{
return stringProperties.Where(stringProperty => String.Equals( stringProperty( source), matchText, StringComparison.OrdinalIgnoreCase));
}
}
class DataClass : IFieldExposer<DataClass>
{
public string PropertyOne { get; set; }
public string PropertyTwo { get; set; }
public ChildClass Child { get; set; }
public IEnumerable<Func<DataClass, string>> SelectedFields {
get {
return new Func<DataClass, string>[] { @this => @this.PropertyOne, @this => @this.Child.PropertyThree };
}
}
public override string ToString() => this.PropertyOne + " " + this.PropertyTwo + " " + this.Child.PropertyThree;
}
class ChildClass
{
public string PropertyThree { get; set; }
}
公共接口IFieldExposer
{
IEnumerable SelectedFields{get;}
}
公共静态类FieldExposeExtensions
{
公共静态IEnumerable MatchIgnoreCase(此IEnumerable stringProperties,T源,字符串matchText)
{
返回stringProperties.Where(stringProperty=>String.Equals(stringProperty(source)、matchText、StringComparison.OrdinalIgnoreCase));
}
}
类数据类:IFieldExposer
{
公共字符串属性one{get;set;}
公共字符串属性two{get;set;}
公共子类子{get;set;}
公共IEnumerable SelectedFields{
得到{
返回新的Func[]{@this=>@this.PropertyOne,@this=>@this.Child.PropertyThree};
}
}
公共重写字符串ToString()=>this.PropertyOne+“”+this.PropertyTwo+“”+this.Child.propertyTree;
}
班级儿童班
{
公共字符串属性树{get;set;}
}
然后用它,
class Program
{
static void Main(string[] args)
{
var data = new DataClass {
PropertyOne = "Lorem",
PropertyTwo = "Ipsum",
Child = new ChildClass {
PropertyThree = "Dolor"
}
};
var data2 = new DataClass {
PropertyOne = "lorem",
PropertyTwo = "ipsum",
Child = new ChildClass {
PropertyThree = "doloreusement"
}
};
var dataList = new List<DataClass>() { data, data2 };
IEnumerable<DataClass> results = dataList.Where( d => d.SelectedFields.MatchIgnoreCase( d, "lorem").Any());
foreach (DataClass source in results) {
Console.WriteLine(source.ToString());
}
Console.ReadKey();
}
}
类程序
{
静态void Main(字符串[]参数)
{
var data=新数据类{
PropertyOne=“Lorem”,
PropertyTwo=“Ipsum”,
Child=新的ChildClass{
PropertyTree=“多洛”
}
};
var data2=新的数据类{
PropertyOne=“lorem”,
PropertyTwo=“ipsum”,
Child=新的ChildClass{
PropertyTree=“doloreusement”
}
};
var dataList=new List(){data,data2};
IEnumerable results=dataList.Where(d=>d.SelectedFields.MatchIgnoreCase(d,“lorem”).Any();
foreach(结果中的数据类源){
Console.WriteLine(source.ToString());
}
Console.ReadKey();
}
}
在开始实施之前,有一些设计缺陷需要修复
首先,几乎所有查询提供程序(除了LINQ to Object,它只是将lambda表达式编译为委托并执行它们)都不支持调用表达式和自定义(未知)方法。这是因为它们不执行表达式,而是将其转换为其他内容(例如SQL),而转换是基于预先了解的
调用表达式的一个示例是Func
委托。因此,您应该做的第一件事是在当前拥有Func
的任何位置使用Expression
其次,查询表达式树是静态构建的,即没有可用于获取元数据的真实对象实例,因此IFieldExposer
的想法行不通。您需要一个静态公开的表达式列表,如下所示:
class DataClass //: IFieldExposer<DataClass>
{
// ...
public static IEnumerable<Expression<Func<DataClass, string>>> GetFields()
{
return new List<Expression<Func<DataClass, string>>>
{
a => a.PropertyOne,
b => b.Child.PropertyThree
};
}
}
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> Match<T>(this IEnumerable<Expression<Func<T, string>>> fields, string text, bool ignoreCase)
{
Expression<Func<string, bool>> match;
if (ignoreCase)
{
text = text.ToLower();
match = input => input.ToLower().Contains(text);
}
else
{
match = input => input.Contains(text);
}
// T source =>
var parameter = Expression.Parameter(typeof(T), "source");
Expression anyMatch = null;
foreach (var field in fields)
{
// a.PropertyOne --> source.PropertyOne
// b.Child.PropertyThree --> source.Child.PropertyThree
var fieldAccess = field.Body.ReplaceParameter(field.Parameters[0], parameter);
// input --> source.PropertyOne
// input --> source.Child.PropertyThree
var fieldMatch = match.Body.ReplaceParameter(match.Parameters[0], fieldAccess);
// matchA || matchB
anyMatch = anyMatch == null ? fieldMatch : Expression.OrElse(anyMatch, fieldMatch);
}
if (anyMatch == null) anyMatch = Expression.Constant(false);
return Expression.Lambda<Func<T, bool>>(anyMatch, parameter);
}
}
在内部,它使用ExpressionVistor
查找传递的参数表达式的每个实例
,并将其替换为传递的表达式
使用此帮助器方法,实现可以如下所示:
class DataClass //: IFieldExposer<DataClass>
{
// ...
public static IEnumerable<Expression<Func<DataClass, string>>> GetFields()
{
return new List<Expression<Func<DataClass, string>>>
{
a => a.PropertyOne,
b => b.Child.PropertyThree
};
}
}
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> Match<T>(this IEnumerable<Expression<Func<T, string>>> fields, string text, bool ignoreCase)
{
Expression<Func<string, bool>> match;
if (ignoreCase)
{
text = text.ToLower();
match = input => input.ToLower().Contains(text);
}
else
{
match = input => input.Contains(text);
}
// T source =>
var parameter = Expression.Parameter(typeof(T), "source");
Expression anyMatch = null;
foreach (var field in fields)
{
// a.PropertyOne --> source.PropertyOne
// b.Child.PropertyThree --> source.Child.PropertyThree
var fieldAccess = field.Body.ReplaceParameter(field.Parameters[0], parameter);
// input --> source.PropertyOne
// input --> source.Child.PropertyThree
var fieldMatch = match.Body.ReplaceParameter(match.Parameters[0], fieldAccess);
// matchA || matchB
anyMatch = anyMatch == null ? fieldMatch : Expression.OrElse(anyMatch, fieldMatch);
}
if (anyMatch == null) anyMatch = Expression.Constant(false);
return Expression.Lambda<Func<T, bool>>(anyMatch, parameter);
}
}
公共静态部分类表达式utils
{
公共静态表达式匹配(此IEnumerable字段、字符串文本、bool ignoreCase)
{
表情匹配;
如果(忽略案例)
{
text=text.ToLower();
match=input=>input.ToLower().Contains(文本);
}
其他的
{
match=input=>input.Contains(文本);
}
//T来源=>
var参数=表达式参数(类型(T),“源”);
表达式anyMatch=null;
foreach(字段中的变量字段)
{
//a.PropertyOne-->源.PropertyOne
//b.Child.propertytree-->源.Child.propertytree
var fieldAccess=field.Body.ReplaceParameter(field.Parameters[0],parameter);
//输入-->source.PropertyOne
//输入-->source.Child.propertyTree
var fieldMatch=match.Body.ReplaceParameter(match.Parameters[0],fieldAccess);
//matchA | | matchA
anyMatch=anyMatch==null?fieldMatch:Expression.OrElse(anyMatch,fieldMatch);
}
if(anyMatch==null)anyMatch=Expression.Constant(false);
返回表达式.Lambda(anyMatch,参数);
}
}
input=>input.ToLower().Contains(text)
或input=>input.Contains(text)
是我们的编译时匹配表达式,然后我们用传递的Ex
public static Expression<Func<T, bool>> Match<T>(
this IEnumerable<Expression<Func<T, string>>> fields, string text, bool ignoreCase)
var dataList = new List<DataClass> { data };
var query = dataList.AsQueryable()
.Where(DataClass.GetFields().Match("lorem", true));
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : base.VisitParameter(node);
}
}
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> Match<T>(this IEnumerable<Expression<Func<T, string>>> fields, string text, bool ignoreCase)
{
Expression<Func<string, bool>> match;
if (ignoreCase)
{
text = text.ToLower();
match = input => input.ToLower().Contains(text);
}
else
{
match = input => input.Contains(text);
}
// T source =>
var parameter = Expression.Parameter(typeof(T), "source");
Expression anyMatch = null;
foreach (var field in fields)
{
// a.PropertyOne --> source.PropertyOne
// b.Child.PropertyThree --> source.Child.PropertyThree
var fieldAccess = field.Body.ReplaceParameter(field.Parameters[0], parameter);
// input --> source.PropertyOne
// input --> source.Child.PropertyThree
var fieldMatch = match.Body.ReplaceParameter(match.Parameters[0], fieldAccess);
// matchA || matchB
anyMatch = anyMatch == null ? fieldMatch : Expression.OrElse(anyMatch, fieldMatch);
}
if (anyMatch == null) anyMatch = Expression.Constant(false);
return Expression.Lambda<Func<T, bool>>(anyMatch, parameter);
}
}
class DataClass
{
…
static public Expression<Func<DataClass,bool>> MatchSelectedFields( string text, bool ignoreCase)
{
return @this => (
String.Equals( text, @this.PropertyOne, (ignoreCase? StringComparison.OrdinalIgnoreCase: StringComparison.Ordinal))
|| String.Equals( text, @this.Child.PropertyThree, (ignoreCase? StringComparison.OrdinalIgnoreCase: StringComparison.Ordinal))
);
}
}
Expression<Func<DataClass,bool>> match = DataClass.MatchSelectedFields( "lorem", ignoreCase);
IEnumerable<DataClass> results = dataList.Where( d => match(d));