最近在弄一个东东,类似那种CMS的后台管理系统,方便作为其它项目的初始化框架用的。

现在遇到个问题,如标题所示:Dapper通用的多表联合分页查询怎么破?

单表的话很简单就可以实现,多表不通用的话也可以很方便的实现,那么如果多表通用的话,怎么办呢?

难道只能通过拼接sql或者使用存储过程吗?我先来展示下我的实现方式,希望你有更好的方式,然后同我分享一下,以便解决我的困扰。

因为本来就是做的传统的CMS类似的项目,所以技术选型也是比较传统,抛弃了mvvm的js框架、webapi接口、以及nosql缓存。

技术选型:MVC5、Mysql、Dapper、Autofac、Layui、阿里巴巴矢量库、T4(后面补上)。

  • MVC5:目前.net开发的主流web框架。
  • Mysql:轻量免费功能强大的关系型数据库。
  • Dapper:据说是性能最好的.net ORM框架。
  • Autofac:据说是性能最好的.net IOC框架。
  • Layui:经典的模块化UI框架,还是很适合我们这样的后端开发人员的。
  • 阿里巴巴矢量库:丰富的矢量图标库。
  • T4:强大的代码生成模板。

我选择的都是轻量级比较干净的东东来组合的框架。

我选择由外入内的方式来阐述我现在遇到的问题。以用户管理界面为例,我讲只列出涉及到用户分页查询的代码,将会省略其它代码.....

大致上的效果如下图所示:

经典的多层架构

Global.asax.cs代码,Dapper自动注入。

  public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles); //创建autofac管理注册类的容器实例
var builder = new ContainerBuilder();
SetupResolveRules(builder);
//使用Autofac提供的RegisterControllers扩展方法来对程序集中所有的Controller一次性的完成注册 支持属性注入
builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(); // 把容器装入到微软默认的依赖注入容器中
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
private static void SetupResolveRules(ContainerBuilder builder)
{
//WebAPI只用引用services和repository的接口,不用引用实现的dll。
//如需加载实现的程序集,将dll拷贝到bin目录下即可,不用引用dll
var iServices = Assembly.Load("RightControl.IService");
var services = Assembly.Load("RightControl.Service");
var iRepository = Assembly.Load("RightControl.IRepository");
var repository = Assembly.Load("RightControl.Repository"); //根据名称约定(服务层的接口和实现均以Services结尾),实现服务接口和服务实现的依赖
builder.RegisterAssemblyTypes(iServices, services)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces().PropertiesAutowired(); //根据名称约定(数据访问层的接口和实现均以Repository结尾),实现数据访问接口和数据访问实现的依赖
builder.RegisterAssemblyTypes(iRepository, repository)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces().PropertiesAutowired();
}
}

BaseController:

   public class BaseController : Controller
{// GET: Base
public virtual ActionResult Index()
{
return View();
}

UserController:

    public class UserController : BaseController
{
private IUserService service; public UserController(IUserService _service)
{
service = _service;
}
/// <summary>
/// 加载数据列表
/// </summary>
/// <param name="pageInfo">页面实体信息</param>
/// <param name="filter">查询条件</param>
/// <returns></returns>
[HttpGet]
public JsonResult List(PageInfo pageInfo, UserModel filter)
{
var result = service.GetListByFilter(filter, pageInfo);
return Json(result, JsonRequestBehavior.AllowGet);
}

PageInfo:

