SmartSql使用教程(1)——初探,建立一個簡單的CURD介面服務

Noah.Ji發表於2019-05-13

一、引言

最近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()
)
Init指令碼

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>
CUD配置

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

在這段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 }
ArticleDataAccess

 

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
            });
        }
    }
}
ArticleController

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 }
Startup

好了!至此專案的大部分元素都做了一個簡單介紹。我們來看看最終的執行結果吧。

5. 介面演示

介面預覽

新增介面

獲取介面

查詢介面

 

6. 結語

本篇文章簡單介紹了一下如何使用SmartSql從無到有,完成一個單表的CURD介面實現。但其實SmartSql是一個非常強大的CRM,它還有許多特性沒有展開。再接下來的系列文章中。我會一一為大家介紹。

示例程式碼連結在這裡

 

下期預告:使用動態代理實現CURD

 

相關文章