• [Architecture Pattern] Repository实作查询功能


    [Architecture Pattern] Repository实作查询功能

    范例下载

    范例程序代码:点此下载

    问题情景

    在系统的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去数据库做查询。

    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提供范例~^^)

    问题情景02

    解决方案

    IRepository设计

    为了解决Repository实作查询功能的问题,回过头思考一般函式库、Web服务提供查询功能的方式。会发现很多查询功能的设计,会在查询功能中提供所有的查询条件,在这些条件内容中填null代表忽略这个条件、填值代表加入这个条件。

    遵循这个设计原则,开发人员可以为Repository上加入GetAllByCondition这个Method,接着把每个查询条件都设计为这个Method的函式参数;最后替不可为null的值类型参数(enum、DateTime...)加上「?」关键词,将这些值类型改为可输入null的Nullable类别。

    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)

      解决方案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实作查询功能的开发工作,参考本篇提供的解决方案应该就能满足大部分的开发需求。

  • 相关阅读:
    iOS应用程序间共享数据(转)
    解决右滑返回手势和UIScrollView中的手势冲突(转)
    (转)iOS被开发者遗忘在角落的NSException-其实它很强大
    iOS 身份证最后一位是X,输入17位后自动补全X(转)
    springboot单机秒杀之queue队列
    springboot单机秒杀-aop+锁
    springbot单机秒杀,锁与事务之间的大坑
    spring-cloud学习之4.微服务请求打通
    spring-cloud学习之3.使用feign实现负载均衡
    spring-cloud学习之2.搭建请求网关spring-cloud-getway
  • 原文地址:https://www.cnblogs.com/clark159/p/3708307.html
Copyright © 2020-2023  润新知