[Architecture Pattern] Repository实作查询功能

[Architecture Pattern] Repository实作查询功能


范例下载


范例程序代码:点此下载

问题情景

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

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

解决方案


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)


  • 执行范例(userId=A123)


  • 执行范例(userId=A123, state=Completed)


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)


  • 执行范例(userId=A123)


  • 执行范例(userId=A123, state=Completed)


IRepository查询

完成上列这些步骤之后,也就完成了Repository实作查询功能的开发工作,用户就能在系统页面上填写查询条件,来从系统中查询所有符合条件内容的数据对象集合。

  • 执行范例(All)


  • 执行范例(userId=A123)


  • 执行范例(userId=A123, state=Completed)


后记

Repository实作查询功能的开发工作套用本篇的解决方案,能在BLL中完全不需要牵扯DAL的信息,只需要单纯传递C#类别来做为查询条件,这部分提高了BLL的内聚力。而GetAllByCondition的设计,因为单纯使用C#类别来传递查询条件,这让DAL实作不会被绑死在特定数据源上,也大幅提高了BLL的重用性。开发人员设计系统时遇到需要Repository实作查询功能的开发工作,参考本篇提供的解决方案应该就能满足大部分的开发需求。

[Architecture Pattern] Repository实作查询功能,布布扣,bubuko.com

时间: 2024-10-24 09:50:42

[Architecture Pattern] Repository实作查询功能的相关文章

springcloud在 repository层通过sql语句实现查询功能

springcloud在实现其查询功能时,其自动生成sql语句,只用写相应的方法名及参数即可,在执行复杂sql语句时,需手写sql语句,其语法规则如下: @Query(value = "SELECT * FROM *** an WHERE an.id = ?1 AND " + "an.status &2 !=2 ORDER BY (an.status &1) DESC;", nativeQuery = true) List<AlumniNoti

[Architecture Pattern] Singleton Locator

[Architecture Pattern] Singleton Locator 目的 组件自己提供Service Locator模式,用来降低组件的耦合度. 情景 在开发系统时,底层的Infrastructure Context.或是核心的Domain Context这些共享对象生成之后,会在系统的许多地方被使用.为了减少共享对象初始生成.参考传递所造成的困扰,可以在系统内套用Service Locator模式,提供统一的静态参考点来生成.存取这些共享对象. Service Locator参考

[Architecture Pattern] Factory Builder

[Architecture Pattern] Factory Builder 目的 同时提供延迟注入对象.挂载注入项目这两个功能 情景 在开发系统时,如果需要在运行时间才生成并注入对象,可以套用Factory模式来提供延迟注入对象功能.例如:一个监控系统在火警发生时,建立功能对象来启动相关设备(洒水设备.警报设备.警消通报). 物件图 但在实作过程中,延迟注入对象这个功能,常常需要伴随挂载注入项目功能,用以增加系统的延展性,这时可以接着套用Builder模式,来提供挂载注入项目功能.例如:一个监

一步一步学SpringDataJpa——JpaRepository查询功能

1.JpaRepository支持接口规范方法名查询.意思是如果在接口中定义的查询方法符合它的命名规则,就可以不用写实现,目前支持的关键字如下. Keyword Sample JPQL snippet IsNotNull findByAgeNotNull ...  where x.age not null Like findByNameLike ...  where x.name like ?1 NotLike findByNameNotLike ...  where x.name not li

Python与数据库[2] -&gt; 关系对象映射/ORM -&gt; 利用 sqlalchemy 实现关系表查询功能

利用 sqlalchemy 实现关系表查询功能 下面的例子将完成一个通过关系表进行查询的功能,示例中的数据表均在MySQL中建立,建立过程可以使用 SQL 命令或编写 Python 适配器完成. 示例中用到的表主要有3张,一张personInfo个人信息表,一张account_store账号信息表,以及一张person_account_rel的个人信息与账号关系表. 示例中将会通过已知的人物年龄和id通过个人信息表查出个人姓名(仅为参考示例,请忽略怪异的查找逻辑 :) ),随后根据关系表得到的人

SpringDataJpa——JpaRepository查询功能(转)

1.JpaRepository支持接口规范方法名查询.意思是如果在接口中定义的查询方法符合它的命名规则,就可以不用写实现,目前支持的关键字如下. Keyword Sample JPQL snippet IsNotNull findByAgeNotNull ...  where x.age not null Like findByNameLike ...  where x.name like ?1 NotLike findByNameNotLike ...  where x.name not li

python实现whois查询功能的方法源码

恐怕很多朋友跟我一样,使用python语言居然能实现whois服务器查询功能.下面我把代码和说明搬来给大家看看,有谁需要可以参考下.本来想直接从whois服务器查询的,但是发现要写socket 用43端口链接服务器,但是有些服务器的地址不清楚,而且查询命令貌似有改变所以不想折腾了,就想着直接用chinaz的页面实现一下算了.如下代码是在 win7下操作的,安装python3.2测试通过. Python3.2实现whois查询功能的方法源码: # -*- coding:utf-8 -*- impo

利用PHP访问数据库——实现分页功能与多条件查询功能

1.实现分页功能 <body><table width="100%" border="1">  <thead>    <tr>      <th>代号</th>      <th>名称</th>      <th>价格</th>    </tr>  </thead>  <tbody>      <?php 

组合查询功能实现

前言 这是我的第二篇文章,这是我之前做的ERP项目的时候设计实现的.在这个ERP系统中,功能比较多,表设计的时候建立了很多业务表.对于一些业务表需要执行很多查询,客户要求针对不同的字段进行查询,基于我们之前的设计,针对不同的查询条件设计不同的DAL方法,通过不同的方法签名来实现客户的对于不同条件查询的要求.但是这种解决方案会让程序员很被动,久而久之整个DAL层会显得很臃肿. 面对这样的困境,考虑是否可以实现用一个通用的DAL方法来代替所有的不同筛选条件查询方法,因为这些查询方法内部的逻辑是一样的