最近在弄一个东东,类似那种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. HTNL表单详解

    HTML表单 表单的结构 表单的标签:<form> </form> 常用属性 Name , method(get,post), action(服务器的接收的页面如:reg.ph ...

  2. python/零起点(一、字符串)

    python/零起点(一.字符串) 字符串(str) 字符串是可迭代的 str()强制转成字符串类型 字符串是有序的,且不可变的数据类型. 字符串转换整型的案例: 1 name=['guobaoyua ...

  3. Spring(六):Spring&Struts2&Hibernate搭建的blog项目

    经过断断续续的学习.累积,终于基于别人的开源blog项目,变成了自己的第一个相对完整点的blog项目. 计划暂时把这个blog程序暂停------有更多(工作中用到的)东西需要去做,因此学习SSH b ...

  4. 小工具:批量导入导出主机上的docker镜像

    工作需要,将主机上的部分镜像导出为tar文件,放便在其他主机上使用 用python实现了一个批量打包脚本: import re import os import subprocess if __nam ...

  5. drupal 8 建立我的相册

    一.先建一个存图片的内容类型 1.创建内容类型 点击[结构]>[内容类型]>[+add content type] 点击[保存和管理字段] 2.添加字段 点击[+添加字段] 点击[保存并继 ...

  6. 前端性能监控系统 & 前端数据分析系统

    前端监控系统 目前已经上线,欢迎使用! 背景:应工作要求,需要整理出前端项目的报错信息,尝试过很多统计工具,如: 腾讯bugly.听云.OneApm.还有一个忘记名字的工具. 因为各种原因,如: 统计 ...

  7. 生成和配置https证书

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

  8. [USACO13OPEN]重力异常

    题目描述 船长正在拯救她的船员,Beefalo 博士. 和所有伟大的冒险故事一样,这个故事也是发生在一个2D平面上的.囧 这个平面是M*N的格子组成的网格,代表着船长的世界的一个侧视图. 有些格子是空 ...

  9. Codeforces Round #407 (Div. 1)

    人傻不会B 写了C正解结果因为数组开小最后RE了 疯狂掉分 AC:A Rank:392 Rating: 2191-92->2099 A. Functions again 题目大意:给定一个长度为 ...

  10. 【USACO07NOV】电话线Telephone Wire

    题目描述 电信公司要更换某个城市的网线.新网线架设在原有的 N(2 <= N <= 100,000)根电线杆上, 第 i 根电线杆的高度为 height_i 米(1 <= heigh ...