Util應用程序框架公共操作類(七):Lambda表達式公共操作類


  前一篇擴展了兩個常用驗證方法,本文將封裝兩個Lambda表達式操作,用來為下一篇的查詢擴展服務。

  Lambda表達式是一種簡潔的匿名函數語法,可以用它將方法作為委托參數傳遞。在Linq中,大量使用Lambda表達式進行查詢,不過這種Lambda表達式被Expression包裝成表達式樹。表達式樹是解釋器的一個實現,它的作用是將一種語法轉換為另一種語法,比如將Lambda表達式解析為Sql語句。

  使用Sql列名進行查詢的主要問題是,列名是一個字符串,沒有智能提示,如果輸入錯誤,也沒有編譯時檢查。使用Lambda表達式查詢可以解決這些問題,這是使用強類型的主要好處,另外當列名與屬性名不一致時,只需修改映射配置,業務代碼不動,從而增強了系統的擴展性。

  Lambda表達式的強類型在帶來諸多好處的同時,也產生了一些問題,比如t => t.Name==”a”這個表達式,如果想把值”a”拿出來進行操作,怎么做到?值”a”對於查詢條件來講,是一個動態傳入的參數,如果對表達式樹完全不了解,這也不是一件輕松的事。另外還有動態查詢的問題,這時候開始懷念弱類型的字符串了,微軟提供了一個動態查詢的輔助類來解決這個問題,待用到的時候我再介紹。    

  本文介紹的一個方法是GetValue,用來將Lambda謂詞表達式中的值取出來,另一個方法是GetCriteriaCount,用來判斷Lambda謂詞表達式中條件的個數。這兩個方法的具體應用將在下一篇介紹。

  在Util項目中添加一個Lambda類,代碼如下。

using System.Linq;
using System.Linq.Expressions;

namespace Util {
    /// <summary>
    /// Lambda表達式操作
    /// </summary>
    public class Lambda {

        #region GetValue(獲取值)

        /// <summary>
        /// 獲取值,范例:t => t.Name == "A",返回 A
        /// </summary>
        /// <param name="expression">表達式,范例:t => t.Name == "A"</param>
        public static object GetValue( LambdaExpression expression ) {
            if ( expression == null )
                return null;
            BinaryExpression binaryExpression = GetBinaryExpression( expression );
            if ( binaryExpression != null )
                return GetBinaryValue( binaryExpression );
            var callExpression = expression.Body as MethodCallExpression;
            if ( callExpression != null )
                return GetMethodValue( callExpression );
            return null;
        }

        /// <summary>
        /// 獲取二元表達式
        /// </summary>
        private static BinaryExpression GetBinaryExpression( LambdaExpression expression ) {
            var binaryExpression = expression.Body as BinaryExpression;
            if ( binaryExpression != null )
                return binaryExpression;
            var unaryExpression = expression.Body as UnaryExpression;
            if ( unaryExpression == null )
                return null;
            return unaryExpression.Operand as BinaryExpression;
        }

        /// <summary>
        /// 獲取二元表達式的值
        /// </summary>
        private static object GetBinaryValue( BinaryExpression binaryExpression ) {
            var unaryExpression = binaryExpression.Right as UnaryExpression;
            if ( unaryExpression != null )
                return GetConstantValue( unaryExpression.Operand );
            return GetConstantValue( binaryExpression.Right );
        }

        /// <summary>
        /// 獲取常量值
        /// </summary>
        private static object GetConstantValue( Expression expression ) {
            var constantExpression = expression as ConstantExpression;
            if ( constantExpression == null )
                return null;
            return constantExpression.Value;
        }

        /// <summary>
        /// 獲取方法調用表達式的值
        /// </summary>
        private static object GetMethodValue( MethodCallExpression callExpression ) {
            var argumentExpression = callExpression.Arguments.FirstOrDefault();
            return GetConstantValue( argumentExpression );
        }

        #endregion

        #region GetCriteriaCount(獲取謂詞條件的個數)

        /// <summary>
        /// 獲取謂詞條件的個數
        /// </summary>
        /// <param name="expression">謂詞表達式,范例:t => t.Name == "A"</param>
        public static int GetCriteriaCount( LambdaExpression expression ) {
            if ( expression == null )
                return 0;
            var result = expression.ToString().Replace( "AndAlso", "|" ).Replace( "OrElse", "|" );
            return result.Split( '|' ).Count();
        }

        #endregion
    }
}

    為了進行測試,需要創建一個測試樣例類Test,代碼如下。

namespace Util.Tests.Samples {
    /// <summary>
    /// 測試
    /// </summary>
    public class Test {
        public string Name { get; set; }
        public int Age { get; set; }
        public int? NullableInt { get; set; }
        public decimal? NullableDecimal { get; set; }
        public TestA A { get; set; }
        public class TestA {
            public int Integer { get; set; }
            public string Address { get; set; }
            public TestB B { get; set; }
            public class TestB {
                public string Name { get; set; }
            }
        }
    }
}

    單元測試代碼如下。

using System;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Util.Tests.Samples;

namespace Util.Tests {
    /// <summary>
    /// Lambda表達式操作測試
    /// </summary>
    [TestClass]
    public class LambdaTest {

        #region GetValue(獲取成員值)

        /// <summary>
        /// 獲取成員值,委托返回類型為Object
        /// </summary>
        [TestMethod]
        public void TestGetValue_Object() {
            Expression<Func<Test, object>> expression = test => test.Name == "A";
            Assert.AreEqual( "A", Lambda.GetValue( expression ) );
        }

        /// <summary>
        /// 獲取成員值,委托返回類型為bool
        /// </summary>
        [TestMethod]
        public void TestGetValue_Boolean() {
            //空值返回null
            Assert.AreEqual( null, Lambda.GetValue( null ) );

            //一級返回值
            Expression<Func<Test, bool>> expression = test => test.Name == "A";
            Assert.AreEqual( "A", Lambda.GetValue( expression ) );

            //二級返回值
            Expression<Func<Test, bool>> expression2 = test => test.A.Integer == 1;
            Assert.AreEqual( 1, Lambda.GetValue( expression2 ) );

            //三級返回值
            Expression<Func<Test, bool>> expression3 = test => test.A.B.Name == "B";
            Assert.AreEqual( "B", Lambda.GetValue( expression3 ) );
        }

        /// <summary>
        /// 獲取可空類型的值
        /// </summary>
        [TestMethod]
        public void TestGetValue_Nullable() {
            //可空整型
            Expression<Func<Test, bool>> expression = test => test.NullableInt == 1;
            Assert.AreEqual( 1, Lambda.GetValue( expression ) );

            //可空decimal
            expression = test => test.NullableDecimal == 1.5M;
            Assert.AreEqual( 1.5M, Lambda.GetValue( expression ) );
        }

        /// <summary>
        /// 獲取成員值,運算符為方法
        /// </summary>
        [TestMethod]
        public void TestGetValue_Method() {
            //1級返回值
            Expression<Func<Test, bool>> expression = t => t.Name.Contains( "A" );
            Assert.AreEqual( "A", Lambda.GetValue( expression ) );

            //二級返回值
            expression = t => t.A.Address.Contains( "B" );
            Assert.AreEqual( "B", Lambda.GetValue( expression ) );

            //三級返回值
            expression = t => t.A.B.Name.StartsWith( "C" );
            Assert.AreEqual( "C", Lambda.GetValue( expression ) );
        }

        #endregion

        #region GetCriteriaCount(獲取謂詞條件的個數)

        /// <summary>
        /// 獲取謂詞條件的個數
        /// </summary>
        [TestMethod]
        public void TestGetCriteriaCount() {
            //0個條件
            Assert.AreEqual( 0, Lambda.GetCriteriaCount( null ) );

            //1個條件
            Expression<Func<Test, bool>> expression = test => test.Name == "A";
            Assert.AreEqual( 1, Lambda.GetCriteriaCount( expression ) );

            //2個條件,與連接符
            expression = test => test.Name == "A" && test.Name == "B";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );

            //2個條件,或連接符
            expression = test => test.Name == "A" || test.Name == "B";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );

            //3個條件
            expression = test => test.Name == "A" && test.Name == "B" || test.Name == "C";
            Assert.AreEqual( 3, Lambda.GetCriteriaCount( expression ) );

            //3個條件,包括導航屬性
            expression = test => test.A.Address == "A" && test.Name == "B" || test.Name == "C";
            Assert.AreEqual( 3, Lambda.GetCriteriaCount( expression ) );
        }

        /// <summary>
        /// 獲取謂詞條件的個數,運算符為方法
        /// </summary>
        [TestMethod]
        public void TestGetCriteriaCount_Method() {
            //1個條件
            Expression<Func<Test, bool>> expression = t => t.Name.Contains( "A" );
            Assert.AreEqual( 1, Lambda.GetCriteriaCount( expression ) );

            //2個條件,與連接
            expression = t => t.Name.Contains( "A" ) && t.Name == "A";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );

            //2個條件,或連接,包含導航屬性
            expression = t => t.Name.Contains( "A" ) || t.A.Address == "A";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );
        }

        #endregion
    }
}

   需要注意的是,GetValue方法不僅要能獲取t=>t.Name==”a”這樣的二元表達式,還要能獲取方法調用表達式中的值,比如t=>t.Name.Contains(“a”)。

  下面再增加一個擴展方法,在Util項目中添加名為Extensions.Expression的文件,代碼如下。

using System;
using System.Linq.Expressions;

namespace Util {
    /// <summary>
    /// 表達式擴展
    /// </summary>
    public static partial class Extensions {

        #region Value(獲取lambda表達式的值)

        /// <summary>
        /// 獲取lambda表達式的值
        /// </summary>
        /// <typeparam name="T">對象類型</typeparam>
        public static object Value<T>( this Expression<Func<T, bool>> expression ) {
            return Lambda.GetValue( expression );
        }

        #endregion
    }
}

 

   Lambda表達式不僅在查詢上大展身手,而且在表現層,比如Mvc上也有大量的應用。本文只介紹下一篇基礎查詢擴展需要用到的兩個方法,其它方法我會在需要用到的時候補充進來。

 

  .Net應用程序框架交流QQ群: 386092459,歡迎有興趣的朋友加入討論。

  謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/xiadao521/

  如果需要下載代碼,請參考Util應用程序框架公共操作類(六):驗證擴展


免責聲明!

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



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