微型项目实践(12):查询与显示

news/2024/7/3 10:50:26

上一篇中,我们分析了页面中的控件和数据绑定中的扩展方法,今天我们看数据的查询和显示。其中,数据的查询属于业务逻辑层(Business),而显示属于UI层。

假设我们要根据以下两个条件进行查询:

  1. 日志分类(ID)
  2. 发布时间(范围)

两者为“AND”的关系,则查询可以通过通过扩展IQueryable<Blog>类实现,该功能定义在BlogExtension类中(位于DongBlog.Business\Blogs\Blog.cs文件中),代码如下:

   1:  /// <summary>
   2:  /// 根据日志分类取得日志
   3:  /// </summary>
   4:  /// <param name="query">日志查询</param>
   5:  /// <param name="blogClassID">日志分类ID</param>
   6:  /// <param name="createDataTimeStart">日志发表的起始时间</param>
   7:  /// <param name="createDataTimeEnd">日志发表的结束时间</param>
   8:  /// <returns>该分类下的日志</returns>
   9:  public static List<Blog> GetBlogsBy(this IQueryable<Blog> query, 
          int? blogClassID, DateTime? createDataTimeStart, DateTime? createDataTimeEnd)
  10:  {
  11:      if (query == null)
  12:          throw new ArgumentNullException("query");
  13:   
  14:      var q = query.AsQueryable();
  15:   
  16:      if (blogClassID.HasValue)
  17:          q = q.Where(b => b.BlogClassID == blogClassID.Value);
  18:      if (createDataTimeStart.HasValue)
  19:          q = q.Where(b => b.CreateDateTime > createDataTimeStart.Value);
  20:      if (createDataTimeEnd.HasValue)
  21:          q = q.Where(b => b.CreateDateTime < createDataTimeEnd.Value);
  22:   
  23:      return q
  24:          .OrderByDescending(b => b.CreateDateTime)
  25:          .ToList();
  26:  }

这段代码充分运用了Linq面向对象的方法进行数据库的查询,表间关联使用类的引用表示,最终会由Linq2SQL翻译为SQL语句。这种方法可以完成非常复杂的查询,在我们团队目前的项目中,最多的在一次查询中涉及五个表,几十个字段条件,条件还包括等于,大于、小于、属于,相似等多种情况。

这段代码虽然实现了查询功能,不过存在几个问题:第一,CreateDataTimeStart和CreateDataTimeEnd应该是一个表示范围的整体;第二,函数的参数随着查询条件的增加会变得越来越臃肿;第三,没有考虑分页。我们重构一下以上代码,解决这些问题。

在重构之前,还有一个要注意的是:重构的安全网——测试。在这个系统中,由于规模很小,没有实现完整的测试。但是所有的代码都是可以(并且容易)测试的,比如上面这个查询函数,可以通过实现用于测试的IQueryable<Blog>实现自动化测试。

我们先来重构第一个问题,抽取CreateDataTimeStart和CreateDataTimeEnd作为一个类,称之为TimeRange,放在Common层中,代码如下:

   1:  using ...
   5:   
   6:  namespace DongBlog.Common
   7:  {
   8:      /// <summary>
   9:      /// 表示一个时间段
  10:      /// </summary>
  11:      public class TimeRange
  12:      {
  13:   
  14:          /// <summary>
  15:          /// 取得或设置起始时间
  16:          /// </summary>
  17:          public DateTime? StartTime { get; set; }
  18:   
  19:          /// <summary>
  20:          /// 取得或设置终止时间
  21:          /// </summary>
  22:          public DateTime? EndTime { get; set; }
  23:   
  24:          /// <summary>
  25:          /// 构造一个时间段
  26:          /// </summary>
  27:          public TimeRange() { }
  28:          /// <summary>
  29:          /// 构造一个时间段
  30:          /// </summary>
  31:          /// <param name="startTime">起始时间</param>
  32:          /// <param name="endTime">终止时间</param>
  33:          public TimeRange(DateTime startTime, DateTime endTime)
  34:          {
  35:              this.StartTime = startTime;
  36:              this.EndTime = endTime;
  37:          }
  38:      }
  39:  }

