數據庫操作

插件通常擁有自己的(de)表結構,用于(yú)存儲插件相關的(de)數據,本節将說(shuō)明如何創建數據庫表,以(yǐ)及如何在(zài)代碼中對數據進行操作。

配置數據庫表結構

爲(wéi / wèi)了(le/liǎo)讓 XYCMS 系統自動創建我們所需要(yào / yāo)的(de)數據庫表,我們需要(yào / yāo)在(zài)插件的(de) package.json 文件中對數據庫表結構進行配置。

爲(wéi / wèi)了(le/liǎo)讓 XYCMS 系統自動創建我們所需要(yào / yāo)的(de)數據庫表,我們需要(yào / yāo)對插件 package.json 配置文件的(de) extensions -> tables 節點進行設置,在(zài)此,我們以(yǐ)XYCMS 内容相冊插件 (opens new window)的(de)package.json (opens new window)作爲(wéi / wèi)示例說(shuō)明表結構的(de)配置:

"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:表名稱,可以(yǐ)任意取值,需要(yào / yāo)确保唯一(yī / yì /yí)性,插件啓動時(shí) XYCMS 系統将爲(wéi / wèi)插件創建對應的(de)數據庫表。
  • columns:表字段集合。
  • attributeName:字段名稱。
  • dataType:字段類型。
  • dataLength:字段長度,如果是(shì)字符串類型字段,不(bù)設置的(de)話默認長度爲(wéi / wèi)500。

系統支持的(de)字段類型有一(yī / yì /yí)下幾種:

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

由于(yú) XYCMS 系統支持多種數據庫,不(bù)同數據庫類型的(de)實際字段類型名稱與 dataType 的(de)名稱可能有區别。

如果插件版本更新的(de)同時(shí)更新了(le/liǎo)數據庫表結構,XYCMS 系統将負責把更新的(de)表結構同步到(dào)實際數據庫中。

需要(yào / yāo)注意的(de)是(shì),XYCMS 系統在(zài)創建和(hé / huò)同步表結構的(de)同時(shí),将默認創建以(yǐ)下字段:

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

數據操作框架

XYCMS 系統将在(zài)插件第一(yī / yì /yí)次加載的(de)時(shí)候核對表字段并創建或者同步表結構到(dào)數據庫,我們接下來(lái)需要(yào / yāo)做的(de)就(jiù)是(shì)編寫代碼操作數據庫,對數據進行增删改查的(de)操作。

實現數據的(de)操作方式有很多,沒有必須遵守的(de)規則,XYCMS 系統使用的(de)數據操作類庫是(shì)我們自行開發的(de) Datory 框架,Datory 框架是(shì)我們在(zài)流行的(de) Dapper 框架基礎上(shàng)封裝了(le/liǎo)一(yī / yì /yí)些操作接口而(ér)成,如果熟悉 Dapper 框架會發現操作接口非常類似。

我們以(yǐ) XYCMS 自帶的(de) Datory 框架作爲(wéi / wèi)演示并不(bù)是(shì)推薦使用此框架,大(dà)家在(zài)實際開發過程中應該盡量使用自己熟悉的(de)數據庫操作類庫進行數據操作。

定義表實體類

我們首先定義一(yī / yì /yí)個(gè)實體類,作爲(wéi / wèi)操作數據的(de)實體模型,我們以(yǐ) XYCMS 内容相冊插件 (opens new window)的(de) Photo (opens new window)類作爲(wéi / wèi)示例:

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

可以(yǐ)看到(dào),Photo 實體類繼承了(le/liǎo) Datory 框架的(de) Entity 類,然後我們需要(yào / yāo)使用 [DataTable] 标識表名,同時(shí)使用 [DataColumn] 标識出(chū)字段。

繼承 Entity 類之(zhī)後,實體類将自動擁有Id(自增長Id字段)、Guid(全局唯一(yī / yì /yí)标識符)、ExtendValues(擴展字段)、CreatedDate(數據創建時(shí)間)、LastModifiedDate(數據修改時(shí)間)這(zhè)五個(gè)字段,并且這(zhè)五個(gè)字段的(de)值是(shì)由系統進行維護的(de)。

數據操作接口

定義好實體類後,我們需要(yào / yāo)定義數據操作接口,此接口定義了(le/liǎo)我們所需要(yào / yāo)用到(dào)的(de)增删改查等操作,我們以(yǐ) XYCMS 内容相冊插件 (opens new window)的(de) IPhotoRepository (opens new window)作爲(wéi / wèi)示例:

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

數據操作實現

定義好接口之(zhī)後,我們需要(yào / yāo)編寫此接口的(de)具體實現,我們以(yǐ) XYCMS 内容相冊插件 (opens new window)的(de) PhotoRepository (opens new window)作爲(wéi / wèi)示例:

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

可以(yǐ)看到(dào),PhotoRepository 類繼承了(le/liǎo) IPhotoRepository 接口,同時(shí)在(zài)構造函數中注入了(le/liǎo) XYCMS 系統的(de) ISettingsManager 接口,ISettingsManager 接口中将包含數據庫連接信息。

由于(yú)我們示例使用的(de)是(shì) Datory 框架,所以(yǐ)我們通過 Datory 框架的(de) Repository<Photo> 泛型類封裝了(le/liǎo) Photo 實體類的(de)所有操作,我們在(zài) PhotoRepository 類中所要(yào / yāo)做的(de)隻是(shì)調用數據庫操作接口。

注入數據操作接口

最後,我們需要(yào / yāo)将 IPhotoRepository 接口注入到(dào)系統中,使 IPhotoRepository 在(zài)需要(yào / yāo)的(de)時(shí)候可以(yǐ)直接使用。

我們以(yǐ) XYCMS 内容相冊插件 (opens new window)的(de) Startup (opens new window)類作爲(wéi / wèi)示例:

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

可以(yǐ)看到(dào)在(zài)插件初始化方法 ConfigureServices 的(de)第一(yī / yì /yí)行注入了(le/liǎo) IPhotoRepository 接口。

使用數據操作接口

現在(zài),我們可以(yǐ)直接使用數據操作接口對數據進行操作了(le/liǎo)。

我們以(yǐ) XYCMS 内容相冊插件 (opens new window)的(de) PhotosController (opens new window)類作爲(wéi / wèi)示例:

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

可以(yǐ)看到(dào),我們在(zài) Get 方法中調用了(le/liǎo) IPhotoRepository 的(de) GetPhotosAsync 方法,以(yǐ)獲取圖片實體列表。