冬Blog

醉心技术、醉心生活
  博客园  :: 首页  :: 新随笔  :: 订阅 订阅  :: 管理

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

Posted on 2008-06-04 20:51  冬冬  阅读(1473)  评论(6编辑  收藏  举报

上一篇中,我们分析了页面中的控件和数据绑定中的扩展方法,今天我们看数据的查询和显示。其中,数据的查询属于业务逻辑层(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)填充。

代码下载