7

匿名オブジェクトで構成されるLINQクエリがあります。

ある時点で、着信検索パラメーターによって結果を制限したいのですが、これは1つ以上のパラメーターである可能性があり、それらを使用して「LIKE x OR LIKE yORLIKEz」を実行したいと思います。

コードでは、次のようになります。

reservations = reservations.Where(r =>
  r.GuestLastName.Contains(parameter1) || r.GuestFirstName.Contains(parameter1) || 
  r.GuestLastName.Contains(parameter2) || r.GuestFirstName.Contains(parameter2) || 
  // Parameter 3, 4, 5,..
);

reservationsそれがタイプであることを知って、どうすればこれを動的に構築できIQueryable<'a> (anonymous object)ますか?私はさまざまなリソースを調べましたが、匿名タイプを使用しているときではなく、タイプを知っているときにのみそれを行う方法を見つけることができるようです。

これはLinqtoSQLであることを知っておくことが重要です。そのため、SQLクエリに変換する必要があり、メモリでフィルタリングしないでください...

4

3 に答える 3

3

次の 2 つの方法があります。

  1. ExpressionCoincoinが指摘したように、を構築する
  2. すべてのパラメータを配列に入れ、次を使用しAnyます。

    var parameters = new [] { parameter1, parameter2, /*...*/ }
    reservations = reservations
        .Where(r => 
            parameters.Any(p => r.GuestFirstName.Contains(p)
                                || r.GuestLastName.Contains(p)));
    
于 2012-11-27T15:32:57.243 に答える
1

独自の汎用拡張メソッドを作成します。

public static class CollectionHelper
{
    public static IQueryable Filter<T>(this IQueryable source, string[] properties, string[] values)
    {
        var lambda = CombineLambdas<T>(properties, values);
        var result = typeof (Queryable).GetMethods().First(
            method => method.Name == "Where"
                      && method.IsGenericMethodDefinition)
                                       .MakeGenericMethod(typeof (T))
                                       .Invoke(null, new object[] {source, lambda});
        return (IQueryable<T>) result;
    }

    // combine lambda expressions using OR operator
    private static LambdaExpression CombineLambdas<T>(string[] properties, string[] values)
    {
        var param = Expression.Parameter(typeof (T));
        LambdaExpression prev = null;
        foreach (var value in values)
        {
            foreach (var property in properties)
            {
                LambdaExpression current = GetContainsExpression<T>(property, value);
                if (prev != null)
                {
                    Expression body = Expression.Or(Expression.Invoke(prev, param),
                                                    Expression.Invoke(current, param));
                    prev = Expression.Lambda(body, param);
                }
                prev = prev ?? current;
            }
        }
        return prev;
    }

    // construct expression tree to represent String.Contains
    private static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string propertyValue)
    {
        var parameterExp = Expression.Parameter(typeof (T), "type");
        var propertyExp = Expression.Property(parameterExp, propertyName);
        var method = typeof (string).GetMethod("Contains", new[] {typeof (string)});
        var someValue = Expression.Constant(propertyValue, typeof (string));
        var containsMethodExp = Expression.Call(propertyExp, method, someValue);

        return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
    }
}

と使用法:

var reservations = new List<TheType>()  // sample collection
    {
        new TheType {FirstName = "aa", LastName = "bb"},
        new TheType {FirstName = "cc", LastName = "dd"},
        new TheType {FirstName = "ee", LastName = "ff"}
    }.AsQueryable();

var filtered = reservations
    .Filter<TheType>(new[] {"FirstName", "LastName"}, new[] {"d", "e"});
/* returnes 2 elements:
 * {FirstName = "cc", LastName = "dd"} and {FirstName = "ee", LastName = "ff"} */

あなたが望む一般的な解決策はわかりません-存在する場合は、必要なフィルターを動的に構築することでケースを解決する代替案として受け入れられることを願っています.

于 2012-11-27T17:57:33.843 に答える
0

いくつかのデバッグ後に解決策を見つけましたが、FirstName 用と LastName 用の複数のセレクターを持つ WhereFilter を作成します。

これは拡張メソッドです:

public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, string[] possibleValues, params Expression<Func<T, string>>[] selectors)
{
    List<Expression> expressions = new List<Expression>();

    var param = Expression.Parameter(typeof(T), "p");

    var bodies = new List<MemberExpression>();
    foreach (var s in selectors)
    {
        bodies.Add(Expression.Property(param, ((MemberExpression)s.Body).Member.Name));
    }

    foreach (var v in possibleValues)
    {
        foreach(var b in bodies) {
            expressions.Add(Expression.Call(b, "Contains", null, Expression.Constant(v)));
        }
    }

    var finalExpression = expressions.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal));

    return source.Where(Expression.Lambda<Func<T, bool>>(finalExpression, param));
}

次のように使用できます。

reservations = reservations.WhereFilter(
    array_of_allowed_values,
    r => r.GuestFirstName,
    r => r.GuestLastName
);

クエリのトレース文字列を確認したところ、実際に SQL に変換されたので、データベースでフィルタリングが実行されます。

于 2012-11-28T10:36:59.023 に答える