1. 程式人生 > >基於 abp vNext 和 .NET Core 開發部落格專案 - 自定義倉儲之增刪改查

基於 abp vNext 和 .NET Core 開發部落格專案 - 自定義倉儲之增刪改查

上一篇文章(https://www.cnblogs.com/meowv/p/12913676.html)我們用Code-First的方式建立了部落格所需的實體類,生成了資料庫表,完成了對EF Core的封裝。 本篇說一下自定義倉儲的實現方式,其實在abp框架中已經預設給我們實現了預設的通用(泛型)倉儲,`IRepository`,有著標準的CRUD操作,可以看:https://docs.abp.io/zh-Hans/abp/latest/Repositories 學習更多。 之所以實現自定義倉儲,是因為abp沒有給我們實現批量插入、更新的方法,這個是需要自己去擴充套件的。 既然是自定義倉儲,那麼就有了很高的自由度,我們可以任意發揮,可以接入第三方批量處理資料的庫,可以接入Dapper操作等等,在這裡貼一下微軟官方推薦的一些EF Core的工具和擴充套件:https://docs.microsoft.com/zh-cn/ef/core/extensions/ 。 ## 自定義倉儲 在`.Domain`領域層中建立倉儲介面,`IPostRepository`、`ICategoryRepository`、`ITagRepository`、`IPostTagRepository`、`IFriendLinkRepository`,這裡直接全部繼承 `IRepository` 以使用已有的通用倉儲功能。 可以轉到`IRepository`介面定義看一下 ![1](https://img2020.cnblogs.com/blog/891843/202005/891843-20200519200528695-235513765.png) 看看abp對於倉儲的介紹,如下: `IRepository` 介面擴充套件了標準 `IQueryable` 你可以使用標準LINQ方法自由查詢。但是,某些ORM提供程式或資料庫系統可能不支援IQueryable介面。 ABP提供了 `IBasicRepository` 和 `IBasicRepository` 介面來支援這樣的場景。 你可以擴充套件這些介面(並可選擇性地從BasicRepositoryBase派生)為你的實體建立自定義儲存庫。 依賴於 `IBasicRepository` 而不是依賴 `IRepository` 有一個優點, 即使它們不支援 `IQueryable` 也可以使用所有的資料來源, 但主要的供應商, 像 Entity Framework, NHibernate 或 MongoDb 已經支援了 `IQueryable`。 因此, 使用 `IRepository` 是典型應用程式的 建議方法。但是可重用的模組開發人員可能會考慮使用 `IBasicRepository` 來支援廣泛的資料來源。 對於想要使用只讀倉儲提供了`IReadOnlyRepository` 與 `IReadOnlyBasicRepository`介面。 倉儲介面類如下: ```CSharp //IPostRepository.cs using Volo.Abp.Domain.Repositories; namespace Meowv.Blog.Domain.Blog.Repositories { /// /// IPostRepository ///
public interface IPostRepository : IRepository { } } ``` ```CSharp //ICategoryRepository.cs using Volo.Abp.Domain.Repositories; namespace Meowv.Blog.Domain.Blog.Repositories { /// /// ICategoryRepository /// public interface ICategoryRepository : IRepository { } } ``` ```CSharp //ITagRepository.cs using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; namespace Meowv.Blog.Domain.Blog.Repositories { /// /// ITagRepository ///
public interface ITagRepository : IRepository { /// /// 批量插入 /// /// /// Task BulkInsertAsync(IEnumerable tags); } } ``` ```CSharp //IPostTagRepository.cs using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; namespace Meowv.Blog.Domain.Blog.Repositories { /// /// IPostTagRepository ///
public interface IPostTagRepository : IRepository { /// /// 批量插入 /// /// /// Task BulkInsertAsync(IEnumerable postTags); } } ``` ```CSharp //IFriendLinkRepository.cs using Volo.Abp.Domain.Repositories; namespace Meowv.Blog.Domain.Blog.Repositories { /// /// IFriendLinkRepository /// public interface IFriendLinkRepository : IRepository { } } ``` 在`ITagRepository`和`IPostTagRepository`倉儲介面中,我們添加了批量插入的方法。相對於的在我們的`.EntityFrameworkCore`層實現這些介面。 建立Repositories/Blog 資料夾,新增實現類:`PostRepository`、`CategoryRepository`、`TagRepository`、`PostTagRepository`、`FriendLinkRepository`。 不知道大家發現沒有,我們的倉儲介面以及實現,都是以`Repository`結尾的,這和我們的`.Application`應用服務層都以`Service`結尾是一個道理。 在自定義倉儲的實現中,我們可以使用任意你想使用的資料訪問工具,我們這裡還是繼續用`Entity Framework Core`,需要繼承`EfCoreRepository`,和我們的倉儲介面`IXxxRepository`。 ![2](https://img2020.cnblogs.com/blog/891843/202005/891843-20200519203537559-1603736992.png) `EfCoreRepository`預設實現了許多預設的方法,然後就可以直接使用 `DbContext` 來執行操作了。 倉儲介面實現類如下: ```CSharp //PostRepository.cs using Meowv.Blog.Domain.Blog; using Meowv.Blog.Domain.Blog.Repositories; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; namespace Meowv.Blog.EntityFrameworkCore.Repositories.Blog { /// /// PostRepository /// public class PostRepository : EfCoreRepository, IPostRepository { public PostRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } } } ``` ```CSharp //CategoryRepository.cs using Meowv.Blog.Domain.Blog; using Meowv.Blog.Domain.Blog.Repositories; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; namespace Meowv.Blog.EntityFrameworkCore.Repositories.Blog { /// /// CategoryRepository /// public class CategoryRepository : EfCoreRepository, ICategoryRepository { public CategoryRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } } } ``` ```CSharp //TagRepository.cs using Meowv.Blog.Domain.Blog; using Meowv.Blog.Domain.Blog.Repositories; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; namespace Meowv.Blog.EntityFrameworkCore.Repositories.Blog { /// /// TagRepository /// public class TagRepository : EfCoreRepository, ITagRepository { public TagRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } /// /// 批量插入 /// /// /// public async Task BulkInsertAsync(IEnumerable tags) { await DbContext.Set().AddRangeAsync(tags); await DbContext.SaveChangesAsync(); } } } ``` ```CSharp //PostTagRepository.cs using Meowv.Blog.Domain.Blog; using Meowv.Blog.Domain.Blog.Repositories; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; namespace Meowv.Blog.EntityFrameworkCore.Repositories.Blog { /// /// PostTagRepository /// public class PostTagRepository : EfCoreRepository, IPostTagRepository { public PostTagRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } /// /// 批量插入 /// /// /// public async Task BulkInsertAsync(IEnumerable postTags) { await DbContext.Set().AddRangeAsync(postTags); await DbContext.SaveChangesAsync(); } } } ``` ```CSharp //FriendLinkRepository.cs using Meowv.Blog.Domain.Blog; using Meowv.Blog.Domain.Blog.Repositories; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; namespace Meowv.Blog.EntityFrameworkCore.Repositories.Blog { /// /// PostTagRepository /// public class FriendLinkRepository : EfCoreRepository, IFriendLinkRepository { public FriendLinkRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } } } ``` 在`TagRepository`和`PostTagRepository`倉儲介面的實現中,因為資料量不大,可以直接用了EF Core自帶的`AddRangeAsync`批量儲存資料。 到這裡,關於部落格的自定義倉儲便完成了,此時專案層級目錄圖,如下: ![3](https://img2020.cnblogs.com/blog/891843/202005/891843-20200519204147807-1375027236.png) ## 增刪改查 接下來在就可以在`.Application`服務層愉快的玩耍了,寫服務之前,我們要分析我們的專案,要有哪些功能業務。由於是部落格專案,無非就是一些增刪改查。今天先不寫部落格業務,先完成對資料庫文章表`meowv_posts`的一個簡單CRUD。 在`.Application`層新建Blog資料夾,新增一個`IBlogService.cs`部落格介面服務類,分別新增增刪改查四個方法。 這時就要用到我們的資料傳輸物件(DTO)了,簡單理解,DTO就是從我們的領域模型中抽離出來的物件,它是很純粹的只包含我們拿到的資料,不參雜任何行為邏輯。 在`.Application.Contracts`層新建Blog資料夾,同時新建一個`PostDto.cs`類,與`.Domain`層中的`Post.cs`與之對應,他們很相似,但是不一樣。 於是`IBlogService.cs`介面服務類的CRUD為: ```CSharp //IBlogService.cs using Meowv.Blog.Application.Contracts.Blog; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog { public interface IBlogService { Task InsertPostAsync(PostDto dto); Task DeletePostAsync(int id); Task UpdatePostAsync(int id, PostDto dto); Task GetPostAsync(int id); } } ``` 介面寫好了,少不了實現方式,直接在Blog資料夾新建Impl資料夾,用來存放我們的介面實現類`BlogService.cs`,注意:都是以`Service`結尾的噢~ 實現服務介面除了要繼承我們的`IBlogService`外,不要忘了還需依賴我們的`ServiceBase`類。由於我們之前直接接入了Autofac,可以直接使用建構函式依賴注入的方式。 ```CSharp //BlogService.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.Domain.Blog.Repositories; using System; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog.Impl { public class BlogService : ServiceBase, IBlogService { private readonly IPostRepository _postRepository; public BlogService(IPostRepository postRepository) { _postRepository = postRepository; } ... } } ``` 現在就可以實現我們寫的`IBlogService`介面了。 先寫新增,這裡實現方式全採用非同步的方法,先構建一個`Post`實體物件,具體內容引數都從`PostDto`中獲取,由於主鍵之前設定了自增,這裡就不用管它了。然後呼叫 `await _postRepository.InsertAsync(entity);`,正好它返回了一個建立成功的`Post`物件,那麼我們就可以判斷物件是否為空,從而確定文章是否新增成功。 程式碼如下: ```CSharp ... public async Task InsertPostAsync(PostDto dto) { var entity = new Post { Title = dto.Title, Author = dto.Author, Url = dto.Url, Html = dto.Html, Markdown = dto.Markdown, CategoryId = dto.CategoryId, CreationTime = dto.CreationTime }; var post = await _postRepository.InsertAsync(entity); return post != null; } ... ``` 然後在`.HttpApi`層和之前新增`HelloWorldController`一樣,新增`BlogController`。呼叫寫的`InsertPostAsync`方法,如下: ```CSharp //BlogController.cs using Meowv.Blog.Application.Blog; using Meowv.Blog.Application.Contracts.Blog; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Volo.Abp.AspNetCore.Mvc; namespace Meowv.Blog.HttpApi.Controllers { [ApiController] [Route("[controller]")] public class BlogController : AbpController { private readonly IBlogService _blogService; public BlogController(IBlogService blogService) { _blogService = blogService; } /// /// 新增部落格 /// /// /// [HttpPost] public async Task InsertPostAsync([FromBody] PostDto dto) { return await _blogService.InsertPostAsync(dto); } } } ``` 新增部落格操作,我們將其設定為`[HttpPost]`方式來提交,因為現在開發介面api,都要遵循RESTful方式,所以就不用給他指定路由了,`[FromBody]`的意思是在請求正文中以JSON的方式來提交引數。 完成上述操作,開啟我們的Swagger文件看看, .../swagger/index.html ,已經出現我們的介面了。 ![4](https://img2020.cnblogs.com/blog/891843/202005/891843-20200519213650913-1871748706.png) 隨手就試一下這個介面,能否成功建立文章。 ![5](https://img2020.cnblogs.com/blog/891843/202005/891843-20200519214442360-1594124256.png) 可以看到資料庫已經躺著我們剛剛新增資料內容。 將剩下的三個介面一一實現,相信大家肯定都知道怎麼寫了。就不逐一嘮叨了,程式碼如下: ```CSharp ... public async Task DeletePostAsync(int id) { await _postRepository.DeleteAsync(id); return true; } public async Task UpdatePostAsync(int id, PostDto dto) { var post = await _postRepository.GetAsync(id); post.Title = dto.Title; post.Author = dto.Author; post.Url = dto.Url; post.Html = dto.Html; post.Markdown = dto.Markdown; post.CategoryId = dto.CategoryId; post.CreationTime = dto.CreationTime; await _postRepository.UpdateAsync(post); return true; } public async Task GetPostAsync(int id) { var post = await _postRepository.GetAsync(id); return new PostDto { Title = post.Title, Author = post.Author, Url = post.Url, Html = post.Html, Markdown = post.Markdown, CategoryId = post.CategoryId, CreationTime = post.CreationTime }; } ... ``` 在這裡先暫時不做引數校驗,咱們預設都是正常操作,如果執行操作成功,直接返回true。大家會發現,當我們使用了DTO後,寫了大量物件的轉換,在這裡暫不做優化,將在後續業務開始後使用`AutoMapper`處理物件對映。如果大家感興趣可以自己先試一下。 在Controller中呼叫,程式碼如下: ``` ... /// /// 刪除部落格 /// /// /// [HttpDelete] public async Task DeletePostAsync([Required] int id) { return await _blogService.DeletePostAsync(id); } /// /// 更新部落格 /// /// /// /// [HttpPut] public async Task UpdatePostAsync([Required] int id, [FromBody] PostDto dto) { return await _blogService.UpdatePostAsync(id, dto); } /// /// 查詢部落格 /// /// /// [HttpGet] public async Task GetPostAsync([Required] int id) { return await _blogService.GetPostAsync(id); } ... ``` `DeletePostAsync`:指定了請求方式`[HttpDelete]`,引數id為必填項 `UpdatePostAsync`:指定了請求方式`[HttpPut]`,引數id為必填項並且為url的一部分,要更新的具體內容和新增部落格的方法`InsertPostAsync`的一樣的 `GetPostAsync`:指定了請求方式`[HttpGet]`,引數id為必填項 ok,開啟Swagger文件看看效果,並試試我們的介面是否好使吧,反正我試了是沒有問題的。 ![6](https://img2020.cnblogs.com/blog/891843/202005/891843-20200519220446147-402514670.png) 做到這一步的專案層級目錄如下: ![7](https://img2020.cnblogs.com/blog/891843/202005/891843-20200519220657414-1572584084.png) 本篇使用自定義倉儲的方式完成了對部落格(meowv_posts)的增刪改查,你學會了嗎?