一、引言

最近smartsql被正式引入到了ncc,借着这个契机写一个使用教程系列

 

二、smartsql简介[摘自官方文档]

1. smartsql是什么?

smartsql = mybatis + cache(memory | redis) + r/w splitting +dynamic repository + diagnostics ……

2. smartsql的特性

简洁、高效、高性能、扩展性、监控、渐进式开发!

3. 她是如何工作的?

  smartsql 借鉴了 mybatis 的思想,使用 xml 来管理 sql ,并且提供了若干个筛选器标签来消除代码层面的各种 if/else 的判断分支。

  smartsql将管理你的 sql ,并且通过筛选标签来维护本来你在代码层面的各种条件判断,使你的代码更加优美。

4. 为什么选择smartsql?

  dotnet 体系下大都是 linq 系的 orm,linq 很好,消除了开发人员对 sql 的依赖。 但却忽视了一点,sql 本身并不复杂,而且在复杂查询场景当中开发人员很难通过编写linq来生成良好性能的sql,相信使用过ef的同学一定有这样的体验:“我想好了sql怎么写,然后再来写linq,完了可能还要再查看一下linq输出的sql是什么样的“。这是非常糟糕的体验。要想对sql做绝对的优化,那么开发者必须对sql有绝对的控制权。另外sql本身很简单,为何要增加一层翻译器呢?

 

三、开始smartsql之旅

  知道了smartsql是什么,那接下来我们开始创建一个项目从0开始使用smartsql写一个简单的curd接口服务。

  先上一个项目结构,然后我们一一分析他们的作用

 

1. 创建db

  这里我用的db是mssql,直接贴脚本了。

create database smartsqlsample
go
use smartsqlsample
go
create table t_article (
    id bigint not null primary key identity(1,1),
    title nvarchar(255) not null,
    content nvarchar(max) null,
    author nvarchar(255) null,
    status int not null,
    createtime datetime not null default getdate(),
    modifiedtime datetime not null default getdate()
)

2. smartsql 基础配置

2.1 添加nuget依赖

  smartsql的库可以直接在nuget上找到,但因为.netcoremvc的项目现在自带了di依赖注入的关系,我们只需要直接引用smartsql.di.extension就可以了。

  项目的依赖性包括了

  1. aspnetcore基础库

  2. smartsql.di.extension(我们的主角)

  3. swashbuckle.aspnetcore(方便我们接口测试)

 

2.2 添加smartsql配置文件

  smartsql是一个基于xml配置的orm。这点和mybatis没有什么不同。如果你熟悉mybatis,相信你很快就能适应smartsql。如果你以前没接触过类似的orm。那请跟着这个教程,一步一步了解smartsql的强大。

  smartsqlmapconfig.xml,smartsql的起点。

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <!--
 3 //*******************************
 4 // create by noah.ji
 5 // date 2019-05-10
 6 // github : https://github.com/noahjzc/smartsqlsample
 7 //*******************************-->
 8 <smartsqlmapconfig xmlns="http://smartsql.net/schemas/smartsqlmapconfig.xsd">
 9   <!-- 允许使用缓存(以后章节细讲) -->
10   <settings iscacheenabled="true" />
11   <!-- 属性、特性配置节点,这里只配置一个连接字符串 -->
12   <properties>
13     <property name="connectionstring" value="data source=localhost;database=smartsqlsample;uid=sa;pwd=123456" />
14     <property name="readoneconnectionstring" value="data source=123.123.123.123;database=smartsqlsample;uid=sa;pwd=123456" />
15   </properties>
16   <!-- 数据库配置 start -->
17   <database>
18     <dbprovider name="sqlserver" />
19     <write name="sample-write" connectionstring="${connectionstring}" />
20     <!-- 多读节点配置 -->
21     <!--
22     <read name="sample-node-1" connectionstring="${readoneconnectionstring}" weight="60"/>
23     <read name="sample-node-2" connectionstring="data source=456.456.456.456;database=smartsqlsample;uid=sa;pwd=123456" weight="40"/>
24     -->
25   </database>
26   <!-- 数据库配置 end -->
27   <!-- 数据map配置 start -->
28   <smartsqlmaps>
29     <!-- 文件夹 -->
30     <smartsqlmap path="maps" type="directory"></smartsqlmap>
31 
32     <!-- 文件夹及子集(递归获取文件夹下所有map文件) -->
33     <!--<smartsqlmap path="maps" type="directorywithallsub"></smartsqlmap>-->
34 
35     <!-- 单个文件 -->
36     <!--<smartsqlmap path="maps/t_article.xml" type="file"></smartsqlmap>-->
37 
38     <!-- 嵌入式资源 -->
39     <!--<smartsqlmap path="smartsqlsamplechapterone.maps.t_article.xml, smartsqlsamplechapterone" type="embedded"></smartsqlmap>-->
40 
41     <!-- http资源 -->
42     <!--<smartsqlmap type="uri" path="https://smartsql.net/maps/t_article.xml" />-->
43   </smartsqlmaps>
44   <!-- 数据map配置 end -->
45 </smartsqlmapconfig>