    public class PageInfo
{
public int page { get; set; }
public int limit { get; set; }
/// <summary>
/// 排序字段 CreateOn
/// </summary>
public string field { get; set; }
/// <summary>
/// 排序方式 asc desc
/// </summary>
public string order { get; set; }
/// <summary>
/// 返回字段逗号分隔
/// </summary>
public string returnFields { get; set; }
public string prefix { get; set; }
}

UserModel:

using DapperExtensions;
using System;
using System.ComponentModel.DataAnnotations; namespace RightControl.Model
{
[Table("t_User")]
public class UserModel:Entity
{
/// <summary>
/// 用户名
/// </summary>
[Display(Name = "用户名")]
public string UserName { get; set; }
/// <summary>
/// 真实名称
/// </summary>
[Display(Name = "真实名称")]
public string RealName { get; set; }
/// <summary>
/// 密码
/// </summary>
public string PassWord { get; set; }
/// <summary>
/// 创建者
/// </summary>
public int CreateBy { get; set; }
/// <summary>
/// 角色ID
/// </summary>
public int RoleId { get; set; }
/// <summary>
/// 更新时间
/// </summary>
[Display(Name = "更新时间")]
public DateTime UpdateOn { get; set; }
[Computed]
public string RoleName { get; set; }
}
}

Entity:

    public class Entity
{
[DapperExtensions.Key(true)]
public virtual int Id { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd HH:mm:ss}")]
[Display(Name = "创建时间")]
public DateTime CreateOn { get; set; }
/// <summary>
/// 菜单状态(1:启用,0:禁用)
/// </summary>
public bool Status { get; set; }
#region 查询条件
[Computed]
public string StartEndDate { get; set; }
#endregion
}

IBaseService:

    public interface IBaseService<T> where T : class, new()
{
dynamic GetListByFilter(T filter, PageInfo pageInfo);
}

IUserService:

    public interface IUserService : IBaseService<UserModel>
{
...
}

BaseService:

    public abstract class BaseService<T> where T : class, new()
{
public IBaseRepository<T> baseRepository{get; set;}
public dynamic GetPageUnite(IBaseRepository<T> repository, PageInfo pageInfo, string where, object filter)
{
string _orderBy = string.Empty;
if (!string.IsNullOrEmpty(pageInfo.field))
{
_orderBy = string.Format(" ORDER BY {0} {1}", pageInfo.prefix+pageInfo.field, pageInfo.order);
}
else
{
_orderBy = string.Format(" ORDER BY {0}CreateOn desc",pageInfo.prefix);
}
long total = ;
var list = repository.GetByPageUnite(new SearchFilter { pageIndex = pageInfo.page, pageSize = pageInfo.limit, returnFields = pageInfo.returnFields, param = filter, where = where, orderBy = _orderBy }, out total); return Pager.Paging(list, total);
}
protected string CreateWhereStr(Entity filter, string _where)
{
if (!string.IsNullOrEmpty(filter.StartEndDate) && filter.StartEndDate != " ~ ")
{
var dts = filter.StartEndDate.Trim().Split('~');
var start = dts[].Trim();
var end = dts[].Trim();
if (!string.IsNullOrEmpty(start))
{
_where += string.Format(" and CreateOn>='{0}'", start + " 00:00");
}
if (!string.IsNullOrEmpty(end))
{
_where += string.Format(" and CreateOn<='{0}'", end + " 59:59");
}
} return _where;
}
}

UserService:

    public class UserService: BaseService<UserModel>, IUserService
{
public IUserRepository repository { get; set; }//属性注入
public dynamic GetListByFilter(UserModel filter, PageInfo pageInfo)
{
pageInfo.prefix = "u.";
string _where = " t_User u INNER JOIN t_role r on u.RoleId=r.Id";
if (!string.IsNullOrEmpty(filter.UserName))
{
_where += string.Format(" and {0}UserName=@UserName",pageInfo.prefix);
}
if (!string.IsNullOrEmpty(pageInfo.order))
{
pageInfo.order = pageInfo.prefix + pageInfo.order;
}
pageInfo.returnFields = string.Format("{0}Id,{0}UserName,{0}RealName,{0}CreateOn,{0}`PassWord`,{0}`Status`,{0}RoleId,r.RoleName",pageInfo.prefix);
return GetPageUnite(baseRepository, pageInfo, _where, filter);
}

IBaseRepository:

