List 列表通用過濾模塊設計


需求描述

數據列表如List<Customer> 加載到DataGridView后,如果記錄比較多可能需要對其進行二次過濾,即客戶端過濾
過濾條件做成可由用戶設置的,如下圖:

在數據源是DataTable時,使用DataView的RowFilter可以輕松按用戶的配置拼接出過濾表達式字符串來,
設置RowFilter就可以實現過濾效果,但是當數據源是List<T>這樣由EF,Linq to sql 等框架返回的集合時要實現上面的功能就需要費點力氣了。

問題分析:

首先參考上面的截圖,用戶設置好過濾條件后會形成:" (工號 = 222 And 部門=人力) Or 性別=女" 這樣的過濾表達式,可以表示成(Exp1 And Exp2) Or Exp3 這樣的形式.針對"工號=222"這樣的Exp求值我們會轉變成針對Employe實體的EmpId屬性是否等於222的判斷(Employe.EmpId==222),這個可以通過反射方式來實現,將多個Exp求值的結果通過And或Or連接並運算得出最終結果,True表示這一行(Employe)符合.

不過考慮Exp1 Or Exp2 Or Exp3  這樣的條件,如果第一個Exp1是True的話結果必定是True,這個時候還去計算Exp2,Exp3是完全多余的,如果List集合有幾萬條記錄(當然超過幾千行的列表對用戶來說是沒有多少意義的,一般人不會看那么多行,這個時候應該想想過濾條件設置是否合理)那么針對列表的每個實體的每個屬性(字段)使用反射的方式計算一遍Exp將是一個比較大的開銷,好在And與Or跟算術操作符(+,-,*,/)有所不同,And運算時只要兩個操作數中有一個是False就沒必要計算另外一個操作數(這里的是Exp)而Or在一個操作數是True時就可以忽略另一個操作數。不過當所的Exp都是false時針對上面"Exp1 Or Exp2 Or Exp3"這樣的表達式計算每個Exp是不可避免的

到這里我們可以看到該問題的本質就是表達式求值,而操作符只限And與Or兩個二元操作符,最后結果是True或False.

 

設計實現:


首先我們將用戶設置的過濾表達式轉變成逆波蘭式(后綴表達式),接着傳入每個要判斷的實體,使用后綴表達式求出該實體是否符合過濾條件,
當然我們也可以將后綴表達式構建成Expression樹,接着將該Expression編譯成動態方法(委托),使用該委托對每個實體做出判斷,下面的代碼給出了這兩種實現,
經過測試發現兩種方法速度區別不大。

自己對逆波蘭式求值時需要下面的判定表,如果構建Expression樹則Expression.AndAlso或Expression.OrElse會自己判斷是否對兩個操作數都進行計算(參考下面的代碼)

代碼:

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
namespace FIStudio.WinUI.Core
{
    public class ExpParser
    {
        
