最近在實習的公司做項目,因為業務邏輯比較復雜,經常要在數據訪問層中的XXXService中添加各種查詢方法。但久而久之,里面的查詢方法越來越多,不僅難以維護,而且在多人開發時,很容易寫出一些功能相同但名字不同的方法。但在三層架構的設計思想中,數據訪問層中的方法應僅包含對數據庫的操作,相關的業務邏輯應該在業務邏輯層中實現。同時,這些查詢方法在本質上都是根據某些條件查詢數據。我就想設計一個通用的查詢方法,一方面可以和具體的業務邏輯解耦,一方面也能簡化代碼。於是有下面的設計(在數據庫操作方面使用了 entity framework技術):
1 public abstract class BaseService<T> where T : class, new() {
2 /// <summary>
3 /// 根據查詢條件和排序方法返回結果集
4 /// </summary>
5 /// <param name="order">排序方法的委托</param>
6 /// <param name="filters">查詢條件</param>
7 public IQueryable<T> GetDatas(Func<IQueryable<T>, IOrderedQueryable<T>> order, params Expression<Func<T, bool>>[] filters) {
8 try {
9 IQueryable<T> rs = GetTheTableData();
10 if (filters != null) {
11 foreach (var filter in filters) {
12 if (filter != null) {
13 rs = rs.Where(filter);
14 }
15 }
16 }
17 if (order != null) {
18 rs = order(rs);
19 }
20 return rs;
21 } catch {
22 throw;
23 }
24 }
25 /// <summary>
26 /// 根據開始索引、每頁大小查詢條件和排序方法返回分頁結果集,適用於前台分頁件
27 /// </summary>
28 /// <param name="startIndex">開始索引</param>
29 /// <param name="pageSize">頁面大小</param>
30 /// <param name="totalCount">數據總數</param>
31 /// <param name="order">排序方法的委托</param>
32 /// <param name="filters">查詢條件</param>
33 public IQueryable<T> GetDatas(int startIndex, int pageSize, out int totalCount,
34 Func<IQueryable<T>, IOrderedQueryable<T>> order, params Expression<Func<T, bool>>[] filters) {
35 try {
36 IQueryable<T> rs = GetDatas(order, filters);
37 totalCount = rs.Count();
38 if (startIndex < 0 || pageSize < 1) {
39 return rs;
40 } else {
41 return rs.Skip(startIndex * pageSize).Take(pageSize);
42 }
43 } catch {
44 throw;
45 }
46 }
47 /// <summary>
48 /// 根據查詢條件和排序方法返回結果
49 /// </summary>
50 /// <param name="filters">查詢條件</param>
51 public T GetData(params Expression<Func<T, bool>>[] filters) {
52 var rs = GetDatas(null, filters).FirstOrDefault();
53 return rs ?? new T();
54 }
55 /// <summary>
56 /// 返回相應的表數據
57 /// </summary>
58 protected abstract IQueryable<T> GetTheTableData();
59 }
我在Linq to Entity技術的基礎上,將通用的查詢過程,封裝成一個泛型的抽象類,包含了常用的查詢單個結果,查詢結果集和查詢分布結果集方法。下面我將通過一個簡單的日志查詢系統來介紹下該類的使用方法和帶來的優勢,前台效果圖如下:
數據庫設計如下:
1 CREATE TABLE [Log](
2 [ID] [int] IDENTITY(1,1) NOT NULL primary key,--主鍵
3 [Name] [nvarchar](50) NULL,--姓名
4 [Operation] [nvarchar](50) NULL,--操作
5 [Time] [datetime] NULL --時間
6 )
首先,新建Ado.net Entity Data Model的項,然后選擇從數據庫生成來創建實體層。然后,為要操作的實體建立相應的Servcie類。
1 public class LogService : BaseService<Model.Log> {
2 Model.TestEntities entities = new Model.TestEntities();
3 protected override IQueryable<Log> GetTheTableData() {
4 return entities.Log;
5 }
6 }
可見,通過對泛型抽象類BaseServcie的繼承,大大減少了代碼量。
相應的業務邏輯層如下:
1 public class LogBiz {
2 Dal.LogService logService = new Dal.LogService();
3 public List<Model.Log> GetAll() {
4 return logService.GetDatas(rs => rs.OrderBy(l => l.Time), null).ToList();
5 }
6 public List<Model.Log> GetByFilter(params Expression<Func<Model.Log, bool>>[] filters) {
7 return logService.GetDatas(rs => rs.OrderBy(l => l.Time), filters).ToList();
8 }
9 }
在寫業務邏輯層時,可以通過調用Service類提供有通用查詢方法和lambada表達式快速的寫出自己想要的邏輯。
前台源文件如下:

