使用EF構建企業級應用(三)


本系列目錄:

使用EF構建企業級應用(一):主要講數據庫訪問基類IRepository及Repository 的實現

使用EF構建企業級應用(二):主要講動態排序擴展的實現

使用EF構建企業級應用(三):主要講靈活的構建查詢條件表達式Expression<Func<TEntity,bool>>.

使用EF構建企業級應用(四):主要講下在MVC環境中前端開發中如何郵箱的使用,及一個實例源碼包

在前兩篇文章中,我們已經實現了基於EF的數據庫基本操作基類的構建,以及簡單的介紹了如何方便的動態構建排序表達式,在第二篇文章結尾,我們遺漏下來了一個問題:如何方便的構建查詢參數(即類似於這樣的Expression<TEntity, bool> expression查詢表達式)

在往常的經驗中,我們知道在和數據庫交互的過程中,查詢可能是最復雜的,做過數據持久化封裝的同學們可能對這個認識尤為突出,其他原因我們就不細說了, 如何豐富的,易用的構建查詢條件這個就有點讓人迷惑.

我們來分析一個常見的查詢條件(a>4 and b<3)Or(d>5 and c in(3,4,5)),

  1. 設定:var exp1=a>4 and b<3;  var exp2=d>5 and c in(3,4,5),則上述條件可以表示為 var exp=exp1 or exp2;
  2. 進一步分解exp1:var exp1_1=a>4; var exp1_2=b<3; 則exp1=exp1_1 and var exp_2
  3. 進一步分解exp2:var exp2_1=d>5; var exp2_2=c in (3,4,5); 則exp2=exp2_1 and exp2_2

有了上面的分解,我們發現其實構建這個查詢條件非常符合我們常見的遞歸算法.下面我們嘗試一下用遞歸的思想來實現這個.我們先大致的畫個UML草圖image

為了不和系統下面的Expression 命名混淆,我們這里采用EFExpression作為條件表達式基類名稱,

  1. EFExpression<T> 為一個抽象基類,里面主要有一個返回類型為 Expression<Func<T,bool>> GetExpression()的一個抽象方法,其具體實現,我們放在了每一個具體的Expression中去定義.
  2. EmptyEFExpression<T> 表示一個空的查詢表達式
  3. BinaryEFExpression<T> 表示一個基於二元條件的查詢表達式,如大於,小於,等於 ……
  4. LikeEFExpression<T> 表示一個用於創建”類似於”條件的查詢表達式
  5. LogicEFExpression<T> 表示一個邏輯運算的查詢表達式,如and or
  6. ….可能還會有其他子類

