18. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--账本
这一篇我们来一起为账本功能编写代码。账本功能的代码很简单,就是一些简单的CURD操作。
一、需求
我们先来看一下需求:
编号 | 需求 | 说明 |
---|---|---|
1 | 新增账本 | 1. 账本名称不能和用户已有的账本名称重复 |
2 | 删除账本 | 1. 存在收支记录的账本不能删除 |
3 | 修改账本 | 1. 修改的账本名称不能和用户已有的账本名称重复 |
4 | 查询站本 | 1. 支持按照账本名称、账本备注进行分页查询; 2. 支持根据账本id查询 |
根据上面的需求我们可以分析出5个接口和数据库表映射类所需的字段。5个接口就不多说了,需求上写的清清楚楚,这一小节我们重点看一下数据库表映射类所需的字段。
从需求1和3中分析出我们需要一个账本类来作为数据库表映射类,并且这个类需要有账本名称,以及账本所属用户的Id,从需求4中得知账本类还需要有备注,同时还需要另一个数据库表映射类收支记录类。
需求我们分析完了,现在我们就一起来编写实现账本功能的代码吧。和前面的文章一样,在这里我只带领大家实现删除功能,剩余的功能大家自己动手实现。
二、功能编写
2.1 创建数据库映射类
这里我们需要两个数据库映射类:账本类(AccountBook
)、收支记录类(IncomeExpenditureRecord
),IncomeExpenditureRecord
类我们会在下一篇文章中实现,因此在这篇文章中我们只需要把它创建出来并在里面加上AccountBook
类的导航属性即可。代码如下:
- AccountBook
using System.ComponentModel.DataAnnotations.Schema; using Microsoft.Build.Framework; using SporeAccounting.BaseModels; namespace SporeAccounting.Models; /// <summary> /// 账簿 /// </summary> [Table(name: "AccountBook")] public class AccountBook : BaseModel { /// <summary> /// 账簿名称 /// </summary> [Column(TypeName = "nvarchar(20)")] [Required] public string Name { get; set; } /// <summary> /// 备注 /// </summary> [Column(TypeName = "nvarchar(100)")] public string? Remarks { get; set; } /// <summary> /// 用户Id /// </summary> [Column(TypeName = "nvarchar(36)")] [ForeignKey("FK_AccountBook_SysUser")] [Required] public string UserId { get; set; } /// <summary> /// 导航属性 /// </summary> public SysUser User { get; set; } /// <summary> /// 导航属性 /// </summary> public ICollection<IncomeExpenditureRecord> IncomeExpenditureRecords { get; set; } = new List<IncomeExpenditureRecord>(); }
- IncomeExpenditureRecord
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using SporeAccounting.BaseModels; namespace SporeAccounting.Models; /// <summary> /// 收支记录表 /// </summary> [Table("IncomeExpenditureRecord")] public class IncomeExpenditureRecord : BaseModel { /// <summary> /// 导航属性 /// </summary> public AccountBook AccountBook { get; set; } }
Tip:不明白什么是导航属性的,请先学习我写的专栏《轻松学EntityFramework Core
》
2.2 实现需求
- 创建AccountBook表数据库操作服务
我们新建** 账本服务接口**IAccountBookServer
以及他的实现类AccountBookImp
,并在添加Delete
方法、IsExistById
方法和判断账本是否存在收支记录的方法IsExistIncomeExpenditureRecord
。
在///IAccountBookServer using SporeAccounting.Models; namespace SporeAccounting.Server.Interface; /// <summary> /// 账本服务接口 /// </summary> public interface IAccountBookServer { /// <summary> /// 删除账本 /// </summary> /// <param name="accountBookId"></param> void Delete(string accountBookId); /// <summary> /// 账本是否存在 /// </summary> /// <param name="accountBookId"></param> /// <returns></returns> bool IsExistById(string accountBookId); /// <summary> /// 账本是否存在收支记录 /// </summary> /// <param name="accountBookId"></param> /// <returns></returns> bool IsExistIncomeExpenditureRecord(string accountBookId); } ///AccountBookImp using SporeAccounting.Models; using SporeAccounting.Server.Interface; namespace SporeAccounting.Server; /// <summary> /// 账本服务实现 /// </summary> public class AccountBookImp : IAccountBookServer { /// <summary> /// 数据库上下文 /// </summary> private readonly SporeAccountingDBContext _sporeAccountingDbContext; /// <summary> /// 构造函数 /// </summary> /// <param name="sporeAccountingDbContext"></param> public AccountBookImp(SporeAccountingDBContext sporeAccountingDbContext) { _sporeAccountingDbContext = sporeAccountingDbContext; } /// <summary> /// 删除账本 /// </summary> /// <param name="accountBookId"></param> public void Delete(string accountBookId) { try { AccountBook accountBook = _sporeAccountingDbContext.AccountBooks .FirstOrDefault(p => p.Id == accountBookId)!; _sporeAccountingDbContext.AccountBooks.Remove(accountBook); _sporeAccountingDbContext.SaveChanges(); } catch (Exception e) { throw; } } /// <summary> /// 账本是否存在 /// </summary> /// <param name="accountBookId"></param> /// <returns></returns> public bool IsExistById(string accountBookId) { try { return _sporeAccountingDbContext.AccountBooks .Any(p => p.Id == accountBookId); } catch (Exception e) { throw; } } /// <summary> /// 账本是否存在收支记录 /// </summary> /// <param name="accountBookId"></param> /// <returns></returns> public bool IsExistIncomeExpenditureRecord(string accountBookId) { try { return _sporeAccountingDbContext.AccountBooks .Include(p => p.IncomeExpenditureRecords) .Any(p => p.Id == accountBookId); // 也可以这么查询 // return _sporeAccountingDbContext.IncomeExpenditureRecords // .Any(p => p.Id == accountBookId); } catch (Exception e) { throw; } } }
IsExistIncomeExpenditureRecord
方法中的注释提供了一个替代实现,直接从IncomeExpenditureRecords
表进行查询,跳过了通过AccountBooks
的关联导航加载。这种方式可能效率更高,具体选择依赖业务需求。
3.2 Controller 实现
新建AccountBookController
控制器,并添加Delete
Action,代码如下:
using System.Net;
using AutoMapper;
using SporeAccounting.BaseModels.ViewModel.Response;
using Microsoft.AspNetCore.Mvc;
using SporeAccounting.BaseModels;
using SporeAccounting.Models;
using SporeAccounting.Models.ViewModels;
using SporeAccounting.Server.Interface;
namespace SporeAccounting.Controllers
{
/// <summary>
/// 账本控制器
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class AccountBookController : BaseController
{
/// <summary>
/// 账本服务
/// </summary>
private readonly IAccountBookServer _accountBookServer;
/// <summary>
/// 映射
/// </summary>
private readonly IMapper _mapper;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="accountBookServer"></param>
/// <param name="mapper"></param>
public AccountBookController(IAccountBookServer accountBookServer, IMapper mapper)
{
_accountBookServer = accountBookServer;
_mapper = mapper;
}
/// <summary>
/// 删除账本
/// </summary>
/// <param name="accountBookId"></param>
/// <returns></returns>
[HttpDelete]
[Route("Delete/{accountBookId}")]
public ActionResult<ResponseData<bool>> Delete([FromRoute] string accountBookId)
{
try
{
//是否存在账本
bool isExist = _accountBookServer.IsExistById(accountBookId);
if (!isExist)
{
return Ok(new ResponseData<bool>(HttpStatusCode.BadRequest, errorMessage: "账本不存在"));
}
//是否存在收支记录
bool isExistIncomeExpenditureRecord = _accountBookServer.IsExistIncomeExpenditureRecord(accountBookId);
if (isExistIncomeExpenditureRecord)
{
return Ok(new ResponseData<bool>(HttpStatusCode.BadRequest, errorMessage: "账本存在收支记录,不能删除"));
}
_accountBookServer.Delete(accountBookId);
return Ok(new ResponseData<bool>(HttpStatusCode.OK, data: true));
}
catch (Exception e)
{
return Ok(new ResponseData<bool>(HttpStatusCode.InternalServerError, errorMessage: "服务端异常"));
}
}
}
}
这段代码定义了一个名为 AccountBookController
的控制器,用于管理账本操作。该控制器继承自 BaseController
,并通过依赖注入方式初始化了两个服务:账本服务 _accountBookServer
和映射服务 _mapper
。控制器的路由前缀设置为 api/AccountBook
,且标记为 API 控制器,确保符合 RESTful API 的约定。
Delete
方法实现了通过账本 ID 删除账本的逻辑,绑定了 DELETE
请求,并在路由中包含动态参数 accountBookId
。方法首先调用账本服务检查指定 ID 的账本是否存在,若不存在则返回带有 400 Bad Request
状态码的响应数据,并附上错误信息。接着检查该账本是否存在收支记录,若存在也返回类似的错误响应,提示无法删除。若上述条件均不满足,则调用账本服务的 Delete
方法删除该账本,并返回成功响应,包含 200 OK
状态码和 true
的数据表示操作成功。在操作过程中若出现异常,捕获后返回服务器错误响应,附带 500 Internal Server Error
状态码和异常提示。
三、总结
这篇文章带领大家一起实现了账本的删除功能,代码比较简单,只要充分了解需求后就能编写出来。账本剩余的功能代码,大家自己动手实现。