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));