24. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--预算扣除、退回、补充

embedded/2025/1/17 3:18:42/

这篇文章我们一起来编写目前为止最为复杂的功能:预算扣除、退回、补充。预算回退有三种情况:修改后的支出金额小于修改前的支出金额支出记录删除后记录类型从支出改为收入。预算补充的情况有两种:记录类型从收入改为支出修改后的支出金额大于修改前的支出金额

一、需求

根据前面所说的需求如下:

编号需求说明
1预算回退1. 修改后的支出金额小于修改前的支出金额;2. 支出记录删除后;3. 记录类型从支出改为收入
2预算补充1. 记录类型从收入改为支出;2. 修改后的支出金额大于修改前的支出金额
3预算扣除新增支出记录后扣除预算

二、功能编写

我们一起来实现如何实现预算退回与预算补充功能。这里要注意的是,我们将引入EF Core 的事务机制,如果你不知道EF Core的事务机制请先去学习专栏《轻松学EntityFramework Core

2.1 预算扣除

预算扣除功能最简单,我们只需要在新增支出记录的同时扣除对应的预算即可。我们修改IncomeExpenditureRecordImp类下的Add方法,修改后的代码如下:

/// <summary>
/// 新增收支记录
/// </summary>
/// <param name="incomeExpenditureRecord"></param>
public void Add(IncomeExpenditureRecord incomeExpenditureRecord)
{//开启事务using (var transaction = _sporeAccountingDbContext.Database.BeginTransaction()){try{// 查找记录范围内的预算var budget = _sporeAccountingDbContext.Budgets.FirstOrDefault(x => x.UserId == incomeExpenditureRecord.UserId&& x.StartTime <= incomeExpenditureRecord.RecordDate &&x.EndTime >= incomeExpenditureRecord.RecordDate&& x.IncomeExpenditureClassificationId ==incomeExpenditureRecord.IncomeExpenditureClassificationId);if (budget != null){// 查询分类var classification = _sporeAccountingDbContext.IncomeExpenditureClassifications.FirstOrDefault(x => x.Id == incomeExpenditureRecord.IncomeExpenditureClassificationId);if (classification.Type== IncomeExpenditureTypeEnmu.Income){budget.Remaining -= incomeExpenditureRecord.AfterAmount;}_sporeAccountingDbContext.Budgets.Update(budget);}_sporeAccountingDbContext.IncomeExpenditureRecords.Add(incomeExpenditureRecord);_sporeAccountingDbContext.SaveChanges();//提交事务transaction.Commit();}catch (Exception e){//回滚事务transaction.Rollback();throw;}}
}

Add 方法的核心部分包括事务处理、预算查找与更新、分类查询、记录添加和错误处理。首先,代码通过调用 _sporeAccountingDbContext.Database.BeginTransaction() 开启了一个数据库事务,以确保数据操作的原子性。如果操作成功,事务会提交;如果发生异常,事务将回滚,避免数据不一致。接着,代码从数据库中查找符合条件的预算记录。预算查找逻辑基于 UserId 和记录日期范围,同时匹配 IncomeExpenditureClassificationId。如果找到相关预算,代码会进一步查询该收支记录的分类信息。然后,根据分类的类型进行处理。如果分类类型为收入IncomeExpenditureTypeEnmu.Income,代码将从预算的剩余金额中扣除收支记录的金额AfterAmount。紧接着代码将新的收支记录添加到 IncomeExpenditureRecords 集合中,并调用 SaveChanges() 保存所有更改。最后,事务被提交。如果过程中出现任何异常,捕获到的异常会触发事务回滚,确保数据库保持一致性并避免部分操作被永久保存。回滚后,异常被重新抛出,以便调用者可以处理这个错误。通过这种方式,代码实现了对收支记录的安全、原子化操作,确保数据一致性和完整性。

2.2 预算回退与补充

这里我们要修改IncomeExpenditureRecordImp类中的两个方法:UpdateDeleteDelete方法很简单,我们直接将支出记录的金额再加回预算即可,代码如下:

