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

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

一、需求

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

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

二、功能编写

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

2.1 预算扣除

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

csharp 复制代码
/// <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方法很简单,我们直接将支出记录的金额再加回预算即可,代码如下:

csharp 复制代码
/// <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 方法的修改需要将数据兼顾记录类分类的类型是否发生变更,代码如下:

csharp 复制代码
/// <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 事务机制确保操作的原子性和数据一致性。具体实现方法包括调整预算金额、根据收支记录类型动态更新预算、处理异常并回滚事务,以保证数据库的完整性和正确性。文章通过实际代码示例清晰地阐述了这些操作的细节。

下一篇文章,我们将结合主币种设置和预算来实现预算金额的币种转换。

相关推荐
踢足球的程序员·17 分钟前
ruoyi-cloud docker启动微服务无法连接nacos,Client not connected, current status:STARTING
docker·微服务·容器
58沈剑1 小时前
如何最小改变架构,快速实现流控的?(第34讲)
架构
HsuYang2 小时前
Vite源码学习(七)——DEV流程中的Middleware
前端·javascript·架构
玉面小君3 小时前
C#图表性能的巅峰之选:LightningChart®.NET
信息可视化·c#·.net
CodeCraft Studio3 小时前
利用.NET版Word处理控件Aspose.Words,使用 C# 在 Word 中创建图表
c#·word·.net
绝无仅有3 小时前
权限系统设计与不同模型对比
面试·程序员·架构
dami_king4 小时前
ElasticSearch|ES|架构介绍|原理浅析
大数据·elasticsearch·架构
dog2505 小时前
互联网架构困境:TCP/IP 拥塞难题与地址对称性
网络·tcp/ip·架构
三天不学习6 小时前
【Delete 删除数据语法合集】.NET开源ORM框架 SqlSugar 系列
后端·开源·.net·orm·微软技术·sqlsugar