簡體   English   中英

沒有PredicateBuilder的LINQ中的動態OR

[英]Dynamic OR in LINQ without the PredicateBuilder

我正在構建一個方法,該方法采用一個或多個標准來使用LINQ查詢數據庫。 我做的:

public ICollection<MyClass> FindAllBy(params Expression<Func<MyClass, bool>>[] criteria)
    {
        using (var ctx = new MyContext())
        {
            IQueryable<MyClass> result = ctx.MyClasses.AsNoTracking();

            if (criteria != null && criteria.Length > 0)
            {
                foreach (var item in criteria)
                {
                    result = result.Where(item);
                }
            }

            return result.ToList();
        }
    }

這樣做的結果是,如果我查找具有Id 1的對象和具有Id 2的對象,我什么也得不到,因為沒有行的Id都是1和2.所以我需要一個OR子句。 我找到了這個頁面:

http://www.albahari.com/nutshell/predicatebuilder.aspx

它有一個PredicateBuilder類,我曾經這樣做過:

    public ICollection<PhotoFile> FindAllBy(params Expression<Func<PhotoFile, bool>>[] criteria)
    {
        using (var ctx = new CotanContext())
        {
            var predicate = PredicateBuilder.False<PhotoFile>();

            if (criteria != null && criteria.Length > 0)
            {
                foreach (var item in criteria)
                {
                    predicate = predicate.Or(item);
                }
            }

            return ctx.PhotoFiles.Where(predicate).ToList();
        }
    }

我的代碼與頁面略有不同,因為我將表達式傳遞給方法,然后將其傳遞給Predicate.Or方法。

上面的方法給出了The LINQ expression node type 'Invoke' is not supported in LINQ to Entities. 錯誤。 這是有道理的,因為實體框架不知道如何將此代碼轉換為有效的查詢。

鏈接站點上的解決方案是下載他們的Nuget包或源代碼,這使得此代碼可以正常工作。 但是,我覺得在單個函數中放入數百行未知和看似未經測試的代碼並不是很舒服,在我看來,微軟應該已經在LINQ中構建了這些代碼。 我的項目的主要開發人員過去也強烈反對使用不直接來自Microsoft的未知軟件包。 我正在處理敏感信息,所以我寧願安全而不是抱歉。

所以,我的問題是:有沒有辦法在LINQ中獲得OR函數而不必使用外部Nuget包?

正如我在評論中提到的,您可以使用Universal PredicateBulder或我的答案中的類來建立linq到實體where子句中兩個列表之間的鏈接

但是,通過使用這個簡單的擴展方法,您可以大大簡化示例中的方法:

public static class QueryableExtensions
{
    public static IQueryable<T> WhereAny<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, bool>>> predicates)
    {
        if (predicates == null || !predicates.Any()) return source;
        var predicate = predicates.Aggregate((a, b) => Expression.Lambda<Func<T, bool>>(
            Expression.OrElse(a.Body, b.Body.ReplaceParameter(b.Parameters[0], a.Parameters[0])),
            a.Parameters[0]));
        return source.Where(predicate);
    }
}

而這又使用了這個幫手:

public static class ExpressionUtils
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == Source ? Target : base.VisitParameter(node);
        }
    }
}

現在示例方法可以很簡單:

public ICollection<PhotoFile> FindAllBy(params Expression<Func<PhotoFile, bool>>[] criteria)
{
    using (var ctx = new CotanContext())
        return ctx.PhotoFiles.WhereAny(criteria).ToList();
}

簡短回答:是的。 如果查看PredicateBuilder代碼,它會使用表達式來構造查詢。 所以你可以嘗試重新創建這種行為,但是之前已經做過,為什么不嘗試修復你的代碼呢?

我在我的存儲庫類中經常使用PredicateBuilder,我記得有同樣的問題。 我通過在每次調用Entity Framework時添加AsExpandable()(在LinqKit命名空間中)解決了這個問題:

return ctx.PhotoFiles.AsExpandable().Where(predicate).ToList();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM