最近發如今項目中或許會遇到讓用戶本身構建查詢表達式的狀況。好比須要經過一種可配置的界面,來讓用戶輸入一組具備邏輯關係的查詢表達式,而後根據這個查詢表達式來過濾並返回所須要的數據。這種用戶案例其實很是常見。由此受到啓發,或許咱們能夠本身定義一種通用的面向查詢的領域特定語言(DSL),來實現查詢的序列化和動態構建。git
由此我發佈了一個稱爲Unified Queries(如下簡稱UQ)的開源項目,UQ定義了一種DSL,用以描述一種查詢的特定結構。它同時還提供了將查詢規約(Query Specification)轉換爲SQL WHERE子句以及Lambda表達式的功能。UQ提供了很是靈活的框架設計,可以很是方便地經過實現IQuerySpecificationCompiler接口,或者繼承QuerySpecificationCompiler<T>抽象類來自定義查詢規約的轉換功能。github
下面的XSD架構(XSD Schema)定義了UQ的DSL語義,須要注意的是,它包含了一組遞歸的層次結構:數據庫
假定在QuerySpecificationSample.xml文件中定義了以下的查詢規約,在執行該查詢規約時,系統將返回全部名字以「Peter」開頭,而且姓氏中不含有「r」字符,以及年收入在30000以上的客戶。架構
<?xml version="1.0" encoding="utf-8"?> <QuerySpecification> <LogicalOperation Operator="And"> <Expression Name="FirstName" Type="String" Operator="StartsWith" Value="Peter"/> <UnaryLogicalOperation Operator="Not"> <LogicalOperation Operator="Or"> <Expression Name="LastName" Type="String" Operator="Contains" Value="r"/> <Expression Name="YearlyIncome" Type="Decimal" Operator="LessThanOrEqualTo" Value="30000"/> </LogicalOperation> </UnaryLogicalOperation> </LogicalOperation> </QuerySpecification>
如下C#代碼將根據該xml文件產生SQL的WHERE子句:框架
static void Main(string[] args) { var querySpecification = QuerySpecification.LoadFromFile("QuerySpecificationSample.xml"); var compiler = new SqlWhereClauseCompiler(); Console.WriteLine(compiler.Compile(querySpecification)); }
所產生的SQL WHERE子句以下:函數
((FirstName LIKE 'Peter%') AND (NOT ((LastName LIKE '%r%') OR (YearlyIncome <= 30000))))
然而在不少狀況下,ADO.NET的開發人員更喜歡經過使用DbParameter來指定查詢中所包含的參數值,而不是簡單地將參數拼接在SQL語句中。UQ通樣可以產生帶有參數列表的SQL WHERE子句。要達到這樣的效果,僅需在初始化SqlWhereClauseCompiler時,將構造函數參數設置爲true便可:ui
var compiler = new SqlWhereClauseCompiler(true);
因而產生的SQL WHERE子句就是:設計
((FirstName LIKE @fvP8gN) AND (NOT ((LastName LIKE @ESzoyd) OR (YearlyIncome <= @fG5Z7e))))
參數值則能夠經過SqlWhereClauseCompiler的ParameterValues屬性得到。xml
事實上SqlWhereClauseCompiler所產生的SQL WHERE子句是知足Microsoft SQL Server須要的,若是您但願可以產生符合Oracle或MySQL語法的WHERE子句,能夠本身擴展SqlWhereClauseCompiler類來實現。對象
接下來,下面的C#代碼能夠將上面的xml文件中所定義的查詢規約編譯成Lambda表達式:
static void Main(string[] args) { var querySpecification = QuerySpecification.LoadFromFile("QuerySpecificationSample.xml"); var compiler = new LambdaExpressionCompiler<Customer>(); Console.WriteLine(compiler.Compile(querySpecification)); }
產生的Lambda表達式以下:
p => (p.FirstName.StartsWith("Peter") AndAlso Not((p.LastName.Contains("r") OrElse (p.YearlyIncome <= 30000))))
下面的C#例子詳細描述瞭如何在一組客戶對象上應用查詢規約,並將知足條件的客戶數據返回:
private static Customer[] GetAllCustomers() { return new[] { new Customer { FirstName = "Sunny", LastName = "Chen", YearlyIncome = 10000 }, new Customer { FirstName = "PeterJam", LastName = "Yo", YearlyIncome = 10000 }, new Customer { FirstName = "PeterR", LastName = "Ko", YearlyIncome = 50000 }, new Customer { FirstName = "FPeter", LastName = "Law", YearlyIncome = 70000 }, new Customer { FirstName = "Jim", LastName = "Peter", YearlyIncome = 30000 } }; } static void Main(string[] args) { var querySpecification = QuerySpecification.LoadFromFile("QuerySpecificationSample.xml"); var compiler = new LambdaExpressionCompiler<Customer>(); var customers = GetAllCustomers(); foreach (var customer in customers.Where(compiler.Compile(querySpecification).Compile())) { Console.WriteLine( "FirstName: {0}, LastName: {1}, YearlyIncome: {2}", customer.FirstName, customer.LastName, customer.YearlyIncome); } }
如今咱們已經有了一種查詢結構的DSL定義,這就使得一個查詢規約能夠保存在內存的對象中,也能夠被持久化到外部的存儲系統,好比xml文件中,或者數據庫中。接下來咱們能夠設計一種通用的界面,經過這個界面來設計一個查詢規約,因而,就能夠經過Compiler將所設計的查詢規約轉換爲另外一種可被已有系統接受的形式。更進一步,咱們還能夠設計一系列的Builder,將SQL WHERE子句或者Lambda表達式轉換爲UQ中的查詢規約。
但願這個小項目可以給你們帶來啓發和幫助。