2.3 表map配置

2.3.1 root节点

1 <smartsqlmap scope="article" xmlns="http://smartsql.net/schemas/smartsqlmap.xsd">
2 ...
3 </smartsqlmap>

这里的关键在于scope,这个属性是用于定位map的。

2.3.2 cud配置

<!--新增-->
<statement id="insert">
  insert into t_article
  (title
  ,content
  ,author
  ,status
  ,createtime
  ,modifiedtime
  )
  values
  (@title
  ,@content
  ,@author
  ,@status
  ,@createtime
  ,getdate()
  );
  select scope_identity();
</statement>
<!--删除-->
<statement id="delete">
  delete t_article where id = @id
</statement>
<!--更新-->
<statement id="update">
  update t_article
  <set>
    modifiedtime = getdate()
    <isproperty prepend="," property="title">
      title = @title
    </isproperty>
    <isproperty prepend="," property="content">
      content = @content
    </isproperty>
    <isproperty prepend="," property="author">
      author = @author
    </isproperty>
    <isproperty prepend="," property="status">
      status = @status
    </isproperty>
    <isproperty prepend="," property="createtime">
      createtime = @createtime
    </isproperty>
  </set>
  where id=@id
</statement>

2.3.3 通用查询节点

<statement id="queryparams">
  <where>
    <isgreaterequal prepend="and" property="id" comparevalue="0">
      t.id = @id
    </isgreaterequal>
    <isnotempty prepend="and" property="title">
      t.title like '%'+@title+'%'
    </isnotempty>
    <isnotempty prepend="and" property="ids">
      t.id in @ids
    </isnotempty>
  </where>
</statement>

这个statement节点其实和别的节点没什么区别。smartsql允许statement的嵌套。使用规则如下面这段配置

<statement id="query">
  select t.* from t_article t
  <include refid="queryparams" />
  <switch prepend="order by" property="orderby">
    <default>
      t.id desc
    </default>
  </switch>
  <isnotempty prepend="limit" property="taken">@taken</isnotempty>
</statement>

在这段query配置中。我们使用了include标签来引入上面定义好的id为queryparams的statement,这样就做到了查询配置的通用性。例如我还可以将queryparams配置到分页和查询结果数的配置中。如下:

<!--获取分页数据-->
<statement id="querybypage">
  select t.* from t_article as t
  <include refid="queryparams" />
  <switch prepend="order by" property="orderby">
    <default>
      t.id desc
    </default>
  </switch>
  offset ((@pageindex-1)*@pagesize) rows fetch next @pagesize rows only;
</statement>

<!--获取记录数-->
<statement id="getrecord">
  select count(1) from t_article t
  <include refid="queryparams" />
</statement>

2.4 startup

注入smartsql

// register smartsql
services.addsmartsql(builder =>
{
    builder.usealias("smartsqlsamplechapterone");       // 定义实例别名,在多库场景下适用。
    //.usexmlconfig(resourcetype.file,"myconfig.xml");
});

在2.2中我们把基础配置文件命名为smartsqlmapconfig。这个是默认文件名,我们也可以像上面的注释代码一样。自定义配置文件的名称。

 

3. 让配置工作起来