        public ExpParser(List<Elem> midfixList)
        {
            this.MidfixList = midfixList;

        }
        public List<Elem> PostfixList { get; private set; }
        public List<Elem> MidfixList { get; private set; }
        private Stack<Elem> CalcStack = new Stack<Elem>();
        private void GuardMidfixListExist()
        {
            if (MidfixList == null || MidfixList.Count <= 0) throw new Exception("中序列表為null或為空!");
        }
        private void EnsurePostfixReady()
        {
            if (PostfixList == null)
            {
                PostfixList = DoParse(MidfixList);
                if (PostfixList == null || PostfixList.Count <= 0) throw new Exception("后序列表為null或為空!");
            }
        }
        /// <summary>
        /// 判斷元素是否符合要求
        /// </summary>
        /// <param name="ent"></param>
        /// <returns></returns>
        public bool IsSatisfy(object ent)
        {
            GuardMidfixListExist();
            EnsurePostfixReady();


            CalcStack.Clear();
            foreach (var item in PostfixList)
            {
                if (item is ExpElem)
                {
                    CalcStack.Push(item);
                    continue;
                }
                #region And 運算
                if (item is AndElem)
                {
                    var op1 = CalcStack.Pop() as ExpElem;
                    var op2 = CalcStack.Pop() as ExpElem;

                    //任意一個是false則直接壓入false
                    if (op1.Result == false || op2.Result == false)
                    {
                        CalcStack.Push(new ExpElem() { Result = false });
                        continue;
                    }

                    if (!op1.Result.HasValue && !op2.Result.HasValue)
                    {
                        op1.Compare(ent);
                        if (op1.Result.Value == false)
                        {
                            CalcStack.Push(new ExpElem() { Result = false });
                            continue;
                        }
                        op2.Compare(ent);
                        CalcStack.Push(new ExpElem() { Result = op2.Result });
                        continue;
                    }
                    if (!op1.Result.HasValue && op2.Result == true)
                    {
                        op1.Compare(ent);
                        CalcStack.Push(new ExpElem() { Result = op1.Result });
                        continue;
                    }
                    if (op1.Result == true && !op2.Result.HasValue)
                    {
                        op2.Compare(ent);
                        CalcStack.Push(new ExpElem() { Result = op2.Result });
                        continue;

                    }
                    if (op1.Result == true && op2.Result == true)
                    {
                        CalcStack.Push(new ExpElem() { Result = true });
                        continue;
                    }

                }
                #endregion
                #region Or 運算
                if (item is OrElem)
                {
                    var op1 = CalcStack.Pop() as ExpElem;
                    var op2 = CalcStack.Pop() as ExpElem;

                    //任意一個是true則直接壓入true
                    if (op1.Result == true || op1.Result == true)
                    {
                        CalcStack.Push(new ExpElem() { Result = true });
                        continue;
                    }

                    if (!op1.Result.HasValue && !op2.Result.HasValue)
                    {
                        op1.Compare(ent);
                        if (!op1.Result == true)
                        {
                            CalcStack.Push(new ExpElem() { Result = true });
                            continue;
                        }
                        op2.Compare(ent);
                        CalcStack.Push(new ExpElem() { Result = op2.Result });
                    }
                    if (!op1.Result.HasValue && op2.Result == false)
                    {
                        op1.Compare(ent);
                        CalcStack.Push(new ExpElem() { Result = op1.Result });
                        continue;
                    }
                    if (op1.Result == false && !op2.Result.HasValue)
                    {
                        op2.Compare(ent);
                        CalcStack.Push(new ExpElem() { Result = op2.Result });

                        continue;
                    }
                    if (op1.Result == false && op2.Result == false)
                    {
                        CalcStack.Push(new ExpElem() { Result = false });
                    }

                }
                #endregion
            }
            return (CalcStack.Pop() as ExpElem).Result.Value;

        }
        /// <summary>
        /// 生成判斷函數
        /// </summary>
        /// <returns></returns>
        public  Expression<Func<T,bool>> GenIsSatisfyFunc<T>()
        {
            GuardMidfixListExist();
            EnsurePostfixReady();
            Stack<object> stack = new Stack<object>();

            ParameterExpression entExp = Expression.Parameter(typeof(T), "ent");
            foreach (var elem in PostfixList)
            {

                if (elem is ExpElem)
                {
                    stack.Push(elem);
                    continue;
                }
                if (elem is AndElem)
                {
                    var elem1 = stack.Pop();
                    var elem2 = stack.Pop();

                    var exp= Expression.AndAlso(GetCallExpression(elem1,entExp),GetCallExpression(elem2,entExp));
                    
                    stack.Push(exp);
                    continue;
                }
                if(elem is OrElem)
                {
                    var elem1 = stack.Pop();
                    var elem2 = stack.Pop();

                    var exp= Expression.OrElse(GetCallExpression(elem1,entExp),GetCallExpression(elem2,entExp));
                    
                    stack.Push(exp);
                    continue;
                }
            }
            LambdaExpression lambda= Expression.Lambda<Func<T,bool>>( stack.Pop() as Expression,entExp);

            return lambda as  Expression<Func<T,bool>>;

        }
        private Expression GetCallExpression(object elem, ParameterExpression entExp)
        {
            if (elem is ExpElem)
            {
               return Expression.Call(Expression.Constant(elem), typeof(ExpElem).GetMethod("Compare"), entExp);
            }
            return elem as Expression;
        }
        /// <summary>
        /// 中序表達式轉后綴表達式
        /// </summary>
        /// <param name="midfix"></param>
        /// <returns></returns>
        private  List<Elem> DoParse(List<Elem> midfix)
        {
            Stack<Elem> stack = new Stack<Elem>();
            var list=new List<Elem>();
            foreach (var elem in midfix)
            {
                if (elem is ExpElem)
                {
                    list.Add(elem);
                    continue;
                }
                if (elem is LBElem)
                {
                    stack.Push(elem);
                    continue;
                }
                if (elem is RBElem)
                {
                    var e = stack.Pop();
                    while (!(e is LBElem))
                    {
                        list.Add(e);
                        e = stack.Pop();
                      

                    }
                    continue;
                }
                if((elem is AndElem) || (elem is OrElem))
                {
                    if (stack.Count > 0)
                    {
                        var e = stack.Peek();
                        while ( !(e is LBElem) && elem.Priority <= e.Priority)
                        {
                            list.Add(stack.Pop());
                            if (stack.Count <= 0) break;
                            e = stack.Peek();
                        }
                    }
                    stack.Push(elem);
                }

            }
            while (stack.Count > 0)
            {
                list.Add(stack.Pop());
            }

            return list;

        }
    }
    #region 節點定義
    public class Elem
    {
        public virtual string Name { get; set; }
        public virtual int Priority { get; set; }
        public Object Data { get; set; }
    }
    /// <summary>
    /// 左括號
    /// 注意stack中只會壓入'(','And','Or'
    /// </summary>
    public class LBElem : Elem
    {
        public override string Name
        {
            get
            {
                return "(";
            }
        }
        public override int Priority
        {
            get
            {
                return 59;
            }
        }
    }
    /// <summary>
    /// 右括號
    /// </summary>
    public class RBElem : Elem
    {
        public override string Name
        {
            get
            {
                return ")";
            }
        }
        public override int Priority
        {
            get
            {
                return 99;
            }
        }

    }