 public interface IBaseRepository<T> where T : class, new()
{
IEnumerable<T> GetByPageUnite(SearchFilter filter, out long total);
}

IUserRepository:

    public interface IUserRepository : IBaseRepository<UserModel>
{ }

BaseRepository:

    public class BaseRepository<T>: IBaseRepository<T> where T :class, new()
{
public IEnumerable<T> GetByPageUnite(SearchFilter filter, out long total)
{
using (var conn = MySqlHelper.GetConnection())
{
return conn.GetByPageUnite<T>(filter.pageIndex, filter.pageSize, out total, filter.returnFields, filter.where, filter.param, filter.orderBy, filter.transaction, filter.commandTimeout);
}
} }

UserRepository:

    public class UserRepository : BaseRepository<UserModel>, IUserRepository
{
}

最后的分页代码:

        /// <summary>
/// 获取分页数据
/// </summary>
public static IEnumerable<T> GetByPageUnite<T>(this IDbConnection conn, int pageIndex, int pageSize, out long total, string returnFields = null, string where = null, object param = null,
string orderBy = null, IDbTransaction transaction = null, int? commandTimeout = null)
{
int skip = ;
if (pageIndex > )
{
skip = (pageIndex - ) * pageSize;
} StringBuilder sb = new StringBuilder();
sb.AppendFormat("SELECT COUNT(1) FROM {0};", where);
sb.AppendFormat("SELECT {0} FROM {1} {2} LIMIT {3},{4}", returnFields, where, orderBy, skip, pageSize);
using (var reader = conn.QueryMultiple(sb.ToString(), param, transaction, commandTimeout))
{
total = reader.ReadFirst<long>();
return reader.Read<T>();
}
}

Index视图:

@{
Layout = "~/Views/Shared/_LayoutList.cshtml";
} <!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title>Table</title>
</head> <body>
<div class="admin-main">
<blockquote class="layui-elem-quote p10">
<form id="formSearch" class="layui-form" action="">
<div class="layui-form-item" style="margin-bottom:0px;">
<label class="layui-form-label">用户名称:</label>
<div class="layui-input-inline">
<input name="UserName" id="UserName" lay-verify="" autocomplete="off" class="layui-input">
</div>
<label class="layui-form-label">角色名称:</label>
<div class="layui-input-inline">
@Html.DropDownList("RoleId", null, "-请选择角色-", new Dictionary<string, object> { { "lay-verify", "required" } })
</div>
<label class="layui-form-label">状态:</label>
<div class="layui-input-inline">
@Html.StatusSelectHtml()
</div>
@Html.SearchBtnHtml()
@Html.ResetBtnHtml()
<div style="float:right;">
@Html.TopToolBarHtml(ViewData["ActionFormRightTop"])
</div>
</div>
</form>
</blockquote>
<div class="layui-field-box">
<table id="defaultTable" lay-filter="defaultruv"></table>
<!-- 这里的 checked 的状态只是演示 -->
@*<input type="checkbox" name="Status" value="{{d.Id}}" lay-skin="switch" lay-text="开启|禁用" lay-filter="statusSwitch" {{ d.Status == 1 ? 'checked' : '' }}>*@
<script type="text/html" id="bar">
@Html.ToolBarHtml(ViewData["ActionList"])
</script>
</div>
</div>
<script>
layui.config({
base: '/plugins/app/'
}); layui.use(['table', 'common', 'form'], function () {
var table = layui.table,
form = layui.form,
common = layui.common;
//表格
table.render({
id: 'defaultReload'
, elem: '#defaultTable'
, height: 'full-112' //高度最大化减去差值
, url: '/Permissions/User/List' //数据接口
, page: true //开启分页
, cols: [[ //表头
{ checkbox: true, fixed: true },
{ field: 'Id', title: 'Id', width: 80, fixed: 'left' }
, { field: 'UserName', title: '用户名称', sort: true }
, { field: 'RealName', title: '真实姓名' }
, { field: 'RoleName', title: '角色名称' }
, { field: 'Status', title: '状态', templet: '<div>{{showStatus(d.Status)}}</div>', unresize: true, width: 100, align: 'center' }
, { field: 'CreateOn', title: '创建时间', width: 160, sort: true, templet: '<div>{{showDate(d.CreateOn)}}</div>' }
, { field: '', title: '操作', toolbar: "#bar" }
]]
});
var $ = layui.$, active = {
reload: function () {
var jsonWhere = urlToJson($("#formSearch").serialize());
//执行重载
table.reload('defaultReload', {
page: {
curr: 1 //重新从第 1 页开始
}
, where: jsonWhere
});
}
};
//服务器排序
table.on('sort(defaultruv)', function (obj) {
//尽管我们的 table 自带排序功能,但并没有请求服务端。
//有些时候,你可能需要根据当前排序的字段,重新向服务端发送请求,如:
table.reload('defaultReload', {
initSort: obj //记录初始排序,如果不设的话,将无法标记表头的排序状态。 layui 2.1.1 新增参数
, where: { //请求参数
field: obj.field //排序字段
, order: obj.type //排序方式
}
});
});
$('#btnSearch').on('click', function () {
var type = $(this).data('type');
active[type] ? active[type].call(this) : '';
});
//add
$('#btnAdd').on('click', function () {
common.openTop({
title: '用户添加', w: '600px', h: '360px', content: '/Permissions/User/Add/'
});
});
//监听工具条
table.on('tool(defaultruv)', function (obj) {
var data = obj.data;
if (obj.event === 'detail') {
common.openTop({
detail: true,
title: '角色详情', w: '600px', h: '360px', content: '/Permissions/User/Detail/' + data.Id, clickOK: function (index) {
common.close(index);
}
});
} else if (obj.event === 'del') {
layer.confirm('确定要删除吗?', function (index) {
$.ajax({
url: "/Permissions/User/Delete",
type: "POST",
data: { "Id": data.Id },
dataType: "json",
success: function (data) {
if (data.state == "success") {
obj.del();//删除这一行
common.msgSuccess("删除成功");
} else {
common.msgError("删除失败");
}
layer.close(index);//关闭弹框
}
});
});
} else if (obj.event === 'edit') {
common.openTop({
title: '用户编辑', w: '600px', h: '360px', content: '/Permissions/User/Edit/' + data.Id
});
} else if (obj.event === 'reset') {
layer.confirm('确定要初始化密码吗?', function (index) {
$.ajax({
url: "/Permissions/User/InitPwd",
type: "POST",
data: { "Id": data.Id },
dataType: "json",
success: function (data) {
if (data.state == "success") {
layer.close(index);//关闭弹框
common.msgSuccess("密码初始化成功");
} else {
common.msgError("密码初始化失败");
}
layer.close(index);//关闭弹框
}
});
});
}
});
});
</script>
</body> </html>

_LayoutBase模板页:

<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<link rel="stylesheet" href="~/plugins/layui/css/layui.css" media="all" />
<link href="~/Content/global.css" rel="stylesheet" />
<link href="~/Content/table.css" rel="stylesheet" />
<script src="~/plugins/layui/layui.js"></script>
<script src="~/plugins/app/global.js"></script>
</head>
<body>
<div id="ajax-loader" style="cursor: progress; position: fixed; top: -50%; left: -50%; width: 200%; height: 200%; background: #fff; z-index: 10000; overflow: hidden;">
<img src="~/Content/images/ajax-loader.gif" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: auto;" />
</div>
@RenderBody()
<script type="text/javascript">
layui.config({
base: '/plugins/app/',
version: '1522709297490' //为了更新 js 缓存,可忽略
});
layui.use(['common'], function () {
layer.config({
skin: 'layui-layer-molv'
})
var $ = layui.jquery;
$(function () {
$('#ajax-loader').fadeOut();
})
})
</script>
</body>
</html>

代码结构基本上就这样了。

作为一名有追求的程序员,当看到代码连自己都受不了,我就开始抓狂,每次写代码的时候,脑袋里都是那几句话“不要重复你的代码、依赖于抽象....”

项目详细介绍和代码获取请移步:.net项目驱动学习

.net通用CMS快速开发框架——问题1:Dapper通用的多表联合分页查询怎么破?的更多相关文章