1.首先我們試着來編寫一下抽象基類EFExpression<T>的實現

 
    
    /// <summary>
    /// 查詢表達式基類
    /// </summary>
    public abstract class EFExpression<T> where T : class
    {

        /// <summary>
        /// 獲取查詢表達式
        /// </summary>
        /// <returns></returns>
        public virtual Expression<Func<T, bool>> GetExpression()
        {
            if (Expression != null)
            {
                var candidateExp = Expression.Parameter(typeof(T), "x");
                var exp = Expression.Lambda<Func<T, bool>>(Expression, candidateExp);
                return exp;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 查詢條件對應表達式
        /// </summary>
        protected Expression _Expression { get; set; }

        /// <summary>
        /// 參數表達式
        /// </summary>
        public ParameterExpression[] Parameters { get; set; }

        /// <summary>
        /// 獲取對應的表達式
        /// </summary>
        internal abstract Expression Expression { get; }

        /// <summary>
        /// 獲取表達式主題
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="P"></typeparam>
        /// <param name="old"></param>
        /// <param name="property"></param>
        /// <returns></returns>
        protected static Expression GetMemberExpression<T, P>(EFExpression<T> old, Expression<Func<T, P>> property) 
            where T : class
        {
            if (old.Parameters == null || old.Parameters.Length == 0)
            {
                old.Parameters = property.Parameters.ToArray();
                return property.Body;
            }

            ParameterExpressionVisitor visitor = new ParameterExpressionVisitor(old.Parameters[0]);

            Expression memberExpr = visitor.ChangeParameter(property.Body);

            return memberExpr;
        }

        /// <summary>
        /// 獲取一個空的表達式
        /// </summary>
        /// <returns></returns>
        public Expression<Func<T, bool>> GetEmptyExpression()
        {
            return (Expression<Func<T, bool>>)(f => true);
        }

        /// <summary>
        /// 兩個條件進行And運算
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static EFExpression<T> operator &(EFExpression<T> left, EFExpression<T> right)
        {
            return new LogicEFExpression<T>(left, ELogicType.And, right);
        }
        /// <summary>
        /// 兩個條件進行Or運行
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static EFExpression<T> operator |(EFExpression<T> left, EFExpression<T> right)
        {
            return new LogicEFExpression<T>(left, ELogicType.Or, right);
        }
    }

2. 接着我們來看一下如何實現這個BinaryEFExpression<T>

 
/// <summary>
    /// 二元運算查詢條件
    /// </summary>
    /// <typeparam name="T">查詢條件實體類型</typeparam>
    /// <typeparam name="TVal">需要比較的屬性類型</typeparam>
    internal class BinanryEFExpression<T, TVal> : EFExpression<T>
        where T : class
        where TVal : IComparable
    {
        /// <summary>
        /// 定義條件的實體屬性
        /// </summary>
        private Expression<Func<T, TVal>> property;

        /// <summary>
        /// 比較的值
        /// </summary>
        private TVal val;

        /// <summary>
        /// 二元運算符
        /// </summary>
        private EBinaryType binaryType;

        /// <summary>
        /// 實例化新的二元查詢表達式
        /// </summary>
        /// <param name="property">定義條件的實體屬性</param>
        /// <param name="binaryType">二元運算符</param>
        /// <param name="val">比較的值</param>
        public BinanryEFExpression(Expression<Func<T, TVal>> property, EBinaryType binaryType, TVal val)
        {
            if (property == null)
                throw new ArgumentNullException("property");
            //if (val == null && binaryType != EBinaryType.Like)
            //    throw new ArgumentNullException("val");
            this.property = property;
            this.val = val;
            this.binaryType = binaryType;
        }

        internal override Expression Expression
        {
            get
            {
                if (_Expression == null)
                {
                    var propertyBody = GetMemberExpression(this, property);

                    Type type = typeof(TVal);
                    Expression compareVal = Expression.Constant(val);
                    //如果是Nullable類型,則把value轉化成Nullable類型
                    if (type.IsNullableType())
                    {
                        compareVal = Expression.Convert(compareVal, type);
                    }
                    Expression tempExp = null;
                    switch (binaryType)
                    {
                        case EBinaryType.Equal:
                            tempExp = Expression.Equal(propertyBody, compareVal);
                            break;
                        case EBinaryType.GreaterThan:
                            tempExp = Expression.GreaterThan(propertyBody, compareVal);
                            break;
                        case EBinaryType.GreaterThanOrEqual:
                            tempExp = Expression.GreaterThanOrEqual(propertyBody, compareVal);
                            break;
                        case EBinaryType.LessThan:
                            tempExp = Expression.LessThan(propertyBody, compareVal);
                            break;
                        case EBinaryType.LessThanOrEqual:
                            tempExp = Expression.LessThanOrEqual(propertyBody, compareVal);
                            break;
                        case EBinaryType.NotEqual:
                            tempExp = Expression.NotEqual(propertyBody, compareVal);
                            break;
                        default:
                            break;
                    }
                    _Expression = tempExp;
                }
                return _Expression;
            }
        }
    }

3.我們在來看一下關於表達式邏輯運算的LogicEFExpression<T> 類又是如何實現的

 /// <summary>
    /// 帶有邏輯運算的查詢表達式
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class LogicEFExpression<T> : EFExpression<T> where T : class
    {
        private EFExpression<T> left;
        private EFExpression<T> right;
        private ELogicType logicType;

        /// <summary>
        /// 實例化新的邏輯運算查詢表達式
        /// </summary>
        /// <param name="left"></param>
        /// <param name="logicType">邏輯運算類型</param>
        /// <param name="right"></param>
        public LogicEFExpression(EFExpression<T> left, ELogicType logicType, EFExpression<T> right)
        {
            if (left == null || right == null)
                throw new ArgumentNullException("left 和 right 不能同時為空");
            this.left = left;
            this.right = right;
            this.logicType = logicType;
        }

        public override Expression<Func<T, bool>> GetExpression()
        {
            if (left == null)
                return right.GetExpression();
            else if (right == null)
                return left.GetExpression();
            else
            {
                //判斷進行運算的兩個條件是否為空
                if (left is EmptyEFExpression<T> && right is EmptyEFExpression<T>)
                    return left.GetExpression();
                else if (left is EmptyEFExpression<T>)
                    return right.GetExpression();
                else if (right is EmptyEFExpression<T>)
                    return left.GetExpression();

                var leftExp = left.GetExpression();
                var rightExp = right.GetExpression();
                Expression<Func<T, bool>> exp = null;

                if (leftExp == null && rightExp == null)
                    return new EmptyEFExpression<T>().GetExpression();
                else
                {
                    if (leftExp == null)
                        return rightExp;
                    else if (rightExp == null)
                        return leftExp;
                    else
                    {
                        switch (logicType)
                        {
                            case ELogicType.And:
                                exp = leftExp.And(rightExp);
                                break;
                            case ELogicType.Or:
                                exp = leftExp.Or(rightExp);
                                break;
                            default:
                                break;
                        }
                    }
                }
                return exp;
            }
        }

        internal override Expression Expression
        {
            get
            {
                return null;
            }
        }
    }
 
        


 

4.為了使用方便,我們在基類中再添加個靜態方法

 /// <summary>
        /// 構建一個二元查詢表達式
        /// </summary>
        /// <typeparam name="TVal">比較值的類型</typeparam>
        /// <param name="property">定義條件的實體屬性</param>
        /// <param name="binaryType">運算符類型</param>
        /// <param name="val">比較的值</param>
        /// <returns></returns>
        public static EFExpression<T> CreateBinaryExpression<TVal>(Expression<Func<T, TVal>> property,
            EBinaryType binaryType, TVal val) where TVal : IComparable
        {
            return new BinanryEFExpression<T, TVal>(property, binaryType, val);
        }

5.有了上面的實現,我們來測試下我們的想法.為了讓我們更容易理解,我們假設在一個訂單管理環境中,有如下一個需求:請查詢滿足以下任意一個條件中的訂單記錄

    1. 訂單狀態還未送貨且金額>50W的
    2. 訂單狀態為送貨中,且客戶地址在深圳片區的

為了便於理解,我們在這里先定義兩個實體類

    /// <summary>
    /// 訂單主表
    /// </summary>
    public class OrderMain
    {
        public Guid Id { get; set; }
        public string Code { get; set; }
        public DateTime BillDate { get; set; }
        public Guid CustomerId { get; set; }
        public virtual Customer Customer { get; set; }
        public decimal TotalAmount { get; set; }
        public int Status { get; set; }
    }
    /// <summary>
    /// 客戶信息
    /// </summary>
    public class Customer
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Area { get; set; }
    }

那么我們這里使用的查詢條件可能就會寫成如下形式:

var exp=EFExpression<OrderMain>.CreateBinaryExpression(o=>o.Status,EBinaryType.Equal,0);   //還沒有送貨

exp&=EFExpression<OrderMain>.CreateBinaryExpression(o=>o.TotalAmount ,EBinaryType.LessThan,500000);  //小於50W

var exp2=EFExpression<OrderMain>.CreateBinaryExpression(o=>o.Status,EBinaryType.Equal,1);   //送貨中

exp2&=EFExpression<OrderMain>.CreateBinaryExpression(o=>o.Customer.Area,EBinaryType.Equal,”0755”);  //地址在深圳片區

exp|=exp2;

var queryExpression=exp.GetExpression();

測試通過.大笑,至於其他的子類在此就不一一列舉了,當然我們可以通過擴展一些寫法使之更容易使用,比如

 /// <summary>
        /// 在當前條件基礎上,構建一個like條件,並且和當前條件產生And運算
        /// </summary>
        /// <param name="property"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public static EFExpression<T> AndLike<T>(this EFExpression<T> old, Expression<Func<T, string>> property, string val) where T : class
        {
            var temp = new LikeEFExpression<T>(property, val);
            if (old == null)
                return temp;
            else
                return new LogicEFExpression<T>(old, ELogicType.And, temp);
        }

        /// <summary>
        /// 在當前條件基礎上,構建一個等於條件,並且和當前條件產生And運算
        /// </summary>
        /// <typeparam name="TVal"></typeparam>
        /// <param name="old"></param>
        /// <param name="property"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public static EFExpression<T> AndEqual<T, TVal>(this EFExpression<T> old, Expression<Func<T, TVal>> property, TVal val)
            where TVal : IComparable
            where T : class
        {
            var temp = new BinanryEFExpression<T, TVal>(property, EBinaryType.Equal, val);
            if (old == null)
                return temp;
            else
                return new LogicEFExpression<T>(old, ELogicType.And, temp);
        }

        /// <summary>
        /// 在當前條件基礎上,構建一個大於條件,並且和當前條件產生And運算
        /// </summary>
        /// <typeparam name="TVal"></typeparam>
        /// <param name="old"></param>
        /// <param name="property"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public static EFExpression<T> AndGreaterThan<T, TVal>(this EFExpression<T> old, Expression<Func<T, TVal>> property, TVal val)
            where TVal : IComparable
            where T : class
        {
            var temp = new BinanryEFExpression<T, TVal>(property, EBinaryType.GreaterThan, val);
            if (old == null)
                return temp;
            else
                return new LogicEFExpression<T>(old, ELogicType.And, temp);
        }

那么我們在使用的時候就可以方便的寫成下面這種形式

var exp=EFExpression<OrderMain>.Create().AndEqual(o=>o.Status,0).AndGreaterThan(o=>o.TotalAmount ,500000)…..

 

 

在此不再贅述,有興趣的朋友們可以自己去寫出類似的東東,或問我要源碼包...


免責聲明!

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



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