範例程序代碼:點此下載sql
在系統的BLL與DAL之間,加入Repository Pattern的設計,可以切割BLL與DAL之間的相依性,而且提供系統抽換DAL的能力。但在軟件開發的過程當中,套用Repository Pattern最容易遇到的問題就是,如何在Repository中實做「查詢」這個功能。像是在下列這個查詢訂單的頁面,系統必需要依照用戶輸入的查詢條件,來從DAL中查詢出全部符合條件內容的Order對象集合,而且將這些Order對象逐一呈現給在系統頁面給用戶瀏覽。數據庫
由於系統頁面上的查詢條件是可填、可不填,這對應到提供數據的Repository上,就變成Repository必須依照各類查詢條件填或不填的各類組合,來提供對應的Method。但這樣的查詢功能設計,在查詢條件少的情景可以正常的開發設計;但在查詢條件較多的情景,就會發現爲每種查詢條件組合創建對應的Method,近乎是一項不可能的任務。(EX:每一個條件內容可填可不填,3個查詢條件就須要2^3=8個Method、7個查詢條件就須要2^7=128個Method。)網絡
public interface IOrderRepository { // Methods IEnumerable<Order> GetAllByCondition(string userId, OrderState state, DateTime startDate, DateTime endDate); IEnumerable<Order> GetAllByCondition(OrderState state, DateTime startDate, DateTime endDate); IEnumerable<Order> GetAllByCondition(string userId, DateTime startDate, DateTime endDate); IEnumerable<Order> GetAllByCondition(DateTime startDate, DateTime endDate); IEnumerable<Order> GetAllByCondition(string userId, OrderState state); IEnumerable<Order> GetAllByCondition(OrderState state); IEnumerable<Order> GetAllByCondition(string userId); IEnumerable<Order> GetAllByCondition(); }
這時最直覺的作法,會在Repository上加入GetAllBySql這個Method,讓系統依照用戶輸入的查詢條件來組合SQL指令,再交由實做Repository的DAL去數據庫作查詢。this
public interface IOrderRepository { // Methods IEnumerable<Order> GetBySql(string sqlCommand, params object[] parameters); }
Repository加入GetAllBySql的這個設計,的確能夠知足用戶需求、提供正確信息給用戶。但仔細思考Repository加入GetAllBySql的這個設計,是讓DAL的職責污染到了BLL,BLL必需要知道DAL所使用的數據表名稱、數據庫字段才能組合出SQL指令,也就是在程序代碼中隱性的讓BLL相依於DAL,這大幅下降了BLL的內聚力。而通常來講只有關係數據庫可以剖析SQL指令來提供數據,也就是DAL實做被綁死在關係數據庫上,這也就大大下降了BLL的重用性。設計
接着,如下列這個開發情景:「系統的數據源,須要依照網絡聯機狀態來決定使用本地數據庫仍是使用外部API」,來思考Repository加入GetAllBySql的這個設計。當外部API不支持SQL指令查詢,系統就沒法創建外部API的GetAllBySql實做,這也就限制了BLL抽換DAL成爲外部API的能力。(感謝91提供範例~^^)code
爲了解決Repository實做查詢功能的問題,回過頭思考通常函式庫、Web服務提供查詢功能的方式。會發現不少查詢功能的設計,會在查詢功能中提供全部的查詢條件,在這些條件內容中填null表明忽略這個條件、填值表明加入這個條件。對象
遵循這個設計原則,開發人員能夠爲Repository上加入GetAllByCondition這個Method,接着把每一個查詢條件都設計爲這個Method的函式參數;最後替不可爲null的值類型參數(enum、DateTime...)加上「?」關鍵詞,將這些值類型改成可輸入null的Nullable類別。blog
public interface IOrderRepository { // Methods IEnumerable<Order> GetAllByCondition(string userId, OrderState? state, DateTime? startDate, DateTime? endDate); }
完成GetAllByCondition的設計以後,系統就能夠將用戶在窗體中所輸入的查詢條件,對應到GetAllByCondition的每一個函式參數。(窗體中條件內容有填的對應爲函式參數內容、窗體中條件內容沒填的對應爲函式參數null。)開發
// UserId string userId = null; if(string.IsNullOrEmpty(this.UserIdTextBox.Text) == false) { userId = this.UserIdTextBox.Text.Trim(); } // State OrderState? state = null; if (this.StateComboBox.SelectedValue != null) { if (this.StateComboBox.SelectedValue.ToString() != "All") { state = Enum.Parse(typeof(OrderState), this.StateComboBox.SelectedValue.ToString()) as OrderState?; } } // StartDate DateTime? startDate = null; if(string.IsNullOrEmpty(this.StartDateTextBox.Text) == false) { startDate = DateTime.Parse(this.StartDateTextBox.Text) as DateTime?; } // EndDate DateTime? endDate = null; if (string.IsNullOrEmpty(this.EndDateTextBox.Text) == false) { endDate = DateTime.Parse(this.EndDateTextBox.Text) as DateTime?; } // Query var orderCollection = _orderRepository.GetAllByCondition(userId, state, startDate, endDate); // Display this.OrderGridView.DataSource = orderCollection;
執行範例(All)get
執行範例(userId=A123)
執行範例(userId=A123, state=Completed)
接着設計封裝本地數據庫的Repository實做,GetAllByCondition函式就能夠依照這些函式參數是否爲null、不爲null的參數內容,來組合查詢條件的SQL指令、提交給本地數據庫而且回傳查詢結果。
依照條件內容是否爲null,來組合SQL指令的Where條件。
// CommandText command.CommandText = @"SELECT USER_ID, STATE, DATE FROM Orders"; // ConditionText var conditionList = new List<string>(); if (string.IsNullOrEmpty(userId) == false) conditionList.Add("USER_ID = @USER_ID"); if (state.HasValue == true) conditionList.Add("STATE = @STATE"); if (startDate.HasValue == true && endDate.HasValue == true) conditionList.Add("Date >= @START_DATE"); if (startDate.HasValue == true && endDate.HasValue == true) conditionList.Add("Date <= @END_DATE"); var conditionString = string.Join(" AND ", conditionList); if (string.IsNullOrEmpty(conditionString) == false) command.CommandText += " WHERE " + conditionString;
依照條件內容是否爲null,來加入Command.Parameters。
// CommandParameters if (string.IsNullOrEmpty(userId) == false) command.Parameters.Add(new SqlParameter("@USER_ID", userId)); if (state.HasValue == true) command.Parameters.Add(new SqlParameter("@STATE", state.ToString())); if (startDate.HasValue == true && endDate.HasValue == true) command.Parameters.Add(new SqlParameter("@START_DATE", startDate.Value)); if (startDate.HasValue == true && endDate.HasValue == true) command.Parameters.Add(new SqlParameter("@END_DATE", endDate.Value));
執行範例(All)
執行範例(userId=A123)
執行範例(userId=A123, state=Completed)
完成上列這些步驟以後,也就完成了Repository實做查詢功能的開發工做,用戶就能在系統頁面上填寫查詢條件,來從系統中查詢全部符合條件內容的數據對象集合。
執行範例(All)
執行範例(userId=A123)
執行範例(userId=A123, state=Completed)
Repository實做查詢功能的開發工做套用本篇的解決方案,能在BLL中徹底不須要牽扯DAL的信息,只須要單純傳遞C#類別來作爲查詢條件,這部分提升了BLL的內聚力。而GetAllByCondition的設計,由於單純使用C#類別來傳遞查詢條件,這讓DAL實做不會被綁死在特定數據源上,也大幅提升了BLL的重用性。開發人員設計系統時遇到須要Repository實做查詢功能的開發工做,參考本篇提供的解決方案應該就能知足大部分的開發需求。