تمديد Iqueryable أين () كما أو بدلا من العلاقة
-
06-09-2019 - |
سؤال
أنا أستخدم أساليب الإرشاد الخاصة بي ل IQueryable <> لإنشاء استعلامات قابل للتعديل مثل FindAld (). Findinzip (12345) .namestartswith ("XYZ"). orderbyhowiiantit () وما إلى ذلك بعد ذلك على التنفيذ المؤجل يخلق استعلام واحد بناء على بلدي سلسلة طرق التمديد.
المشكلة مع هذا، هي أن كل مكان في سلسلة التمديد (FindXyz، Findinzip وما إلى ذلك) سوف تتحد دائما كما والذي يعني أنني لا أستطيع فعل شيء مثل هذا:
Findall (). Firstnamestartswith ("X"). orlastnamestartswith ("z") لأنني لا أعرف كيف يمكنني حقن أو في طريقة منفصلة فيها.
أي فكرة كيف يمكنني حل هذا؟
إضافي؛ حتى الآن أفهم كيفية سلسلة التعبيرات كما أو إذا قمت بلفها (مثل الترجمة (Firstnamestartswith ("A"). ترسامستارزواي ("z"). orderby (..))
ما أحاول القيام به رغم أنه أكثر تعقيدا قليلا (وتسجيل المساسخ لا يساعد هنا ..) أريد أن أحيل لاحقا IQueryable للوصول بشكل أساسي إلى الظروف التي تم إنشاؤها قبل ذلك دون الحاجة إلى لفها لإنشاء أو بين هم.
نظرا لأن كل طريقة تمديد إرجاع IQueryable <> أفهم أنه يجب أن يكون لها معرفة حول الوضع الحالي لظروف الاستعلام في مكان ما، مما يدفعني إلى الاعتقاد بأنه يجب أن يكون هناك بعض الطرق الآلية أو إنشاء أو عبر كل ما قبل الظروف دون الحاجة إلى التفاف ما تريد أو.
المحلول
أفترض أن الأجزاء المختلفة من الاستعلام معروفة فقط في وقت التشغيل، أي لا يمكنك فقط استخدامها ||
في where
...
خيار كسول واحد هو Concat
- ولكن هذا يميل إلى أن يؤدي إلى ضعف Tsql وغيرها؛ ومع ذلك، فإنني أميل إلى أن أميل إلى كتابة مخصص Expression
s بدلا من ذلك. يعتمد النهج الذي يتعين اتخاذه على ما يدعمه المزود، حيث يدعم LinQ-To-SQL خيارات مختلفة إلى EF (على سبيل المثال) - والتي لها تأثير حقيقي هنا (نظرا لعدم استخدام التعبيرات الفرعية مع EF). هل تستطيع أن تخبرنا بذلك؟
إليك بعض الكود الذي يجب أن يعمل مع LinQ-To-SQL؛ إذا قمت ببناء مجموعة (أو قائمة، والاتصال .ToArray()
) من التعبيرات، يجب أن تعمل بشكل جيد؛ على سبيل المثال هو LinQ-to-cossss، ولكن يجب أن لا تزال تعمل:
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 عن طريق تمديد التعبيرات. أيضا مصدر طريقة التمديد التي أستخدمها.