天天看點

利用 DynamicLinq 實作簡單的動态表達式建構查詢

平時使用 LINQ 進行一些簡單的條件拼接查詢一般都會這樣操作:

public class SearchInputDto
{
    public string ConditionA { get; set; }
    public int? ConditionB { get; set; }
    public string ConditionC { get; set; }
}
           

這裡有三個條件,是前端傳入的搜尋條件,然後我們來編寫一個查詢語句:

public Task Search(SearchInputDto input)
{
    var queryResult = _db.Where(z=>(input.ConditionA == null || z.Name == input.ConditionA) 
                                && (input.ConditionB == null || z.Number == input.ConditionB)
                                && (input.ConditionC == null || z.Address == input.ConditionC));
    
    // 執行其他操作...
    
    return Task.FromResult(0);
}
           

因為我們前端傳入的條件不是固定的,是以有可能會出現有的條件沒有傳入的情況,如果是 SQL 的動态拼接 SQL 就可以了,而 Linq 你肯定是沒法動态拼接的,隻有自己建構一個表達式樹傳入到

IQuerable<T>.Where(Expression<Func<T,bool>> expression)

裡面進行查詢。

純手工建構表達式樹也不是不可以,隻是略微麻煩,而我們則可以借助

System.Linq.Dynamic.Core

來友善的實作動态查詢語句拼接。

他的正常用法如下:

官方 WIKI 位址:https://github.com/StefH/System.Linq.Dynamic.Core/wiki/Dynamic-Expressions

var query = db.Customers
              .Where("City == @0 and Orders.Count >= @1", "London", 10)
              .OrderBy("CompanyName")
              .Select("new(CompanyName as Name, Phone)");
           

既然是字元串那麼就可以拼接,我們來做一下改造。

首先去 NuGet 當中搜尋 System.Linq.Dynamic.Core 庫,安裝之後我們來重新編寫之前的查詢範例,首先我們來寫一個建構器,用于建構我們的表達式樹:

using Abp.Runtime.Caching;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Abp.Linq.Expressions
{
    public class ExpressionBuilder<TEntity, TSearchDto>
    {
        // 其實這裡也可以通過傳入 params Expression<Func<TRelateEntity, object>>[] selectFields 來建構
        public Expression<Func<TEntity, bool>> Build(string[] excludeFields, TSearchDto dto)
        {
            var parameters = GenerateParametersDictionary(excludeFields, dto);

            StringBuilder sb = new StringBuilder();
            var fieldNames = parameters.Keys.ToList();

            // 動态拼接
            for (int i = 0; i < fieldNames.Count; i++)
            {
                sb.Append(fieldNames[i]).Append($" == @{i}").Append(" && ");
            }

            var lambdaStr = sb.ToString();
            lambdaStr = lambdaStr.Substring(0, lambdaStr.Length - " && ".Length);

            // 建構表達式
            return DynamicExpressionParser.ParseLambda<TEntity, bool>(new ParsingConfig(), false, lambdaStr, parameters.Values.ToArray());
        }

        // 建構參數/值鍵值對,如果參數值為 NULL 則不進行建構
        private Dictionary<string, object> GenerateParametersDictionary(string[] excludeFields, TSearchDto dto)
        {
            var typeInfo = typeof(TSearchDto);
            var properties = typeInfo.GetProperties();
            var parameters = new Dictionary<string, object>();

            foreach (var property in properties)
            {
                var propertyValue = property.GetValue(dto);

                if (propertyValue == null) continue;
                if (excludeFields == null) continue;
                if (excludeFields.Contains(property.Name)) continue;
                if (parameters.ContainsKey(property.Name)) continue;

                parameters.Add(property.Name, propertyValue);
            }

            return parameters;
        }
    }
}
           

用法很簡單,用剛才的代碼作為一個例子:

public Task Search(SearchInputDto input)
{
    var builder = new ExpressionBuilder<EntityA,SearchInputDto>();
    var queryResult = _db.Where(builder.Build(null,input));
    
    // 執行其他操作...
    
    return Task.FromResult(0);
}
           

可以看到已經變得十分簡潔,這裡僅僅作為抛磚引玉,其實還有更多進階的用法,這裡不再贅述。

繼續閱讀