然后GetBlogs函数可以重构为:

   1:  public static List<Blog> GetBlogsBy(
             this IQueryable<Blog> query, int? blogClassID, TimeRange createDateTime)
   2:  {
   3:      if (query == null)
   4:          throw new ArgumentNullException("query");
   5:   
   6:      var q = query.AsQueryable();
   7:   
   8:      if (blogClassID.HasValue)
   9:          q = q.Where(b => b.BlogClassID == blogClassID.Value);
  10:   
  11:      if (createDateTime != null)
  12:      {
  13:          if (createDateTime.StartTime.HasValue)
  14:              q = q.Where(b => b.CreateDateTime > createDateTime.StartTime.Value);
  15:          if (createDateTime.EndTime.HasValue)
  16:              q = q.Where(b => b.CreateDateTime < createDateTime.EndTime.Value);
  17:      }
  18:   
  19:      return q
  20:          .OrderByDescending(b => b.CreateDateTime)
  21:          .ToList();
  22:  }

对月第二和第三个问题,我们利用前面提到的Common层的Query相关类一并解决。添加BlogQueryInformation类,代码如下:

   1:  /// <summary>
   2:  /// 日志查询信息
   3:  /// </summary>
   4:  public class BlogQueryInformation : QueryInformation
   5:  {
   6:      /// <summary>
   7:      /// 取得或设置日志分类ID
   8:      /// </summary>
   9:      public int? BlogClassID { get; set; }
  10:      /// <summary>
  11:      /// 取得或设置日志发表时间
  12:      /// </summary>
  13:      public TimeRange CreateDateTime { get; set; }
  14:  }

这样,就可以把GetBlogs方法重构如下:

   1:  public static QueryResult<Blog> GetBlogsBy(
           this IQueryable<Blog> query, BlogQueryInformation queryInfo)
   2:  {
   3:      if (query == null)
   4:          throw new ArgumentNullException("query");
   5:      if (queryInfo == null)
   6:          throw new ArgumentNullException("queryInfo");
   7:   
   8:      var q = query.AsQueryable();
   9:   
  10:      if (queryInfo.BlogClassID.HasValue)
  11:          q = q.Where(b => b.BlogClassID == queryInfo.BlogClassID.Value);
  12:   
  13:      if (queryInfo.CreateDateTime != null)
  14:      {
  15:          if (queryInfo.CreateDateTime.StartTime.HasValue)
  16:              q = q.Where(b => b.CreateDateTime > queryInfo.CreateDateTime.StartTime.Value);
  17:          if (queryInfo.CreateDateTime.EndTime.HasValue)
  18:              q = q.Where(b => b.CreateDateTime < queryInfo.CreateDateTime.EndTime.Value);
  19:      }
  20:   
  21:      var resultList = q.OrderByDescending(b => b.CreateDateTime)
  22:          .Skip(queryInfo.Begin).Take(queryInfo.Limit)
  23:          .ToList();
  24:   
  25:      return new QueryResult<Blog>(resultList, q.Count());
  26:  }

最后两句代码实现了分页功能。以后添加新的查询条件时,只需要修改BlogQueryInformation和GetBlogs方法就可以了,函数的签名不需要修改。

然后我们看页面的调用,Default页面中,负责显示的代码如下:

   1:  protected void Page_Load(object sender, EventArgs e)
   2:  {
   3:      if (!IsPostBack)
   4:          display();
   5:  }
   6:   
   7:  private void display()
   8:  {
   9:      var pageCountHelper = new PageCountHelper(this, "篇");
  10:      var queryInfo = new BlogQueryInformation();
  11:   
  12:      queryInfo.BlogClassID = getBlogClassID();
  13:      queryInfo.CreateDateTime = getCreateDateTime();
  14:      queryInfo.Begin = pageCountHelper.CurrentPageCount
                 * pageCountHelper.RecordPerPage;
  15:      queryInfo.Limit = pageCountHelper.RecordPerPage;
  16:   
  17:      var result = Database.Blogs.GetBlogsBy(queryInfo);
  18:      ListView_BlogList.DataSource = result.ResultList;
  19:      LiteralPageCount.Text = pageCountHelper.GetPageCountHtml(result.Total);
  20:   
  21:      DataBind();
  22:  }
  23:  private TimeRange getCreateDateTime()
  24:  {
  25:      var createDateTime = new TimeRange();
  26:      return TimeRange.TryPrase(Request["CreateDateTime"], createDateTime) ? 
                 createDateTime : null;
  27:  }
  28:  private int? getBlogClassID()
  29:  {
  30:      var blogClassIDString = Request["BlogClassID"];
  31:      return String.IsNullOrEmpty(blogClassIDString) ? 
               null : new Nullable<int>(Convert.ToInt32(blogClassIDString));
  32:  }

