Linq to Entity 多條件 OR查詢


技術背景:框架MVC,linq to Entity 需要一定的lambda書寫能力

問題:在簡單的orm中完成一些簡單的增刪查改是通過where insert delete update 完成的,但是在這個過程中出現了一個需求:多項條件的and 和or 的組合查詢

眾所周知直接通過linq 寫的話很方便,但是我們的框架使用了linq to entity,如果只用lambda來寫比較復雜的and 和or 查詢就比較麻煩了。

一:簡單and 和or 查詢

public void TextFoZK()
        {
            using (var dbContext = new CRMDbContext())
            {
                //第一句解析出來的sql是  select * from membertype where commercialtenantid=0 or name='住客'
                dbContext.MemberType.Where(m => m.CommercialTenantID == 0 || m.Name == "住客");
                //第二句解析出來的sql是  select * from membertype where commercialtenantid=0 and name='住客'
                dbContext.MemberType.Where(m => m.CommercialTenantID == 0 && m.Name == "住客");
            }
        }

二:復雜邏輯的and 和or 查詢

public void TextFoZK(int status, string name, int commercialtenantid)
        {
            using (var dbContext = new CRMDbContext())
            {
                IQueryable<MemberType> iqm = dbContext.MemberType;
                if (status > 0)
                {
                    iqm = iqm.Where(m => m.Status == status);
                }
                if (!string.IsNullOrEmpty(name))
                {
                    iqm = iqm.Where(m => m.Name == name && m.CommercialTenantID == commercialtenantid);
                }
                iqm = iqm.Where(m => m.ID > 0 || m.ID == 1);
                iqm.ToList();
                //select * from membertype where (status=1) and (name='住客' and commercialtenantid=1) and (id>0 or id=1)
            }
        }

這里使用了IQuerable的擴展方法where ,代表着每個iquerable之間為and 關系,但是又可以包含or 

三:復雜and 和 or

public void TextFoZK(int status, string name, int commercialtenantid)
        {
            using (var dbContext = new CRMDbContext())
            {
                IQueryable<MemberType> iqm = dbContext.MemberType;
                if (status > 0)
                {
                    iqm = iqm.Where(m => m.Status == status);
                }
                if (!string.IsNullOrEmpty(name))
                {
                    iqm = iqm.Where(m => m.Name == name && m.CommercialTenantID == commercialtenantid);
                }
                //重新聲明一個iq,兩個iq 之間為or 關系
                IQueryable<MemberType> iqmtwo = dbContext.MemberType;
                iqmtwo = iqmtwo.Where(m => m.ID > 0 || m.ID == 1);
                iqm = iqm.Union(iqmtwo);
                iqm.ToList();
                
            }
        }

這里使用了iquerable中的擴展方法union 可以把多個iq方法合成為一個iq ,之間為union all  關系

第一個iq 為一個結果集,第二個為一個結果集,最后合並兩個結果集。

可以滿足一個sql過程中查詢多處結果的要求,但是生成的sql還是有點麻煩

 1 exec sp_executesql N'SELECT TOP (10) 
 2 [Project4].[C1] AS [C1], 
 3 [Project4].[C2] AS [C2], 
 4 [Project4].[C3] AS [C3], 
 5 [Project4].[C4] AS [C4], 
 6 [Project4].[C5] AS [C5], 
 7 [Project4].[C6] AS [C6], 
 8 [Project4].[C7] AS [C7], 
 9 [Project4].[C8] AS [C8], 
10 [Project4].[C9] AS [C9], 
11 [Project4].[C10] AS [C10]
12 FROM ( SELECT [Project4].[C1] AS [C1], [Project4].[C2] AS [C2], [Project4].[C3] AS [C3], [Project4].[C4] AS [C4], [Project4].[C5] AS [C5], [Project4].[C6] AS [C6], [Project4].[C7] AS [C7], [Project4].[C8] AS [C8], [Project4].[C9] AS [C9], [Project4].[C10] AS [C10], row_number() OVER (ORDER BY [Project4].[C1] ASC) AS [row_number]
13     FROM ( SELECT 
14         [Distinct1].[C1] AS [C1], 
15         [Distinct1].[C2] AS [C2], 
16         [Distinct1].[C3] AS [C3], 
17         [Distinct1].[C4] AS [C4], 
18         [Distinct1].[C5] AS [C5], 
19         [Distinct1].[C6] AS [C6], 
20         [Distinct1].[C7] AS [C7], 
21         [Distinct1].[C8] AS [C8], 
22         [Distinct1].[C9] AS [C9], 
23         [Distinct1].[C10] AS [C10]
24         FROM ( SELECT DISTINCT 
25             [UnionAll1].[ID] AS [C1], 
26             [UnionAll1].[CommercialTenantID] AS [C2], 
27             [UnionAll1].[Name] AS [C3], 
28             [UnionAll1].[Status] AS [C4], 
29             [UnionAll1].[Discount] AS [C5], 
30             [UnionAll1].[GiveIntegralScale] AS [C6], 
31             [UnionAll1].[Creator] AS [C7], 
32             [UnionAll1].[CreatorID] AS [C8], 
33             [UnionAll1].[GMT_Create] AS [C9], 
34             [UnionAll1].[GMT_Modified] AS [C10]
35             FROM  (SELECT 
36                 [Extent1].[ID] AS [ID], 
37                 [Extent1].[CommercialTenantID] AS [CommercialTenantID], 
38                 [Extent1].[Name] AS [Name], 
39                 [Extent1].[Status] AS [Status], 
40                 [Extent1].[Discount] AS [Discount], 
41                 [Extent1].[GiveIntegralScale] AS [GiveIntegralScale], 
42                 [Extent1].[Creator] AS [Creator], 
43                 [Extent1].[CreatorID] AS [CreatorID], 
44                 [Extent1].[GMT_Create] AS [GMT_Create], 
45                 [Extent1].[GMT_Modified] AS [GMT_Modified]
46                 FROM [dbo].[commercialtenant_membertype] AS [Extent1]
47                 WHERE [Extent1].[CommercialTenantID] = @p__linq__0
48             UNION ALL
49                 SELECT 
50                 [Extent2].[ID] AS [ID], 
51                 [Extent2].[CommercialTenantID] AS [CommercialTenantID], 
52                 [Extent2].[Name] AS [Name], 
53                 [Extent2].[Status] AS [Status], 
54                 [Extent2].[Discount] AS [Discount], 
55                 [Extent2].[GiveIntegralScale] AS [GiveIntegralScale], 
56                 [Extent2].[Creator] AS [Creator], 
57                 [Extent2].[CreatorID] AS [CreatorID], 
58                 [Extent2].[GMT_Create] AS [GMT_Create], 
59                 [Extent2].[GMT_Modified] AS [GMT_Modified]
60                 FROM [dbo].[commercialtenant_membertype] AS [Extent2]
61                 WHERE (0 = [Extent2].[CommercialTenantID]) AND (N''住客'' = [Extent2].[Name])) AS [UnionAll1]
62         )  AS [Distinct1]
63     )  AS [Project4]
64 )  AS [Project4]
65 WHERE [Project4].[row_number] > 0
66 ORDER BY [Project4].[C1] ASC',N'@p__linq__0 int',@p__linq__0=1
sql 想看的可以打開看看

