前言

  搜索功能是一个很常用的功能,当然这个搜索不是指全文检索,是指网站的后台管理系统或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. YII 1.0 验证码

    public function actions(){ return array ( ‘captcha’=> array( ‘class’=> ‘CCatpchaAction’, ‘heig ...

  2. 1.4.2.2. PATHS(Core Data 应用程序实践指南)

    持久化存储文件在文件系统中的位置 先获取应用程序文档目录的路径 #pragma mark - PATHS - (NSString *)applicationDocumentsDirectory { ) ...

  3. jQuery的each函数

    http://www.cnblogs.com/xiaojinhe2/archive/2011/10/12/2208740.html http://www.cnblogs.com/mabelstyle/ ...

  4. 基于Daydream technical preview GVR13开发Daydream,Cardboard的Android应用

    本文用Unity的Daydream Preview GVR13版本开发同时兼容Daydream和Cardboard的Android应用,Android Studio版本为2.2.3. 下载最新Dayd ...

  5. EntityFramework Core解决并发详解

    前言 对过年已经无感,不过还是有很多闲暇时间来学学东西,这一点是极好的,好了,本节我们来讲讲EntityFramewoek Core中的并发问题. 话题(EntityFramework Core并发) ...

  6. webpack+react+redux+es6开发模式---续

    一.前言 之前介绍了webpack+react+redux+es6开发模式 ,这个项目对于一个独立的功能节点来说是没有问题的.假如伴随着源源不断的需求,前段项目会涌现出更多的功能节点,需要独立部署运行 ...

  7. node源码详解(六) —— 从server.listen 到事件循环

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载保留声明头部与原文链接https://luzeshu.com/blog/nodesource6 本博客同步在https://cnodejs.o ...

  8. MVC + Vue.js 初体验(实现表单操作)

    Vuejs http://cn.vuejs.org/ Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的 渐进式框架.与其他重量级框架不同的是,Vue 采用自底向上增量开发的 ...

  9. CodeForces757A

    A. Gotta Catch Em' All! time limit per test 1 second memory limit per test 256 megabytes input stand ...

  10. 路由器安装ubuntu-16.04.1-server-amd64出现“无法安装busybox-initramfs”错误。向目标系统中安装busybox-initramfs软件包时出现一个错误。请检查/var/log/syslog或查看第四虚拟控制台以获得详细

    公司的路由器要ubuntu服务器进行路由网络功能的管理,在安装的时候出现下面的错误提示: 安装ubuntu-16.04.1-server-amd64出现“无法安装busybox-initramfs”错 ...