[Architecture Pattern] Repository實做查詢功能

[Architecture Pattern] Repository實做查詢功能

範例下載

範例程序代碼:點此下載sql

問題情景

在系統的BLL與DAL之間,加入Repository Pattern的設計,可以切割BLL與DAL之間的相依性,而且提供系統抽換DAL的能力。但在軟件開發的過程當中,套用Repository Pattern最容易遇到的問題就是,如何在Repository中實做「查詢」這個功能。像是在下列這個查詢訂單的頁面,系統必需要依照用戶輸入的查詢條件,來從DAL中查詢出全部符合條件內容的Order對象集合,而且將這些Order對象逐一呈現給在系統頁面給用戶瀏覽。數據庫

問題情景01

由於系統頁面上的查詢條件是可填、可不填,這對應到提供數據的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

問題情景02

解決方案

IRepository設計

爲了解決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);
}

IRepository使用

完成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

    解決方案01

    解決方案02

  • 執行範例(userId=A123)

    解決方案03

    解決方案04

  • 執行範例(userId=A123, state=Completed)

    解決方案05

    解決方案06

SqlRepository實做

接着設計封裝本地數據庫的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)

    解決方案07

    解決方案08

  • 執行範例(userId=A123)

    解決方案09

    解決方案10

  • 執行範例(userId=A123, state=Completed)

    解決方案11

    解決方案12

IRepository查詢

完成上列這些步驟以後,也就完成了Repository實做查詢功能的開發工做,用戶就能在系統頁面上填寫查詢條件,來從系統中查詢全部符合條件內容的數據對象集合。

  • 執行範例(All)

    解決方案13

  • 執行範例(userId=A123)

    解決方案14

  • 執行範例(userId=A123, state=Completed)

    解決方案15

後記

Repository實做查詢功能的開發工做套用本篇的解決方案,能在BLL中徹底不須要牽扯DAL的信息,只須要單純傳遞C#類別來作爲查詢條件,這部分提升了BLL的內聚力。而GetAllByCondition的設計,由於單純使用C#類別來傳遞查詢條件,這讓DAL實做不會被綁死在特定數據源上,也大幅提升了BLL的重用性。開發人員設計系統時遇到須要Repository實做查詢功能的開發工做,參考本篇提供的解決方案應該就能知足大部分的開發需求。

相關文章
相關標籤/搜索