IQueryable ()などORの代わりに、AND関係を拡張
-
06-09-2019 - |
質問
IなどのfindAll()。FindInZip(12345).NameStartsWith(「XYZ」)などのチェーン可能クエリを作成するために> <のIQueryableの私自身の拡張メソッドを使用しています。OrderByHowIWantIt()など私の拡張メソッドチェーンに基づいています。
これに伴う問題しかし、延長チェーン(FindXYZ、FindInZipなど)でのWHEREすべてがいつものように結合することで、どの私はこのような何かを行うことができないことを意味します:
のfindAll()。FirstNameStartsWith( "X")。OrLastNameStartsWith( "Z")私は別々に注入ORできるかわからないので、どこ方法ます。
私はこの問題を解決することができますどのように任意のアイデア?
<時間>追加。 これまでのところ私はどのように私はそれらをラップする(例えばCompileAsOr(FirstNameStartsWith( "A")。LastNameStartsWith( "Z")。のOrderBy(..))
などの場合やチェーン表現をするために理解します私はかかわらず、やろうとしていることは少し複雑である(とPredicateBuilderがここには役立ちません。)その中で私は、後のIQueryableは基本的に作成するためにそれらをラップすることなく、前に確立された場合には条件をアクセスしたいですそれらの間それともます。
は、それぞれの拡張メソッドは、私はそれはいくつかの自動化された方法またはなし以前のすべての条件間または作成がなければならないと信じて私をリードどこかに、クエリ条件の現在の状態に関する知識を持っている必要があることを理解>のIQueryableを<返したようあなたは論理和演算をしたいものをラップすること。
解決
私は
...あなただけの||
でwhere
を使用することはできませんつまり、クエリの異なる部分は、実行時にのみ知られていると仮定しています
は一つの怠惰なオプションがConcat
ある - これは貧しいTSQLなどにつながる傾向があります。しかし、私が代わりにカスタムExpression
sを書くために傾斜する傾向があります。 (あなたがEFでサブ式を使用することはできませんので)、ここで本物の影響を与えている - 取るアプローチは、SQLは、LINQツーEFに(たとえば)異なるオプションをサポートしているとして、プロバイダが、何であるかに依存します。あなたは私たちに伝えることができた?
ここでLINQツーSQLで動作する必要のあるいくつかのコードです。あなたは配列を作成(またはリスト、および.ToArray()
呼び出し)式であれば、それが正常に動作する必要があります。例では、LINQツーオブジェクトですが、まだ動作する必要があります:
static void Main()
{
var data = (new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).AsQueryable();
var predicates = new List<Expression<Func<int, bool>>>();
predicates.Add(i => i % 3 == 0);
predicates.Add(i => i >= 8);
foreach (var item in data.WhereAny(predicates.ToArray()))
{
Console.WriteLine(item);
}
}
public static IQueryable<T> WhereAny<T>(
this IQueryable<T> source,
params Expression<Func<T,bool>>[] predicates)
{
if (source == null) throw new ArgumentNullException("source");
if (predicates == null) throw new ArgumentNullException("predicates");
if (predicates.Length == 0) return source.Where(x => false); // no matches!
if (predicates.Length == 1) return source.Where(predicates[0]); // simple
var param = Expression.Parameter(typeof(T), "x");
Expression body = Expression.Invoke(predicates[0], param);
for (int i = 1; i < predicates.Length; i++)
{
body = Expression.OrElse(body, Expression.Invoke(predicates[i], param));
}
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return source.Where(lambda);
}
他のヒント
PredicateBuilder<T>
して使用してください。それはあなたが望むものはおそらくです。
List<string> fruits =
new List<string> { "apple", "passionfruit", "banana", "mango",
"orange", "blueberry", "grape", "strawberry" };
var query = fruits.AsQueryable();
// Get all strings whose length is less than 6.
query = query.Where(fruit => fruit.Length < 6);
// Hope to get others where length is more than 8. But you can't, they're gone.
query = query.Where(fruit => 1 == 1 || fruit.Length > 8);
foreach (string fruit in query)
Console.WriteLine(fruit);
理想的な世界で、私は個人的に||
と&&
演算子は、最もシンプルで読みやすいと思います。しかし、それはコンパイルされません。
演算子 '||'型「
のオペランドに適用することはできませんExpression<Func<YourClass,bool>>
」と「Expression<Func<YourClass,bool>>
」
このため、私はこのための拡張メソッドを使用します。あなたの例では、次のようになります。
.Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now))
ます。
の代わりに:
.Where(FindInZip(12345) || NameStartsWith("XYZ") && (PostedOnOrAfter(DateTime.Now))
ます。
式の例:
private Expression<Func<Post,bool>> PostedOnOrAfter(DateTime cutoffDate)
{
return post => post.PostedOn >= cutoffDate;
};
拡張方法:
public static class PredicateExtensions
{
/// <summary>
/// Begin an expression chain
/// </summary>
/// <typeparam id="T""></typeparam>
/// <param id="value"">Default return value if the chanin is ended early</param>
/// <returns>A lambda expression stub</returns>
public static Expression<Func<T, bool>> Begin<T>(bool value = false)
{
if (value)
return parameter => true; //value cannot be used in place of true/false
return parameter => false;
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
return CombineLambdas(left, right, ExpressionType.AndAlso);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
return CombineLambdas(left, right, ExpressionType.OrElse);
}
#region private
private static Expression<Func<T, bool>> CombineLambdas<T>(this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right, ExpressionType expressionType)
{
//Remove expressions created with Begin<T>()
if (IsExpressionBodyConstant(left))
return (right);
ParameterExpression p = left.Parameters[0];
SubstituteParameterVisitor visitor = new SubstituteParameterVisitor();
visitor.Sub[right.Parameters[0]] = p;
Expression body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
private static bool IsExpressionBodyConstant<T>(Expression<Func<T, bool>> left)
{
return left.Body.NodeType == ExpressionType.Constant;
}
internal class SubstituteParameterVisitor : ExpressionVisitor
{
public Dictionary<Expression, Expression> Sub = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node)
{
Expression newValue;
if (Sub.TryGetValue(node, out newValue))
{
return newValue;
}
return node;
}
}
#endregion
}
式を拡張することでLINQクエリについては本当に良い記事。また、私が使って拡張メソッドのソース。
<のhref = "https://www.red-gate.com/simple-talk/dotnet/net-framework/giving-clarity-to-linq-queries-by-extending-expressions/" のrel =」 noreferrer nofollowを "> https://www.red-gate.com/simple-talk/dotnet/net-framework/giving-clarity-to-linq-queries-by-extending-expressions/ の