其实到了这一步一切都顺其自然了。我感觉没有什么可以多讲了。直接上代码了!

  1 using microsoft.extensions.dependencyinjection;
  2 using smartsql;
  3 using smartsqlsamplechapterone.entity;
  4 using system;
  5 using system.collections.generic;
  6 
  7 namespace smartsqlsamplechapterone.dataaccess
  8 {
  9     /// <summary>
 10     /// 
 11     /// </summary>
 12     public class articledataaccess
 13     {
 14         private readonly isqlmapper _sqlmapper;
 15 
 16         /// <summary>
 17         /// 
 18         /// </summary>
 19         /// <param name="sp"></param>
 20         public articledataaccess(iserviceprovider sp)
 21         {
 22             _sqlmapper = sp.getsmartsql("smartsqlsamplechapterone").sqlmapper;
 23         }
 24 
 25         /// <summary>
 26         /// insert
 27         /// </summary>
 28         /// <param name="article"></param>
 29         /// <returns></returns>
 30         public long insert(t_article article)
 31         {
 32             return _sqlmapper.executescalar<long>(new requestcontext
 33             {
 34                 scope = "article",
 35                 sqlid = "insert",
 36                 request = article
 37             });
 38         }
 39 
 40         /// <summary>
 41         /// update
 42         /// </summary>
 43         /// <param name="article"></param>
 44         /// <returns></returns>
 45         public int update(t_article article)
 46         {
 47             return _sqlmapper.execute(new requestcontext
 48             {
 49                 scope = "article",
 50                 sqlid = "update",
 51                 request = article
 52             });
 53         }
 54 
 55         /// <summary>
 56         /// dyupdate
 57         /// </summary>
 58         /// <param name="updateobj"></param>
 59         /// <returns></returns>
 60         public int dyupdate(object updateobj)
 61         {
 62             return _sqlmapper.execute(new requestcontext
 63             {
 64                 scope = "article",
 65                 sqlid = "update",
 66                 request = updateobj
 67             });
 68         }
 69 
 70         /// <summary>
 71         /// delete
 72         /// </summary>
 73         /// <param name="id"></param>
 74         /// <returns></returns>
 75         public int delete(long id)
 76         {
 77             return _sqlmapper.execute(new requestcontext
 78             {
 79                 scope = "article",
 80                 sqlid = "delete",
 81                 request = new { id = id }
 82             });
 83         }
 84 
 85         /// <summary>
 86         /// getbyid
 87         /// </summary>
 88         /// <param name="id"></param>
 89         /// <returns></returns>
 90         public t_article getbyid(long id)
 91         {
 92             return _sqlmapper.querysingle<t_article>(new requestcontext
 93             {
 94                 scope = "article",
 95                 sqlid = "getentity",
 96                 request = new { id = id }
 97             });
 98         }
 99 
100         /// <summary>
101         /// query
102         /// </summary>
103         /// <param name="queryparams"></param>
104         /// <returns></returns>
105         public ienumerable<t_article> query(object queryparams)
106         {
107             return _sqlmapper.query<t_article>(new requestcontext
108             {
109                 scope = "article",
110                 sqlid = "query",
111                 request = queryparams
112             });
113         }
114 
115         /// <summary>
116         /// getrecord
117         /// </summary>
118         /// <param name="queryparams"></param>
119         /// <returns></returns>
120         public int getrecord(object queryparams)
121         {
122             return _sqlmapper.executescalar<int>(new requestcontext
123             {
124                 scope = "article",
125                 sqlid = "getrecord",
126                 request = queryparams
127             });
128         }
129 
130         /// <summary>
131         /// isexist
132         /// </summary>
133         /// <param name="queryparams"></param>
134         /// <returns></returns>
135         public bool isexist(object queryparams)
136         {
137             return _sqlmapper.querysingle<bool>(new requestcontext
138             {
139                 scope = "article",
140                 sqlid = "isexist",
141                 request = queryparams
142             });
143         }
144     }
145 }

 

4. 最后一步

4.1 articlecontroller

有了dataaccess我们可以轻松的操作数据库了。最后一步我们建立一个controller,对外暴露一些接口吧。

using microsoft.aspnetcore.mvc;
using smartsqlsamplechapterone.dataaccess;
using smartsqlsamplechapterone.entity;
using system.collections.generic;