    public class AndElem : Elem
    {
        public override string Name
        {
            get
            {
                return "And";
            }
        }
        public override int Priority
        {
            get
            {
                return 88;
            }
        }
    }

    public class OrElem : Elem
    {
        public override string Name
        {
            get
            {
                return "Or";
            }
        }
        public override int Priority
        {
            get
            {
                return 77;
            }
        }
    }
    public class ExpElem : Elem
    {

        public override int Priority
        {
            get
            {
                return 66;
            }
        }
        public bool Compare(object ent)
        {
            Console.WriteLine("計算了:" + Name);
            bool? ret=null;
            if (AssertType == Core.CompareType.Equal)
            {
                ret= string.Compare(GetV(ent),Value,true)==0;
            }
            if (AssertType == Core.CompareType.NotEqual)
            {
                ret =  string.Compare(GetV(ent), Value, true) != 0;
            }
            if (AssertType == Core.CompareType.Greate)
            {
                ret = string.Compare(GetV(ent), Value, true) > 0;
            }
            if (AssertType == Core.CompareType.GreateOrEqual)
            {
                ret = string.Compare(GetV(ent), Value, true) >= 0;
            }
            if (AssertType == Core.CompareType.Less)
            {
                ret = string.Compare(GetV(ent), Value, true) < 0;
            }
            if (AssertType == Core.CompareType.LessOrEqual)
            {
                ret = string.Compare(GetV(ent), Value, true) <= 0;
            }
            if (AssertType == Core.CompareType.Contains)
            {
                ret = GetV(ent).Contains(Value);
            }
            if (AssertType == Core.CompareType.NoContains)
            {
                ret =! GetV(ent).Contains(Value);
            }
            if (AssertType == Core.CompareType.StartWith)
            {
                ret = GetV(ent).StartsWith(Value);
            }
            if (AssertType == Core.CompareType.EndWith)
            {
                ret = GetV(ent).EndsWith(Value);
            }
            if (!ret.HasValue) throw new Exception("未知的CompareType!");
            Result = ret;
            return ret.Value;

           
        }
        public bool? Result { get; set; }
        public PropertyInfo Property { get; set; }
        public CompareType AssertType { get; set; }
        public string Value { get; set; }

        private string GetV(object ent)
        {
            var tmp= Property.GetValue(ent, null);
            if (tmp == null) tmp = string.Empty;
            return tmp.ToString();
        }

        
    }

    public enum CompareType { Equal, NotEqual, Less, LessOrEqual, Greate, GreateOrEqual, Contains, NoContains, StartWith, EndWith };

#endregion

}

參考:

  逆波蘭式構建方法

  1、從左至右掃描一中綴表達式。
                2、若讀取的是操作數,則判斷該操作數的類型,並將該操作數存入操作數堆棧
                3、若讀取的是運算符
                   (1) 該運算符為左括號"(",則直接存入運算符堆棧。
                   (2) 該運算符為右括號")",則輸出運算符堆棧中的運算符到操作數堆棧,直到遇到左括號為止,此時拋棄該左括號。
                   (3) 該運算符為非括號運算符:
                       (a) 若運算符堆棧棧頂的運算符為左括號,則直接存入運算符堆棧。
                       (b) 若比運算符堆棧棧頂的運算符優先級高,則直接存入運算符堆棧。
                       (c) 若比運算符堆棧棧頂的運算符優先級低或相等,則輸出棧頂運算符到操作數堆棧,
                            直至運算符棧棧頂運算符低於(不包括等於)該運算符優先級,或為左括號,並將當前運算符壓入運算符堆棧。
                4、當表達式讀取完成后運算符堆棧中尚有運算符時,則依序取出運算符到操作數堆棧,直到運算符堆棧為空。

   逆波蘭表達式求值算法:

               1、循環掃描語法單元的項目。
               2、如果掃描的項目是操作數,則將其壓入操作數堆棧,並掃描下一個項目。
               3、如果掃描的項目是一個二元運算符,則對棧的頂上兩個操作數執行該運算。
               4、如果掃描的項目是一個一元運算符,則對棧的最頂上操作數執行該運算。
               5、將運算結果重新壓入堆棧。
               6、重復步驟2-5,堆棧中即為結果值。

  資源

字符串公式解析器——使用“逆波蘭式算法”及C#實現

 

 


免責聲明!

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



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