即:构造查询条件 -> 调用业务逻辑层(这里是GetBlogs方法)进行查询 -> 显示查询结果。其中用到了PageCountHelper这个辅助类,负责处理页面的分页,原理很简单,无非是拼Html,细节可以参考代码。

Master页面中,用于显示日志分类的ListView也使用这种方法实现数据绑定。至此,日志显示、根据日志分类的简单筛选和分页都已经实现了,最后我们得到了如下效果:

image

其中的测试数据,使用测试(InitialTestData)填充。

代码下载

本文转自冬冬博客园博客,原文链接:http://www.cnblogs.com/yuandong/archive/2008/06/04/1213908.html ,如需转载请自行联系原作者

http://www.niftyadmin.cn/n/2015594.html

相关文章

Python 核心编程(第二版)——文件和输入输出

1.文件对象 文件对象不仅可以用来访问普通的磁盘文件, 而且也可以访问任何其它类型抽象层面上的"文件". 一旦设置了合适的"钩子", 你就可以访问具有文件类型接口的其它对象, 就好像访问的是普通文件一样。文件只是连续的字节序列. 数据的传输经常会用到字节…

使用 Nim 语言和使用 C 语言, 调用系统 POSIX 接口传递文件描述符的对比

进程组之间传递文件描述符, 是多进程服务器共享套接字常用的方式. 基于此方式, 可以提供 round-robin , 使每个服务器获得平衡的负载. Nim 和 C 语言调用相同的 POSIX 接口的编程方式如下, (事实上, Nim 是调用的 C POSIX 接口) Nim import posixproc CMSG_LEN(length: cuint)…

Python模块导入时全局变量__all__的作用

Python中一个py文件就是一个模块&#xff0c;“__all__”变量是一个特殊的变量&#xff0c;可以在py文件中&#xff0c;也可以在包的__init__.py中出现。 1、在普通模块中使用时&#xff0c;表示一个模块中允许哪些属性可以被导入到别的模块中&#xff0c;如&#xff1a;全局变…

准备充分了嘛就想学函数式编程?(Part 2)

本文讲的是准备充分了嘛就想学函数式编程&#xff1f;(Part 2)&#xff0c;想要理解函数式编程&#xff0c;第一步总是最重要&#xff0c;也是最困难的。但是只要有了正确的思维&#xff0c;其实也不是太难。 之前的部分: 第一部分 友情提示 请读仔细读代码&#xff0c;确保继续…

Python浅拷贝和深拷贝的思考

简而言之&#xff0c;浅拷贝就是只是复制了引用&#xff0c;并没有复制引用指向地址的内容&#xff0c;可以看做是同一个对象&#xff0c;使用is操作符时返回true&#xff0c;而深拷贝刚好相反&#xff0c;不管变量或者对象包含多少其他的引用都原模原样复制一份&#xff0c;即…

Mysql创建多列唯一索引Sql

ALTER TABLE t_city_combo ADD UNIQUE INDEX t_city_combo_index (combo_id, combo_name, city_id) USING BTREE 语法 ALTER TABLE 表名英文 ADD UNIQUE INDEX 索引名英文 (列1英文, 列2英文, 列3) USING BTREE Navicat 创建索引&#xff0c;需要如下步骤 在表右键-->设计…

Python对象的命名和私有化

在Python中常见的变量和方法等命名通常是这样的&#xff1a;name&#xff1a;一般变量&#xff0c;在类中&#xff0c;类外都可以使用&#xff0c;对象直接可以访问__name:双下划线私有变量&#xff0c;在类中可以访问&#xff0c;类外不能直接访问_name:单下划线的变量&#x…

在Jmeter中使用自定义编写的Java测试代码

分类&#xff1a; 测试工具 2007-01-19 15:02 8204人阅读 评论(3) 收藏 举报 测试javaloadrunner编译器importtemplates我们在做性能测试时,有时需要自己编写测试脚本,很多测试工具都支持自定义编写测试脚本,比如LoadRunner就有很多自定义脚本的协议,比如"C Vuser",&…