namespace smartsqlsamplechapterone.controllers
{
    /// <summary>
    /// 
    /// </summary>
    [route("[controller]/[action]")]
    public class articlecontroller : controller
    {
        private readonly articledataaccess _articledataaccess;

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="articledataaccess"></param>
        public articlecontroller(articledataaccess articledataaccess)
        {
            _articledataaccess = articledataaccess;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="article"></param>
        /// <returns></returns>
        [httppost]
        public t_article add([frombody] t_article article)
        {
            article.id = _articledataaccess.insert(article);
            return article;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [httpget]
        public t_article get([fromquery] long id)
        {
            return _articledataaccess.getbyid(id);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="article"></param>
        /// <returns></returns>
        [httppost]
        public bool update([frombody] t_article article)
        {
            return _articledataaccess.update(article) > 0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        [httppost]
        public bool updatestatus([fromquery] long id, [fromquery] int status)
        {
            return _articledataaccess.dyupdate(new
            {
                id = id,
                status = status
            }) > 0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [httpget]
        public bool isexist([fromquery] long id)
        {
            return _articledataaccess.isexist(new
            {
                id = id
            });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        [httpget]
        public ienumerable<t_article> query([fromquery] string key = "")
        {
            return _articledataaccess.query(new
            {
                title = key
            });
        }
    }
}

4.2 startup

前面我们已经把smartsql注入到了di。现在我们再完善一下它,把mvc和swagger也注入进去。

 1 using microsoft.aspnetcore.builder;
 2 using microsoft.aspnetcore.hosting;
 3 using microsoft.extensions.configuration;
 4 using microsoft.extensions.dependencyinjection;
 5 using microsoft.extensions.logging;
 6 using swashbuckle.aspnetcore.swagger;
 7 using system;
 8 using system.io;
 9 using smartsql.configbuilder;
10 using smartsqlsamplechapterone.dataaccess;
11 
12 namespace smartsqlsamplechapterone
13 {
14     public class startup
15     {
16         public startup(iconfiguration configuration)
17         {
18             configuration = configuration;
19         }
20 
21         public iconfiguration configuration { get; }
22         // this method gets called by the runtime. use this method to add services to the container.
23         // for more information on how to configure your application, visit https://go.microsoft.com/fwlink/?linkid=398940
24         public void configureservices(iservicecollection services)
25         {
26             services.addmvc();
27 
28             services.addlogging(logging =>
29             {
30                 logging.setminimumlevel(loglevel.trace);
31                 logging.addconsole();
32             });
33 
34             // register smartsql
35             services.addsmartsql(builder =>
36             {
37                 builder.usealias("smartsqlsamplechapterone");       // 定义实例别名,在多库场景下适用。
38                 //.usexmlconfig(resourcetype.file,"myconfig.xml");
39             });
40 
41             // register data access
42             services.addsingleton<articledataaccess>();
43 
44             // register swagger
45             services.addswaggergen(c =>
46             {
47                 c.swaggerdoc("smartsqlsamplechapterone", new info
48                 {
49                     title = "smartsqlsample.chapterone",
50                     version = "v1",
51                     description = "smartsqlsample.chapterone"
52                 });
53                 var filepath = path.combine(appdomain.currentdomain.basedirectory, "smartsqlsamplechapterone.xml");
54                 if (file.exists(filepath)) c.includexmlcomments(filepath);
55             });
56 
57         }
58 
59         // this method gets called by the runtime. use this method to configure the http request pipeline.
60         public void configure(iapplicationbuilder app, ihostingenvironment env)
61         {
62             if (env.isdevelopment()) app.usedeveloperexceptionpage();
63             app.usemvc();
64 
65             app.useswagger(c => { });
66             app.useswaggerui(c => { c.swaggerendpoint("/swagger/smartsqlsamplechapterone/swagger.json", "smartsqlsamplechapterone"); });
67         }
68     }
69 }

好了!至此项目的大部分元素都做了一个简单介绍。我们来看看最终的运行结果吧。

5. 接口演示

接口预览

添加接口

获取接口

查询接口

 

6. 结语

本篇文章简单介绍了一下如何使用smartsql从无到有,完成一个单表的curd接口实现。但其实smartsql是一个非常强大的crm,它还有许多特性没有展开。再接下来的系列文章中。我会一一为大家介绍。

示例代码链接在这里

 

下期预告:使用动态代理实现curd