用lambda表达式参数调用泛型方法的反射

nat*_*nho 6 c# generics reflection lambda where

我正在寻找一种方法来调用带有lambda表达式的泛型方法,该表达式在一个项目数组中调用Contains.

在这种情况下,我正在使用Entity Framework Where方法,但该方案可以应用于其他IEnumerables.

我需要通过Reflection调用上面代码的最后一行,所以我可以使用任何类型和任何属性传递给Contains方法.

var context = new TestEntities();

var items = new[] {100, 200, 400, 777}; //IN list (will be tested through Contains)
var type = typeof(MyType); 

context.Set(type).Where(e => items.Contains(e.Id)); //**What is equivalent to this line using Reflection?**
Run Code Online (Sandbox Code Playgroud)

在研究中,我注意到我应该使用GetMethod,MakeGenericType和Expression来实现这一点,但我无法弄清楚如何去做.拥有这个样本非常有帮助,因此我可以理解Reflection如何与Lambda和Generic概念一起工作.

基本上,目标是编写一个正确版本的函数,如下所示:

//Return all items from a IEnumerable(target) that has at least one matching Property(propertyName) 
//with its value contained in a IEnumerable(possibleValues)
static IEnumerable GetFilteredList(IEnumerable target, string propertyName, IEnumerable searchValues)
{
    return target.Where(t => searchValues.Contains(t.propertyName));
    //Known the following:
    //1) This function intentionally can't be compiled
    //2) Where function can't be called directly from an untyped IEnumerable
    //3) t is not actually recognized as a Type, so I can't access its property 
    //4) The property "propertyName" in t should be accessed via Linq.Expressions or Reflection
    //5) Contains function can't be called directly from an untyped IEnumerable
}

//Testing environment
static void Main()
{
    var listOfPerson = new List<Person> { new Person {Id = 3}, new Person {Id = 1}, new Person {Id = 5} };
    var searchIds = new int[] { 1, 2, 3, 4 };

    //Requirement: The function must not be generic like GetFilteredList<Person> or have the target parameter IEnumerable<Person>
    //because the I need to pass different IEnumerable types, not known in compile-time
    var searchResult = GetFilteredList(listOfPerson, "Id", searchIds);

    foreach (var person in searchResult)
        Console.Write(" Found {0}", ((Person) person).Id);

    //Should output Found 3 Found 1
}
Run Code Online (Sandbox Code Playgroud)

我不确定其他问题是否解决了这种情况,因为我认为我不能清楚地理解表达式是如何工作的.

更新:

我不能使用泛型,因为我在运行时只有要测试的类型和属性(在Contains中).在第一个代码示例中,假设在编译时不知道"MyType".在第二个代码示例中,类型可以作为参数传递给GetFilteredList函数,也可以通过Reflection(GetGenericArguments)获取.

谢谢,

nat*_*nho 13

经过广泛的研究和大量的表达式研究后,我可以自己编写解决方案.它当然可以改进,但完全符合我的要求.希望它可以帮助别人.

//Return all items from a IEnumerable(target) that has at least one matching Property(propertyName) 
//with its value contained in a IEnumerable(possibleValues)
static IEnumerable GetFilteredList(IEnumerable target, string propertyName, IEnumerable searchValues)
{
    //Get target's T 
    var targetType = target.GetType().GetGenericArguments().FirstOrDefault();
    if (targetType == null)
        throw new ArgumentException("Should be IEnumerable<T>", "target");

    //Get searchValues's T
    var searchValuesType = searchValues.GetType().GetGenericArguments().FirstOrDefault();
    if (searchValuesType == null)
        throw new ArgumentException("Should be IEnumerable<T>", "searchValues");

    //Create a p parameter with the type T of the items in the -> target IEnumerable<T>
    var containsLambdaParameter = Expression.Parameter(targetType, "p");

    //Create a property accessor using the property name -> p.#propertyName#
    var property = Expression.Property(containsLambdaParameter, targetType, propertyName);

    //Create a constant with the -> IEnumerable<T> searchValues
    var searchValuesAsConstant = Expression.Constant(searchValues, searchValues.GetType());

    //Create a method call -> searchValues.Contains(p.Id)
    var containsBody = Expression.Call(typeof(Enumerable), "Contains", new[] { searchValuesType }, searchValuesAsConstant, property);

    //Create a lambda expression with the parameter p -> p => searchValues.Contains(p.Id)
    var containsLambda = Expression.Lambda(containsBody, containsLambdaParameter);

    //Create a constant with the -> IEnumerable<T> target
    var targetAsConstant = Expression.Constant(target, target.GetType());

    //Where(p => searchValues.Contains(p.Id))
    var whereBody = Expression.Call(typeof(Enumerable), "Where", new[] { targetType }, targetAsConstant, containsLambda);

    //target.Where(p => searchValues.Contains(p.Id))
    var whereLambda = Expression.Lambda<Func<IEnumerable>>(whereBody).Compile();

    return whereLambda.Invoke();
}
Run Code Online (Sandbox Code Playgroud)


Gab*_*pes 5

为了避免使用泛型(由于在设计时不知道类型),可以使用一些反射并“手动”构建表达式

为此,您需要在一个Where子句中定义一个“包含”表达式:

public IQueryable GetItemsFromContainsClause(Type type, IEnumerable<string> items)
    {
        IUnitOfWork session = new SandstoneDbContext();
        var method = this.GetType().GetMethod("ContainsExpression");
        method = method.MakeGenericMethod(new[] { type });

        var lambda = method.Invoke(null, new object[] { "Codigo", items });
        var dbset = (session as DbContext).Set(type);
        var originalExpression = dbset.AsQueryable().Expression;

        var parameter = Expression.Parameter(type, "");
        var callWhere = Expression.Call(typeof(Queryable), "Where", new[] { type }, originalExpression, (Expression)lambda);
        return dbset.AsQueryable().Provider.CreateQuery(callWhere);

    }

    public static Expression<Func<T, bool>> ContainsExpression<T>(string propertyName, IEnumerable<string> values)
    {
        var parameterExp = Expression.Parameter(typeof(T), "");
        var propertyExp = Expression.Property(parameterExp, propertyName);
        var someValue = Expression.Constant(values, typeof(IEnumerable<string>));
        var containsMethodExp = Expression.Call(typeof(Enumerable), "Contains", new[] { typeof(string) }, someValue, propertyExp);
        return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
    }
Run Code Online (Sandbox Code Playgroud)

在这种情况下,“ Codigo”是硬编码的,但是它可能是获取您定义的类型的任何属性的参数。

您可以使用以下方法进行测试:

public void LambdaConversionBasicWithEmissor()
    {
        var cust= new Customer();
        var items = new List<string>() { "PETR", "VALE" };
        var type = cust.GetType();
        // Here you have your results from the database
        var result = GetItemsFromContainsClause(type, items);
    }
Run Code Online (Sandbox Code Playgroud)