1 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LogSearch._Default" %>
2
3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4 <html xmlns="http://www.w3.org/1999/xhtml">
5 <head runat="server">
6 <title></title>
7 <link type="text/css" rel="stylesheet" href="../Css/jquery.ui.all.css" />
8 <link type="text/css" rel="stylesheet" href="../Css/demos.css" />
9
10 <script type="text/javascript" src="../Script/jquery-1.4.2.js"></script>
11
12 <script type="text/javascript" src="../Script/jquery-ui-1.8.10.custom.min.js"></script>
13
14 <script type="text/javascript" src="../Script/jquery.ui.datepicker-zh-CN.js"></script>
15
16 <script type="text/javascript">
17 $(function() {
18 $.datepicker.setDefaults($.datepicker.regional["zh-CN"]);
19 var dates = $("#<%=txtFromDate.ClientID %>, #<%=txtToDate.ClientID %>").datepicker({
20 defaultDate: "+1w",
21 changeMonth: true,
22 numberOfMonths: 1,
23
24 onSelect: function(selectedDate) {
25 var option = this.id == "<%=txtFromDate.ClientID%>" ? "minDate" : "maxDate",
26 instance = $(this).data("datepicker");
27 var date;
28 if (option == "minDate") {
29 selectedDate = theDate.toDateString();
30 date = $.datepicker.parseDate(
31 instance.settings.dateFormat ||
32 $.datepicker._defaults.dateFormat,
33 selectedDate, instance.settings);
34 } else {
35
36 date = $.datepicker.parseDate(
37 instance.settings.dateFormat ||
38 $.datepicker._defaults.dateFormat,
39 selectedDate, instance.settings);
40 }
41 dates.not(this).datepicker("option", option, date);
42 }
43 });
44 });
45 </script>
46
47 </head>
48 <body>
49 <form id="form1" runat="server">
50 <div>
51 <p>
52 關鍵字:<asp:TextBox ID="txtKeyWord" runat="server"></asp:TextBox>時間區間:
53 <asp:TextBox ID="txtFromDate" runat="server"></asp:TextBox>~<asp:TextBox ID="txtToDate" runat="server"></asp:TextBox>
54 <asp:Button ID="btnSearch" runat="server" Text="查詢" onclick="btnSearch_Click" /></p>
55 <table border="1" cellspacing="0" cellpadding="0" width="600px">
56 <tr>
57 <td>序號</td>
58 <td>姓名</td>
59 <td>操作</td>
60 <td>時間</td>
61 </tr>
62 <asp:Repeater ID="rpData" runat="server">
63 <ItemTemplate>
64 <tr>
65 <td><%#Eval("ID") %></td>
66 <td><%#Eval("Name") %></td>
67 <td><%#Eval("Operation") %></td>
68 <td><%#Eval("Time","{0:yyyy-M-d}") %></td>
69 </tr>
70 </ItemTemplate>
71 </asp:Repeater>
72 </table>
73 </div>
74 </form>
75 </body>
76 </html>
后台文件如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using System.Web.UI;
6 using System.Web.UI.WebControls;
7 using System.Linq.Expressions;
8
9 namespace LogSearch {
10 public partial class _Default : System.Web.UI.Page {
11 protected void Page_Load(object sender, EventArgs e) {
12 if (!IsPostBack) {
13 Biz.LogBiz logBiz = new Biz.LogBiz();
14 rpData.DataSource = logBiz.GetAll();
15 rpData.DataBind();
16 }
17 }
18
19 protected void btnSearch_Click(object sender, EventArgs e) {
20 List<Expression<Func<Model.Log, bool>>> filters = new List<Expression<Func<Model.Log, bool>>>();
21 if (!string.IsNullOrEmpty(txtKeyWord.Text)) {
22 string keyWord = txtKeyWord.Text;
23 Expression<Func<Model.Log, bool>> keyWordFilter = l => l.Name.Contains(keyWord);
24 filters.Add(keyWordFilter);
25 }
26 if (!string.IsNullOrEmpty(txtFromDate.Text)) {
27 DateTime fromDate = DateTime.Parse(txtFromDate.Text);
28 Expression<Func<Model.Log, bool>> fromDateFilter = l => l.Time >= fromDate;
29 filters.Add(fromDateFilter);
30 }
31 if (!string.IsNullOrEmpty(txtToDate.Text)) {
32 DateTime toDate = DateTime.Parse(txtToDate.Text);
33 Expression<Func<Model.Log, bool>> toDateFilter = l => l.Time <= toDate;
34 filters.Add(toDateFilter);
35 }
36 Biz.LogBiz logBiz = new Biz.LogBiz();
37 rpData.DataSource = logBiz.GetByFilter(filters.ToArray());
38 rpData.DataBind();
39
40 }
41 }
42 }
在btnSearch_Click方法中可以體現出,這樣的設計可以動態的添加查詢條件。要獲得其它查詢只需要用lambada表達式寫不同的條件即可,再也不用改Service類並在Business類中添加相應調用方法了。即使是復雜的條件也可以通過Expression表達式樹來生成,同時,相比於傳統拼sql語句方法,lambada表達式的使用也減少了犯錯的可能。
使用到的技術:entity framework,lambada表達式,Expression表達式樹,泛型,params關鍵字
第一次發文章,難免有不妥的地方,還請大家多多指正。
源碼下載地址:http://115.com/file/beswmm9j#LogSearch.zip
注:代碼是用VS2008寫的。數據庫名為Test,按照提供的sql語句創建表。如果數據庫取其它的名字,請更改下web.config中connectionStrings的設置。