6

アイテムの配列で Contains を呼び出すラムダ式でジェネリック メソッドを呼び出す方法を探しています。

この場合、Entity Framework Where メソッドを使用していますが、シナリオは他の IEnumerables にも適用できます。

リフレクションを介して上記のコードの最後の行を呼び出す必要があるため、任意の型と任意のプロパティを使用して、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?**

研究では、GetMethod、MakeGenericType、および Expression を使用してそれを実現する必要があることに気付きましたが、その方法がわかりませんでした。リフレクションがラムダとジェネリックの概念でどのように機能するかを理解できるように、このサンプルがあると非常に役立ちます。

基本的に、目的は次のような関数の正しいバージョンを書くことです:

//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
}

式がどのように機能するかを明確に理解できたとは思わないため、他の質問がこのシナリオに対応しているかどうかはわかりません。

アップデート:

実行時に (Contains で) テストするタイプとプロパティしかないため、ジェネリックを使用できません。最初のコード サンプルでは、​​コンパイル時に "MyType" が不明であるとします。2 番目のコード サンプルでは、​​型をパラメーターとして GetFilteredList 関数に渡すか、リフレクション (GetGenericArguments) を介して取得することができます。

ありがとう、

4

3 に答える 3

5

ジェネリックの使用を避けるために (型は設計時に不明なため)、何らかのリフレクションを使用して「手作業で」式を作成することができます。

これを行うには、1 つの Where 句内で "Contains" 式を定義する必要があります。

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

この場合、「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);
    }
于 2013-07-04T20:29:52.923 に答える
0

次の一連のクラスを使用して、問題を解決できます。

まず、ソース配列から選択する項目を決定する Contains クラスを作成する必要があります。

class Contains
{
    public bool Value { get; set; }

    public Contains(object[] items, object item)
    {
        Value = (bool)(typeof(Enumerable).GetMethods()
                                         .Where(x => x.Name.Contains("Contains"))
                                         .First()
                                         .MakeGenericMethod(typeof(object))
                                         .Invoke(items, new object[] { items, item }));
    }
}

次に、選択される項目に基づいて述語を形成するために使用される Where クラスを作成する必要があります。この場合、述語メソッドに Contains クラスを使用することは明らかです。

class Where
{
    public object Value { get; set; }

    public Where(object[] items, object[] items2)
    {
        Value = typeof(Enumerable).GetMethods()
                                  .Where(x => x.Name.Contains("Where"))
                                  .First()
                                  .MakeGenericMethod(typeof(object))
                                  .Invoke(items2, new object[] { items2, new Func<object, bool>(i => new Contains(items, i).Value) });
    }
}

最後の手順は、Where クラスから取得した結果を呼び出すだけです。これは実際には Enumerable.WhereArrayIterator 型であり、List 型ではありません。Where 拡張メソッドの結果は遅延実行の結果であるためです。

したがって、ToList 拡張メソッドを呼び出して非遅延オブジェクトを作成し、結果を取得する必要があります。

class ToList
{
    public List<object> Value { get; set; }

    public ToList(object[] items, object[] items2)
    {
        var where = new Where(items, items2).Value;

        Value = (typeof(Enumerable).GetMethods()
                                  .Where(x => x.Name.Contains("ToList"))
                                  .First()
                                  .MakeGenericMethod(typeof(object))
                                  .Invoke(where, new object[] { where })) as List<object>;
    }
}

最後に、次のクラスを使用してプロセス全体を簡単にテストできます。

class Program
{
    static void Main()
    {
        var items = new object[] { 1, 2, 3, 4 };
        var items2 = new object[] { 2, 3, 4, 5 };

        new ToList(items, items2).Value.ForEach(x => Console.WriteLine(x));

        Console.Read();
    }
}
于 2013-07-02T01:26:28.543 に答える