/// <summary>
/// 删除收支记录
/// </summary>
/// <param name="incomeExpenditureRecordId"></param>
/// <returns></returns>
public void Delete(string incomeExpenditureRecordId)
{//开启事务using (var transaction = _sporeAccountingDbContext.Database.BeginTransaction()){try{var incomeExpenditureRecord = _sporeAccountingDbContext.IncomeExpenditureRecords.FirstOrDefault(x => x.Id == incomeExpenditureRecordId);if (incomeExpenditureRecord != null){// 查找记录范围内的预算var budget = _sporeAccountingDbContext.Budgets.FirstOrDefault(x => x.UserId == incomeExpenditureRecord.UserId&& x.StartTime <= incomeExpenditureRecord.RecordDate &&x.EndTime >= incomeExpenditureRecord.RecordDate&& x.IncomeExpenditureClassificationId == incomeExpenditureRecord.IncomeExpenditureClassificationId);if (budget != null){// 查询分类var classification = _sporeAccountingDbContext.IncomeExpenditureClassifications.FirstOrDefault(x => x.Id == incomeExpenditureRecord.IncomeExpenditureClassificationId);if (classification.Type== IncomeExpenditureTypeEnmu.Income){budget.Remaining += incomeExpenditureRecord.AfterAmount;}_sporeAccountingDbContext.Budgets.Update(budget);}_sporeAccountingDbContext.IncomeExpenditureRecords.Remove(incomeExpenditureRecord);_sporeAccountingDbContext.SaveChanges();//提交事务transaction.Commit();}}catch (Exception e){//回滚事务transaction.Rollback();throw;}}
}

Delete 方法首先开启一个数据库事务,确保在整个删除操作过程中,如果出现任何问题,可以回滚所有更改以保持数据一致性。然后,通过查询 IncomeExpenditureRecords 集合查找与给定 incomeExpenditureRecordId 匹配的收支记录。如果找到相应的记录,代码继续执行下一步。接下来,代码查找与该收支记录相关的预算信息。预算查询的条件包括用户 ID、记录日期范围,以及收支分类 ID。如果找到匹配的预算,代码会进一步查找该收支记录的分类信息,以确定该记录是否属于收入类型。如果分类类型是收入,代码将从预算的剩余金额中增加这笔收支记录的金额。这一步骤的目的是在删除收支记录时,恢复预算中的剩余金额,从而保持预算数据的准确性。
完成预算调整后,代码将目标收支记录从 IncomeExpenditureRecords 集合中移除,并调用 SaveChanges() 方法将所有更改保存到数据库。最后,事务被提交,确认所有操作成功完成。

Update 方法的修改需要将数据兼顾记录类分类的类型是否发生变更,代码如下:

/// <summary>
/// 修改收支记录
/// </summary>
/// <param name="incomeExpenditureRecord"></param>
/// <returns></returns>
public void Update(IncomeExpenditureRecord incomeExpenditureRecord)
{using (var transaction = _sporeAccountingDbContext.Database.BeginTransaction()){try{// 查询原记录var oldIncomeExpenditureRecord = _sporeAccountingDbContext.IncomeExpenditureRecords.FirstOrDefault(x => x.Id == incomeExpenditureRecord.Id);// 查找记录范围内的预算var budget = _sporeAccountingDbContext.Budgets.FirstOrDefault(x => x.UserId == incomeExpenditureRecord.UserId&& x.StartTime <= incomeExpenditureRecord.RecordDate &&x.EndTime >= incomeExpenditureRecord.RecordDate&& x.IncomeExpenditureClassificationId ==incomeExpenditureRecord.IncomeExpenditureClassificationId);if (budget != null){// 查询分类var classification = _sporeAccountingDbContext.IncomeExpenditureClassifications.FirstOrDefault(x => x.Id == incomeExpenditureRecord.IncomeExpenditureClassificationId);if (classification.Type== IncomeExpenditureTypeEnmu.Income){//如果是支出,需要减去原来的金额budget.Remaining = (budget.Amount - incomeExpenditureRecord.AfterAmount);}else{//如果是收入,需要加上原来的金额budget.Remaining = (budget.Amount + incomeExpenditureRecord.AfterAmount);}_sporeAccountingDbContext.Budgets.Update(budget);}oldIncomeExpenditureRecord.AfterAmount = incomeExpenditureRecord.AfterAmount;oldIncomeExpenditureRecord.BeforAmount = incomeExpenditureRecord.BeforAmount;oldIncomeExpenditureRecord.RecordDate = incomeExpenditureRecord.RecordDate;oldIncomeExpenditureRecord.Remark = incomeExpenditureRecord.Remark;oldIncomeExpenditureRecord.IncomeExpenditureClassificationId =incomeExpenditureRecord.IncomeExpenditureClassificationId;_sporeAccountingDbContext.IncomeExpenditureRecords.Update(oldIncomeExpenditureRecord);_sporeAccountingDbContext.SaveChanges();//提交事务transaction.Commit();}catch (Exception e){//回滚事务transaction.Rollback();throw;}}
}

Update 方法通过 IncomeExpenditureRecords 集合查找与提供的记录 ID 匹配的原始收支记录。找到后,代码继续查找与这条记录相关的预算信息。预算查询条件包括用户 ID、记录日期范围和收支分类 ID。如果找到匹配的预算,代码接着通过 IncomeExpenditureClassifications 集合查询记录的分类信息。根据分类类型(收入或支出),代码调整预算的剩余金额。如果分类是收入,代码从预算金额中减去更新后的金额;如果是支出,代码将金额加回预算。这确保了预算在更新记录后仍然准确。之后,代码更新原始收支记录的相关属性,包括金额、日期、备注和分类 ID。将修改后的记录通过 Update 方法标记为需要更新,并调用 SaveChanges() 保存所有更改。

三、总结

文章介绍了预算管理中复杂功能的实现,包括预算扣除、退回和补充。预算回退涉及三种情况:修改后的支出金额小于修改前的金额、删除支出记录、记录类型从支出改为收入;预算补充包括记录类型从收入改为支出以及修改后的支出金额大于修改前的金额。文章详细描述了如何在新增、删除和修改收支记录时,使用 EF Core 事务机制确保操作的原子性和数据一致性。具体实现方法包括调整预算金额、根据收支记录类型动态更新预算、处理异常并回滚事务,以保证数据库的完整性和正确性。文章通过实际代码示例清晰地阐述了这些操作的细节。
下一篇文章,我们将结合主币种设置和预算来实现预算金额的币种转换。


http://www.ppmy.cn/embedded/154545.html

相关文章

【Linux】【内存】Linux内核内存分配

【Linux】【内存】Linux内核内存分配 Linux内存管理几大分配方式 内存分配器分配函数使用场景引导内存分配器&#xff08;Boot allocator&#xff09;bootmem内核启动时进行内存初始化Buddy伙伴分配器&#xff08;Buddy allocator&#xff09;alloc_pages以4K(一页)为分配单元…

C++STL中常用的排序算法:sort、random_shuffle、merge和reverse(附C++代码)

&#x1f4aa; 图像算法工程师&#xff0c;专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &a…

docker安装rabbit后访问报错最佳的几种解决方案

错误通常是由于RabbitMQ的安全配置导致的&#xff0c;RabbitMQ默认配置允许的用户仅能通过localhost访问。这通常出现在RabbitMQ的guest用户上&#xff0c;guest用户默认只能从localhost登录&#xff0c;而无法从其他IP地址进行远程访问。 解决方法&#xff1a; 1. **创建一个…

【网络云SRE运维开发】2025第3周-每日【2025/01/15】小测-【第14章ospf高级配置】理论和实操解析

文章目录 14.1 选择题解题思路和参考答案14.2 理论题解题思路和参考答案14.3 实操题解题思路和参考答案思科&#xff08;Cisco&#xff09;设备华为&#xff08;Huawei&#xff09;设备小米/锐捷&#xff08;或其他支持标准CLI命令的设备&#xff09;通过网络管理工具注意事项 …

Public Key Retrieval is not allowed 解决方法

如图&#xff1a;我的报错是Public Key Retrieval is not allowed&#xff0c;我的前后端都能正常加载&#xff0c;但是在请求数据库时就会报错如下&#xff1a; 解决办法&#xff1a; 在 application.yaml 中的数据库设置地方加上allowPublicKeyRetrievaltrue&#xff0c;然后…

C# OpenCV机器视觉:主色提取

在一个忙碌的工作日&#xff0c;小李正对着电脑屏幕上密密麻麻的数据愁眉苦脸&#xff0c;突然&#xff0c;手机铃声大作&#xff0c;打破了办公室的宁静。原来是工厂的张厂长打来的电话&#xff1a;“小李啊&#xff0c;咱们新生产的那批产品&#xff0c;客户要求必须提取出主…

获取文章列表功能

总说 过程参考黑马程序员SpringBoot3Vue3全套视频教程&#xff0c;springbootvue企业级全栈开发从基础、实战到面试一套通关_哔哩哔哩_bilibili 目录 总说 一、功能实现 1.1 Controller层 1.2 Service层 1.3 Impl层 1.4 Mapper层 1.5 测试接口 二、优化 2.1 2.2 一、…

Java安全—SPEL表达式XXESSTI模板注入JDBCMyBatis注入

前言 之前我们讲过SpringBoot中的MyBatis注入和模板注入的原理&#xff0c;那么今天我们就讲一下利用以及发现。 这里推荐两个专门研究java漏洞的靶场&#xff0c;本次也是根据这两个靶场来分析代码&#xff0c;两个靶场都是差不多的。 https://github.com/bewhale/JavaSec …