數據庫操作

插件通常擁有自己的表結構,用于存儲插件相關的數據,本節將(jiāng)說(shuō)明如何創建數據庫表,以及如何在代碼中對(duì)數據進(jìn)行操作。

配置數據庫表結構

爲了讓 XYCMS 系統自動創建我們所需要的數據庫表,我們需要在插件的 package.json 文件中對(duì)數據庫表結構進(jìn)行配置。

爲了讓 XYCMS 系統自動創建我們所需要的數據庫表,我們需要對(duì)插件 package.json 配置文件的 extensions -> tables 節點進(jìn)行設置,在此,我們以XYCMS 内容相冊插件 (opens new window)的package.json (opens new window)作爲示例說(shuō)明表結構的配置:

"tables": {
  "xycms_photos": {
    "columns": [
      {
        "attributeName": "SiteId",
        "dataType": "Integer"
      },
      {
        "attributeName": "ChannelId",
        "dataType": "Integer"
      },
      {
        "attributeName": "ContentId",
        "dataType": "Integer"
      },
      {
        "attributeName": "SmallUrl",
        "dataType": "VarChar"
      },
      {
        "attributeName": "MiddleUrl",
        "dataType": "VarChar"
      },
      {
        "attributeName": "LargeUrl",
        "dataType": "VarChar"
      },
      {
        "attributeName": "Taxis",
        "dataType": "Integer"
      },
      {
        "attributeName": "Description",
        "dataType": "VarChar",
        "dataLength": 2000
      }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  • xycms_photos:表名稱,可以任意取值,需要确保唯一性,插件啓動時(shí) XYCMS 系統將(jiāng)爲插件創建對(duì)應的數據庫表。
  • columns:表字段集合。
  • attributeName:字段名稱。
  • dataType:字段類型。
  • dataLength:字段長(cháng)度,如果是字符串類型字段,不設置的話默認長(cháng)度爲500。

系統支持的字段類型有一下幾種(zhǒng):

屬性類型
Boolean布爾值
DateTime日期
Decimal小數
Integer整數
Text備注
VarChar字符串

由于 XYCMS 系統支持多種(zhǒng)數據庫,不同數據庫類型的實際字段類型名稱與 dataType 的名稱可能(néng)有區别。

如果插件版本更新的同時(shí)更新了數據庫表結構,XYCMS 系統將(jiāng)負責把更新的表結構同步到實際數據庫中。

需要注意的是,XYCMS 系統在創建和同步表結構的同時(shí),將(jiāng)默認創建以下字段:

屬性類型說(shuō)明
IdInteger自增長(cháng)Id字段
GuidVarChar字符串類型的全局唯一标識符,XYCMS系統負責爲此字段賦值并保持唯一性
ExtendValuesText擴展字段
CreatedDateDateTime數據創建時(shí)間,XYCMS系統負責爲此字段賦值
LastModifiedDateDateTime數據修改時(shí)間,XYCMS系統負責爲此字段賦值

數據操作框架

XYCMS 系統將(jiāng)在插件第一次加載的時(shí)候核對(duì)表字段并創建或者同步表結構到數據庫,我們接下來需要做的就(jiù)是編寫代碼操作數據庫,對(duì)數據進(jìn)行增删改查的操作。

實現數據的操作方式有很多,沒(méi)有必須遵守的規則,XYCMS 系統使用的數據操作類庫是我們自行開(kāi)發(fā)的 Datory 框架,Datory 框架是我們在流行的 Dapper 框架基礎上封裝了一些操作接口而成(chéng),如果熟悉 Dapper 框架會(huì)發(fā)現操作接口非常類似。

我們以 XYCMS 自帶的 Datory 框架作爲演示并不是推薦使用此框架,大家在實際開(kāi)發(fā)過(guò)程中應該盡量使用自己熟悉的數據庫操作類庫進(jìn)行數據操作。

定義表實體類

我們首先定義一個實體類,作爲操作數據的實體模型,我們以 XYCMS 内容相冊插件 (opens new window)的 Photo (opens new window)類作爲示例:

using Datory;
using Datory.Annotations;

namespace XYCMS.Photos.Models
{
    [DataTable("xycms_photos")]
    public class Photo : Entity
    {
        [DataColumn]
        public int SiteId { get; set; }

        [DataColumn]
        public int ChannelId { get; set; }

        [DataColumn]
        public int ContentId { get; set; }

        [DataColumn]
        public string SmallUrl { get; set; }

        [DataColumn]
        public string MiddleUrl { get; set; }

        [DataColumn]
        public string LargeUrl { get; set; }

        [DataColumn]
        public int Taxis { get; set; }

        [DataColumn(Length = 2000)]
        public string Description { get; set; }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

可以看到,Photo 實體類繼承了 Datory 框架的 Entity 類,然後(hòu)我們需要使用 [DataTable] 标識表名,同時(shí)使用 [DataColumn] 标識出字段。

繼承 Entity 類之後(hòu),實體類將(jiāng)自動擁有Id(自增長(cháng)Id字段)、Guid(全局唯一标識符)、ExtendValues(擴展字段)、CreatedDate(數據創建時(shí)間)、LastModifiedDate(數據修改時(shí)間)這(zhè)五個字段,并且這(zhè)五個字段的值是由系統進(jìn)行維護的。

數據操作接口

定義好(hǎo)實體類後(hòu),我們需要定義數據操作接口,此接口定義了我們所需要用到的增删改查等操作,我們以 XYCMS 内容相冊插件 (opens new window)的 IPhotoRepository (opens new window)作爲示例:

using System.Collections.Generic;
using System.Threading.Tasks;
using XYCMS.Photos.Models;

namespace XYCMS.Photos.Abstractions
{
    public interface IPhotoRepository
    {
        Task<int> InsertAsync(Photo photoInfo);

        Task UpdateAsync(Photo photo);

        Task UpdateDescriptionAsync(int photoId, string description);

        Task UpdateTaxisAsync(List<int> photoIds);

        Task DeleteAsync(int photoId);

        Task DeleteAsync(int siteId, int channelId, int contentId);

        Task<Photo> GetFirstPhotoAsync(int siteId, int channelId, int contentId);

        Task<int> GetCountAsync(int siteId, int channelId, int contentId);

        Task<List<int>> GetPhotoContentIdListAsync(int siteId, int channelId, int contentId);

        Task<List<Photo>> GetPhotosAsync(int siteId, int channelId, int contentId);

        Task<Photo> GetAsync(int photoId);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

數據操作實現

定義好(hǎo)接口之後(hòu),我們需要編寫此接口的具體實現,我們以 XYCMS 内容相冊插件 (opens new window)的 PhotoRepository (opens new window)作爲示例:

using System.Collections.Generic;
using System.Threading.Tasks;
using Datory;
using XYCMS.Photos.Abstractions;
using XYCMS.Photos.Models;
using XYCMS.Services;

namespace XYCMS.Photos.Core
{
    public class PhotoRepository : IPhotoRepository
    {
        private readonly Repository<Photo> _repository;

        public PhotoRepository(ISettingsManager settingsManager)
        {
            _repository = new Repository<Photo>(settingsManager.Database);
        }

        public async Task<int> InsertAsync(Photo photoInfo)
        {
            var maxTaxis = await GetMaxTaxisAsync(photoInfo.SiteId, photoInfo.ChannelId, photoInfo.ContentId);
            photoInfo.Taxis = maxTaxis + 1;
            photoInfo.Id = await _repository.InsertAsync(photoInfo);

            return photoInfo.Id;
        }

        public async Task UpdateAsync(Photo photo)
        {
            await _repository.UpdateAsync(photo);
        }

        public async Task UpdateDescriptionAsync(int photoId, string description)
        {
            await _repository.UpdateAsync(Q
                .Set(nameof(Photo.Description), description)
                .Where(nameof(Photo.Id), photoId)
            );
        }

        public async Task UpdateTaxisAsync(List<int> photoIds)
        {
            var taxis = 1;
            foreach (var photoId in photoIds)
            {
                await SetTaxisAsync(photoId, taxis);
                taxis++;
            }
        }

        public async Task DeleteAsync(int photoId)
        {
            await _repository.DeleteAsync(photoId);
        }

        public async Task DeleteAsync(int siteId, int channelId, int contentId)
        {
            await _repository.DeleteAsync(Q
                .Where(nameof(Photo.SiteId), siteId)
                .Where(nameof(Photo.ChannelId), channelId)
                .Where(nameof(Photo.ContentId), contentId)
            );
        }

        public async Task<Photo> GetFirstPhotoAsync(int siteId, int channelId, int contentId)
        {
            return await _repository.GetAsync(Q
                .Where(nameof(Photo.SiteId), siteId)
                .Where(nameof(Photo.ChannelId), channelId)
                .Where(nameof(Photo.ContentId), contentId)
                .OrderBy(nameof(Photo.Taxis))
            );
        }

        public async Task<int> GetCountAsync(int siteId, int channelId, int contentId)
        {
            return await _repository.CountAsync(Q
                .Where(nameof(Photo.SiteId), siteId)
                .Where(nameof(Photo.ChannelId), channelId)
                .Where(nameof(Photo.ContentId), contentId)
            );
        }

        public async Task<List<int>> GetPhotoContentIdListAsync(int siteId, int channelId, int contentId)
        {
            return await _repository.GetAllAsync<int>(Q
                .Select(nameof(Photo.Id))
                .Where(nameof(Photo.SiteId), siteId)
                .Where(nameof(Photo.ChannelId), channelId)
                .Where(nameof(Photo.ContentId), contentId)
                .OrderBy(nameof(Photo.Taxis))
            );
        }

        public async Task<List<Photo>> GetPhotosAsync(int siteId, int channelId, int contentId)
        {
            return await _repository.GetAllAsync(Q
                .Where(nameof(Photo.SiteId), siteId)
                .Where(nameof(Photo.ChannelId), channelId)
                .Where(nameof(Photo.ContentId), contentId)
                .OrderBy(nameof(Photo.Taxis))
            );
        }

        public async Task<Photo> GetAsync(int photoId)
        {
            return await _repository.GetAsync(photoId);
        }

        private async Task SetTaxisAsync(int id, int taxis)
        {
            await _repository.UpdateAsync(Q
                .Set(nameof(Photo.Taxis), taxis)
                .Where(nameof(Photo.Id), id)
            );
        }

        private async Task<int> GetMaxTaxisAsync(int siteId, int channelId, int contentId)
        {
            return await _repository.MaxAsync(nameof(Photo.Taxis), Q
                       .Where(nameof(Photo.SiteId), siteId)
                       .Where(nameof(Photo.ChannelId), channelId)
                       .Where(nameof(Photo.ContentId), contentId)
                   ) ?? 0;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

可以看到,PhotoRepository 類繼承了 IPhotoRepository 接口,同時(shí)在構造函數中注入了 XYCMS 系統的 ISettingsManager 接口,ISettingsManager 接口中將(jiāng)包含數據庫連接信息。

由于我們示例使用的是 Datory 框架,所以我們通過(guò) Datory 框架的 Repository<Photo> 泛型類封裝了 Photo 實體類的所有操作,我們在 PhotoRepository 類中所要做的隻是調用數據庫操作接口。

注入數據操作接口

最後(hòu),我們需要將(jiāng) IPhotoRepository 接口注入到系統中,使 IPhotoRepository 在需要的時(shí)候可以直接使用。

我們以 XYCMS 内容相冊插件 (opens new window)的 Startup (opens new window)類作爲示例:

using Microsoft.Extensions.DependencyInjection;
using XYCMS.Photos.Abstractions;
using XYCMS.Photos.Core;
using XYCMS.Plugins;

namespace XYCMS.Photos
{
    public class Startup : IPluginConfigureServices
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IPhotoRepository, PhotoRepository>();
            services.AddScoped<IPhotoManager, PhotoManager>();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

可以看到在插件初始化方法 ConfigureServices 的第一行注入了 IPhotoRepository 接口。

使用數據操作接口

現在,我們可以直接使用數據操作接口對(duì)數據進(jìn)行操作了。

我們以 XYCMS 内容相冊插件 (opens new window)的 PhotosController (opens new window)類作爲示例:

public partial class PhotosController : ControllerBase
{
    private const string Route = "photos/photos";
    private const string RouteUpload = "photos/photos/actions/upload";

    private readonly IAuthManager _authManager;
    private readonly IPathManager _pathManager;
    private readonly ISiteRepository _siteRepository;
    private readonly IPhotoManager _photoManager;
    private readonly IPhotoRepository _photoRepository;

    public PhotosController(IAuthManager authManager, IPathManager pathManager, ISiteRepository siteRepository, IPhotoManager photoManager, IPhotoRepository photoRepository)
    {
        _authManager = authManager;
        _pathManager = pathManager;
        _siteRepository = siteRepository;
        _photoManager = photoManager;
        _photoRepository = photoRepository;
    }

    ......

    [HttpGet, Route(Route)]
    public async Task<ActionResult<GetResult>> Get([FromQuery] ContentRequest request)
    {
        if (!await _authManager.HasContentPermissionsAsync(request.SiteId, request.ChannelId, PhotoManager.PermissionsContent))
            return Unauthorized();

        var site = await _siteRepository.GetAsync(request.SiteId);

        var photos = await _photoRepository.GetPhotosAsync(request.SiteId, request.ChannelId, request.ContentId);

        foreach (var photo in photos)
        {
            photo.LargeUrl = await _pathManager.ParseSiteUrlAsync(site, photo.LargeUrl, true);
            photo.MiddleUrl = await _pathManager.ParseSiteUrlAsync(site, photo.MiddleUrl, true);
            photo.SmallUrl = await _pathManager.ParseSiteUrlAsync(site, photo.SmallUrl, true);
        }

        return new GetResult
        {
            Photos = photos
        };
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

可以看到,我們在 Get 方法中調用了 IPhotoRepository 的 GetPhotosAsync 方法,以獲取圖片實體列表。