这一篇我们来一起为账本功能编写代码。账本功能的代码很简单,就是一些简单的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
状态码和异常提示。
三、总结
这篇文章带领大家一起实现了账本的删除功能,代码比较简单,只要充分了解需求后就能编写出来。账本剩余的功能代码,大家自己动手实现。