最后提供一種擴展方法

四:多條件之間均為or

/// <summary>
        /// 傳入條件之間為OR查詢
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="predicates"></param>
        /// <returns></returns>
        public static IQueryable<T> WhereOR<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);
        }
public void TextFoZK(int status, string name, int commercialtenantid)
        {
            using (var dbContext = new CRMDbContext())
            {
                IQueryable<MemberType> iqm = dbContext.MemberType;
                if (status > 0)
                {
                    iqm = iqm.Where(m => m.Status == status);
                }
                if (!string.IsNullOrEmpty(name))
                {
                    iqm = iqm.Where(m => m.Name == name && m.CommercialTenantID == commercialtenantid);
                }
                var predicates = new List<Expression<Func<MemberType, bool>>>();
                predicates.Add(m => m.CommercialTenantID == 0 && m.Name == "住客");
                predicates.Add(m=>m.ID>0);
                //這兩個條件之間為or 
                //與iqm之間為and 
                //如果要與iqm之間為or 也可以使用union方法,但是總感覺有點麻煩
                iqm = iqm.WhereOR(predicates.ToArray());
                iqm.ToList();
                //select * from membertype where (status=1) and (name='住客' and commercialtenantid=1) or (id>0 or id=1)
            }
        }

我感覺已經研究到這一步了索性就再往深的看一看,於是我找到了IQuerable的where 和union 的底層方法

 
        
 public static IQueryable<TSource> Union<TSource>(this IQueryable<TSource> source1, IEnumerable<TSource> source2)
        {
            if (source1 == null)
            {
                throw System.Linq.Error.ArgumentNull("source1");
            }
            if (source2 == null)
            {
                throw System.Linq.Error.ArgumentNull("source2");
            }
            return source1.Provider.CreateQuery<TSource>(Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source1.Expression, GetSourceExpression<TSource>(source2) }));
        }
 [__DynamicallyInvokable]
        public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
        {
            if (source == null)
            {
                throw System.Linq.Error.ArgumentNull("source");
            }
            if (predicate == null)
            {
                throw System.Linq.Error.ArgumentNull("predicate");
            }
            return source.Provider.CreateQuery<TSource>(Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression, Expression.Quote(predicate) }));
        }

只是淺薄只能看到這一步。

最后我還求助大神,大神又提出一個方案使用的是expresion方法,這個其實就是我上面提供的whereor 方法內的同樣技術,只不過我是封裝了的。

Expression<Func<MemberType, bool>> funtyps = c => c.ID > 0;
Expression<Func<MemberType, bool>> ortype = c => c.CommercialTenantID == 0 && c.Name == "住客";
funtyps = funtyps.Or(ortype);
iqmemebertype = iqmemebertype.Where(funtyps);

應該是是要更好的方案,我只是記錄我目前理解的方法。

 最后附上關於expression的底層方法or 和 and ,提供了express語句之間可或與查詢的接口

    /// <summary>
    /// 用於多條件動態查詢
    /// zk(-_-)
    /// </summary>
    public static class PredicateBuilderUtility
    {
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
            // apply composition of lambda expression bodies to parameters from the first expression 
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }
        /// <summary>
        /// 動態And
        /// </summary>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.AndAlso);
        }
        /// <summary>
        /// 動態Or
        /// </summary>
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }
        /// <summary>
        /// 傳入條件之間為OR查詢
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="predicates"></param>
        /// <returns></returns>
        public static IQueryable<T> WhereOR<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);
        }
       
    }
expression

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM