前言

  搜索功能是一个很常用的功能,当然这个搜索不是指全文检索,是指网站的后台管理系统或ERP系统列表的搜索功能。常见做法一般就是在搜索栏上加上几个常用字段来搜索。代码可能一般这样实现

StringBuilder sqlStr = new StringBuilder();
if (!string.IsNullOrEmpty(RealName))
{
    sqlStr.Append(" and RealName  = @RealName");
}
if (Age != -1)
{
    sqlStr.Append(" and Age = @Age");
}
if (!string.IsNullOrEmpty(StartTime))
{
    sqlStr.Append(" and CreateTime >= @StartTime");
}
if (!string.IsNullOrEmpty(EndTime))
{
    sqlStr.Append(" and CreateTime <= @EndTime");
}
MySqlParameter[] paras = new MySqlParameter[]{
            new MySqlParameter("@Age", Age),
            new MySqlParameter("@RealName", RealName),
            new MySqlParameter("@StartTime", StartTime),
            new MySqlParameter("@EndTime", EndTime)
        };

这段代码如果遇到下面几个需求,又该如何处理?

  1. 再加一个查询字段
  2. RealName需要改成模糊查询
  3. Age需要支持范围查询

可能大多数程序猿想法,这是新的需求,那么就直接改代码,简单粗暴。然后在前台加个age范围文本框,后台再加个if判断,realname的=号就直接改成like,就这样轻松搞定了。但需求总是不断变化,如果一张表有50个字段,同时需要支持其中40个字段查询。我想大都数人第一反应:卧槽,神经病!难道就没有一个通用的办法来解决这种搜索的问题?我想说当然有,本文接下来就用DapperExtensions和反射来解决这个问题,最终于实现的效果如下图:

DapperExtensions介绍

  DapperExtensions是基于Dapper的一个扩展,主要在Dapper基础上实现了CRUD的操作。它还提供了一个谓词系统,可以实现更多复杂的高级查询功能。还可以通过ClassMapper来定义实体类和表的映射。

通用搜索功能实现

1.首先创建一个account表,然后增加一个Account类

public class Account
    {
        public Account()
        {
            Age = -1;
        }
        /// <summary>
        /// 账户ID
        /// </summary>
        [Mark("账户ID")]
        public int AccountId { get; set; }
        /// <summary>
        /// 姓名
        /// </summary>
        [Mark("姓名")]
        public string RealName { get; set; }
        /// <summary>
        /// 年龄
        /// </summary>
        [Mark("年龄")]
        public int Age { get; set; }
        /// <summary>
        /// 创建时间
        /// </summary>
        [Mark("创建时间")]
        public DateTime CreateTime { get; set; }
    }

2.为了获取字段对应的中文名称,我们增加一个MarkAttribute类。因为有强大的反射功能,我们可以通过反射动态获取每张表实体类的属性和中文名称。

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class MarkAttribute : Attribute
{
public MarkAttribute(string FiledName, string Description = "")
{
this.FiledName = FiledName;
this.Description = Description;
} private string _FiledName;
public string FiledName
{
get { return _FiledName; }
set { _FiledName = value; }
} private string _Description;
public string Description
{
get { return _Description; }
set { _Description = value; }
}
}

3.通用搜索思路主要是把搜索功能抽象出一个对象,本质上也就列名、操作符、值组成的一个对象集合,这样就可以实现多个搜索条件的组合。我们增加一个Predicate类

public class Predicate
{
/// <summary>
/// 列名
/// </summary>
public string ColumnItem { get; set; }
/// <summary>
/// 操作符
/// </summary>
public string OperatorItem { get; set; }
/// <summary>
/// 值
/// </summary>
public object Value { get; set; }
}

4.然后通过反射Account类的属性加载到前台列名的DropDownList,再增加一个操作符的DropDownList

var columnItems = new List<SelectListItem>();
//通过反射来获取类的属性
//Type t = Assembly.Load("SearchDemo").GetType("SearchDemo.Models.Account");
       Type t = typeof(SearchDemo.Models.Account);   
foreach (PropertyInfo item in t.GetProperties())
{
string filedName = (item.GetCustomAttributes(typeof(MarkAttribute), false)[] as MarkAttribute).FiledName;
columnItems.Add(new SelectListItem() { Text = filedName, Value = item.Name });
}
ViewBag.columnItems = columnItems; var operatorItems = new List<SelectListItem>()
{
new SelectListItem() {Text = "等于", Value = "Eq"},
new SelectListItem() {Text = "大于", Value = "Gt"},
new SelectListItem() {Text = "大于或等于", Value = "Ge"},
new SelectListItem() {Text = "小于", Value = "Lt"},
new SelectListItem() {Text = "小于或等于", Value = "Le"},
new SelectListItem() {Text = "模糊", Value = "Like"}
};
ViewBag.operatorItems = operatorItems;

5.前台界面实现代码

<!DOCTYPE html>
<html>
<head>
    <title>DapperExtensions通用搜索</title>
    <script src="../../Scripts/jquery-1.4.4.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        Date.prototype.format = function (format) {
            var o = {
                "M+": this.getMonth() + 1, //month   
                "d+": this.getDate(), //day   
                "h+": this.getHours(), //hour   
                "m+": this.getMinutes(), //minute   
                "s+": this.getSeconds(), //second   
                "q+": Math.floor((this.getMonth() + 3) / 3), //quarter   
                "S": this.getMilliseconds() //millisecond   
            }             if (/(y+)/.test(format)) {
                format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
            }             for (var k in o) {
                if (new RegExp("(" + k + ")").test(format)) {
                    format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
                }
            }
            return format;
        }  
    </script>
    <style type="text/css">
        ul
        {
            list-style: none;
            padding: 0px;
            margin: 0px;
            width: 590px;
            height: 20px;
            line-height: 20px;
            border: 1px solid #99CC00;
            border-top: 0px;
            font-size: 12px;
        }
        ul li
        {
            display: block;
            width: 25%;
            float: left;
            text-indent: 2em;
        }
        .th
        {
            background: #F1FADE;
            font-weight: bold;
            border-top: 1px solid #99CC00;
        }
    </style>
    <script type="text/javascript">
        var predicates = [];
        var index = 0;
        $(document).ready(function () {
            $("#btnAdd").click(function () {
                var columnItem = $("#columnItems option:selected");
                var operatorItem = $("#operatorItems option:selected");
                var value = $("#value").val();
                if(value == ""){
                    alert("请输入值");
                    return;
                }
                var predicate = { index: index, columnItem: columnItem.val(), operatorItem: operatorItem.val(), value: value };
                predicates.push(predicate);
                var html = "<ul><li>" + columnItem.text() + "</li><li>" + operatorItem.text() + "</li><li>" + value + "</li><li><a href='javascript:;' onclick='del(this," + index + ")'>删除</a></li></ul>"
                $("#predicates ul:last").after(html);
                index++;
            })             $("#btnSearch").click(function () {
                $.ajax({
                    type: "POST",
                    url: "home/search",
                    data: JSON.stringify(predicates),
                    contentType: "application/json",
                    success: function (data) {
                        if (data.Error != null) {
                            alert(data.Error);
                            return;
                        }
                        $("#list .th").nextAll().remove();
                        var html = "";
                        $.each(data, function (index, item) {
                            html += "<ul><li>" + item.AccountId + "</li>";
                            html += "<li>" + item.RealName + "</li>";
                            html += "<li>" + item.Age + "</li>";
                            //转换日期
                            var dateMilliseconds = parseInt(item.CreateTime.replace(/\D/igm, ""));
                            var date = new Date(dateMilliseconds);
                            html += "<li>" + date.format("yyyy-MM-dd hh:mm:ss") + "</li></ul>";
                        });
                        $("#list .th").after(html);
                    }
                });
            })
        })         function del(obj,index) {
            obj.parentNode.parentNode.remove();
            for (var i = 0; i < predicates.length; i++) {
                if (predicates[i].index == index) {
                    predicates.splice(i, 1);
                }
            }
        }
    </script>
</head>
<body>
    <div>
        列名:@Html.DropDownList("columnItems")&nbsp;&nbsp;操作符:@Html.DropDownList("operatorItems")&nbsp;&nbsp;值:@Html.TextBox("value")&nbsp;&nbsp;
        <input id="btnAdd" type="button" value="增加" />&nbsp;&nbsp;<input id="btnSearch" type="button" value="搜索" />
    </div>
    <br />
    <div id="predicates">
        <ul class="th">
            <li>列名</li>
            <li>操作符</li>
            <li>值</li>
            <li>操作</li>
        </ul>
    </div>
    <br />
    <div id="list">
        <ul class="th">
            <li>账户ID</li>
            <li>姓名</li>
            <li>年龄</li>
            <li>创建时间</li>
        </ul>    
    </div>
</body>
</html>

6.最后通过DapperExtensions的谓词和反射实现搜索方法

        [HttpPost]
public JsonResult Search(List<Predicate> predicates)
{
if (predicates == null)
{
return Json(new { Error = "请增加搜索条件" });
} using (var connection = SqlHelper.GetConnection())
{
var pga = new PredicateGroup { Operator = GroupOperator.And, Predicates = new List<IPredicate>() };
foreach (var p in predicates)
{
var predicate = Predicates.Field<Account>(GetExpression(p), (Operator)Enum.Parse(typeof(Operator), p.OperatorItem), p.Value);
pga.Predicates.Add(predicate);
}
var list = connection.GetList<Account>(pga);
return Json(list);
}
} private static Expression<Func<Account, object>> GetExpression(Predicate p)
{
ParameterExpression parameter = Expression.Parameter(typeof(Account), "p");
return Expression.Lambda<Func<Account, object>>(Expression.Convert(Expression.Property(parameter, p.ColumnItem), typeof(object)), parameter);
}

  最终,通过简单的几行代码,在基于DapperExtensions的功能基础上,我们最终实现了一个可以支持多个字段、多个条件、多个操作符的通用查询功能。本文也只是抛砖引玉,只是提供一种思路,还有更多细节没有考虑。比如多个条件的组合可以再增加一个逻辑符来连接、多个条件组合嵌套查询、多表查询等等。

Dapper地址

DapperExtensions的Wiki

SearchDemo源码

ps:本人也是第一次在博客园发博客,有些地方可能表达不清楚,请广大网友多多见谅。如果您有什么好的建议和意见,也可以在文章的评论区留言给我,我会及时更正! 我希望以后在博客园多发布一些文章,和大家做更多的技术交流和分享。如果觉得本文对你有用,请大家多多点推荐或收藏。

用DapperExtensions和反射来实现一个通用搜索的更多相关文章

  1. Java反射结合JDBC写的一个通用DAO

    以前写反射只是用在了与设计模式的结合上,并没有考虑到反射可以与DAO结合.也是一个偶然的机会,被正在上培训的老师点到这个问题,才考虑到这个可能性,于是上网参考各种代码,然后自己动手开发了一个通用DAO ...

  2. 为了去重复,写了一个通用的比较容器类,可以用在需要比较的地方,且支持Lamda表达式

    为了去重复,写了一个通用的比较容器类,可以用在需要比较的地方,且支持Lamda表达式,代码如下: public class DataComparer<T>:IEqualityCompare ...

  3. 写一个通用的List集合导出excel的通用方法

    前几天要做一个数据导出Excel 我就打算写一个通用的. 这样一来用的时候也方便,数据主要是通过Orm取的List.这样写一个通用的刚好. public static void ListToExcel ...

  4. 编写一个通用的Makefile文件

    1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...

  5. Linux C编程学习之开发工具3---多文件项目管理、Makefile、一个通用的Makefile

    GNU Make简介 大型项目的开发过程中,往往会划分出若干个功能模块,这样可以保证软件的易维护性. 作为项目的组成部分,各个模块不可避免的存在各种联系,如果其中某个模块发生改动,那么其他的模块需要相 ...

  6. 封装一个通用递归算法,使用TreeIterator和TreeMap来简化你的开发工作。

    在实际工作中,你肯定会经常的对树进行遍历,并在树和集合之间相互转换,你会频繁的使用递归. 事实上,这些算法在逻辑上都是一样的,因此可以抽象出一个通用的算法来简化工作. 在这篇文章里,我向你介绍,我封装 ...

  7. 一个通用的DataGridView导出Excel扩展方法(支持列数据格式化)

    假如数据库表中某个字段存放的值“1”和“0”分别代表“是”和“否”,要在DataGridView中显示“是”和“否”,一般用两种方法,一种是在sql中直接判断获取,另一种是在DataGridView的 ...

  8. 利用RBAC模型实现一个通用的权限管理系统

    本文主要描述一个通用的权限系统实现思路与过程.也是对此次制作权限管理模块的总结. 制作此系统的初衷是为了让这个权限系统得以“通用”.就是生产一个web系统通过调用这个权限系统(生成的dll文件), 就 ...

  9. 用Java实现一个通用并发对象池

    这篇文章里我们主要讨论下如何在Java里实现一个对象池.最近几年,Java虚拟机的性能在各方面都得到了极大的提升,因此对大多数对象而言,已经没有必要通过对象池来提高性能了.根本的原因是,创建一个新的对 ...

随机推荐

  1. HBuilder mui引导页制作

    http://www.bcty365.com/content-146-2582-1.html

  2. Eclipse 使用说明

    2. 使用技巧 2.1. Eclipse 打开文件所在文件夹 2.2. Mac 安装Eclipse 2.3. Eclipse 安装插件 2.4. Eclipse 工具栏不见了 2.5. Eclipse ...

  3. 部署statspack工具(二)之解决方案1

    7.解决方案一:调整buffer cache sys@TESTDB12>alter system set sga_max_size=804m scope=spfile;        //重启数 ...

  4. easyUI resizable组件使用

    easyUI resizable组件使用: <!DOCTYPE html> <html lang="en"> <head> <meta c ...

  5. Asp.net mvc 知多少(三)

    本系列主要翻译自<ASP.NET MVC Interview Questions and Answers >- By Shailendra Chauhan,想看英文原版的可访问http:/ ...

  6. 【原】小写了一个cnode的小程序

    小程序刚出来的第一天,朋友圈被刷屏了,所以趁周末也小玩了一下小程序.其实发觉搭建一个小程序不难,只要给你一个demo,然后自己不断的查看文档,基本就可以入门了,不过对于这种刚出来的东西,还是挺多坑的, ...

  7. php知识点总结(一)

    1.把数组以表格的形式显示 <?php $array = array( '书籍' =>  array( '生活',  '人与自然','动物世界'), '体育用品' =>  array ...

  8. Angular企业级开发(6)-使用Gulp构建和打包前端项目

    1.gulp介绍 基于流的前端自动化构建工具,利用gulp可以提高前端开发效率,特别是在前后端分离的项目中.使用gulp能完成以下任务: 压缩html.css和js 编译less或sass等 压缩图片 ...

  9. php curl详细解析和常见大坑

    1. 拿来先试试手 比如我们以著名的"测试网络是否连接"的网站--百度为例,来尝试下curl <?php // create curl resource $ch = curl ...

  10. 融云发送手机短信验证短信(.net版本)

    首先本次需求是在MVC5中进行的,通过收费的融云服务来验证手机号码,而且本次的项目也是前后台分离,所以添加了WEBAPI2,那么先添加WEBAPI的接口 using System; using Sys ...