  1. CRL快速开发框架系列教程一(Code First数据表不需再关心)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  2. CRL快速开发框架系列教程十三(嵌套查询)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  3. CRL快速开发框架系列教程十二(MongoDB支持)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  4. CRL快速开发框架系列教程十一(大数据分库分表解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  5. CRL快速开发框架系列教程十(导出对象结构)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  6. CRL快速开发框架系列教程九(导入/导出数据)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  7. CRL快速开发框架系列教程七(使用事务)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  8. CRL快速开发框架系列教程六(分布式缓存解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  9. CRL快速开发框架系列教程五(使用缓存)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

随机推荐

  1. ELK学习总结(1-3)倒排索引

    1.倒排索引(反向索引) 一种索引方法,用来存储在全文检索下某个单词在一个/组文档中的存储位置. 常规索引,文档->关键词,费时,得把一个文档全部遍历一遍 倒排索引,关键词->文档,全文搜 ...

  2. Linux后台运行命令 nohup command > myout.file 2>&1

    Linux命令后台运行 转自北国的雨,谢谢:http://www.cnblogs.com/lwm-1988/archive/2011/08/20/2147299.html 有两种方式:1. comma ...

  3. spring6——AOP的编程术语

    面向切面编程作为一种编程思想,允许我们对程序的执行流程及执行结果动态的做出改变,以达到业务逻辑之间的分层管理或者是目标对象方法的增强,spring框架很好的实现了这种编程思想,让我们可以对主业务逻辑和 ...

  4. Java-NIO(七):阻塞IO与非阻塞IO

    阻塞IO 传统的 IO 流都是阻塞式的. 也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务. 因此,在完成网络通信 ...

  5. Why deep learning?

    1. 深度学习中网络越深越好么? 理论上说是这样的,因为网络越深,参数也越多,拟合能力也越强(但实际情况是,网络很深的时候,不容易训练,使得表现能力可能并不好). 2. 那么,不同什么深度的网络,在参 ...

  6. 典型分布式系统分析之MapReduce

    在 <分布式学习最佳实践:从分布式系统的特征开始(附思维导图)>一文中,提到学习分布式系统的一个好方法是思考分布式系统要解决的问题,有哪些衡量标准,为了解决这些问题:提出了哪些理论.协议. ...

  7. XML 解析默认去掉命名空间和注释

    //创建xml文档 XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(@"C:\Users\Tony\Downloads\统一标准报文格 ...

  8. 生成和配置https证书

    最近在做小程序,调用后台接口需要https协议请求,小程序之所以这么要求,也是因为http协议是明文传播文件数据的,出于数据安全考虑,必须使用https协议. http想实现为https 就需要为配置 ...

  9. 在Linux服务器部署 .NET-Core 项目

    一.文章概要  这篇文章是讲述一个Linux 新手将 .NET-Core 项目部署在 Linux 服务器上的一个记录,以及在部署期间遇到的问题以及解决办法.有不恰当的地方.欢迎大神指正. 二.前期准备 ...

  10. 模板 AC自动机

    题目描述 有$N$ 个由小写字母组成的模式串以及一个文本串$T$ .每个模式串可能会在文本串中出现多次.你需要找出哪些模式串在文本串$T$ 中出现的次数最多. 输入输出格式 输入格式: 输入含多组数据 ...