最近在实习的公司做项目,因为业务逻辑比较复杂,经常要在数据访问层中的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的设置。