# TransactionRecordRepository 查询语句文档 本文档整理了所有与账单(TransactionRecord)相关的查询语句,包括仓储层、服务层中的SQL查询。 ## 目录 1. [TransactionRecordRepository 查询方法](#transactionrecordrepository-查询方法) 2. [其他仓储中的账单查询](#其他仓储中的账单查询) 3. [服务层中的SQL查询](#服务层中的sql查询) 4. [总结](#总结) --- ## TransactionRecordRepository 查询方法 ### 1. 基础查询 #### 1.1 根据邮件ID和交易时间检查是否存在 ```csharp /// 位置: TransactionRecordRepository.cs:94-99 return await FreeSql.Select() .Where(t => t.EmailMessageId == emailMessageId && t.OccurredAt == occurredAt) .FirstAsync(); ``` #### 1.2 根据导入编号检查是否存在 ```csharp /// 位置: TransactionRecordRepository.cs:101-106 return await FreeSql.Select() .Where(t => t.ImportNo == importNo && t.ImportFrom == importFrom) .FirstAsync(); ``` --- ### 2. 核心查询构建器 #### 2.1 BuildQuery() 私有方法 - 统一查询构建 ```csharp /// 位置: TransactionRecordRepository.cs:53-92 private ISelect BuildQuery( int? year = null, int? month = null, DateTime? startDate = null, DateTime? endDate = null, TransactionType? type = null, string[]? classifies = null, string? searchKeyword = null, string? reason = null) { var query = FreeSql.Select(); // 搜索关键词条件(Reason/Classify/Card/ImportFrom) query = query.WhereIf(!string.IsNullOrWhiteSpace(searchKeyword), t => t.Reason.Contains(searchKeyword!) || t.Classify.Contains(searchKeyword!) || t.Card.Contains(searchKeyword!) || t.ImportFrom.Contains(searchKeyword!)) .WhereIf(!string.IsNullOrWhiteSpace(reason), t => t.Reason == reason); // 按分类筛选(处理"未分类"特殊情况) if (classifies is { Length: > 0 }) { var filterClassifies = classifies.Select(c => c == "未分类" ? string.Empty : c).ToList(); query = query.Where(t => filterClassifies.Contains(t.Classify)); } // 按交易类型筛选 query = query.WhereIf(type.HasValue, t => t.Type == type!.Value); // 按年月筛选 if (year.HasValue && month.HasValue) { var dateStart = new DateTime(year.Value, month.Value, 1); var dateEnd = dateStart.AddMonths(1); query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd); } // 按日期范围筛选 query = query.WhereIf(startDate.HasValue, t => t.OccurredAt >= startDate!.Value) .WhereIf(endDate.HasValue, t => t.OccurredAt <= endDate!.Value); return query; } ``` --- ### 3. 分页查询与统计 #### 3.1 分页获取交易记录列表 ```csharp /// 位置: TransactionRecordRepository.cs:108-137 var query = BuildQuery(year, month, startDate, endDate, type, classifies, searchKeyword, reason); // 排序:按金额或按时间 if (sortByAmount) { return await query .OrderByDescending(t => t.Amount) .OrderByDescending(t => t.Id) .Page(pageIndex, pageSize) .ToListAsync(); } return await query .OrderByDescending(t => t.OccurredAt) .OrderByDescending(t => t.Id) .Page(pageIndex, pageSize) .ToListAsync(); ``` #### 3.2 获取总数(与分页查询条件相同) ```csharp /// 位置: TransactionRecordRepository.cs:139-151 var query = BuildQuery(year, month, startDate, endDate, type, classifies, searchKeyword, reason); return await query.CountAsync(); ``` #### 3.3 获取所有不同的交易分类 ```csharp /// 位置: TransactionRecordRepository.cs:153-159 return await FreeSql.Select() .Where(t => !string.IsNullOrEmpty(t.Classify)) .Distinct() .ToListAsync(t => t.Classify); ``` --- ### 4. 按邮件相关查询 #### 4.1 获取指定邮件的交易记录列表 ```csharp /// 位置: TransactionRecordRepository.cs:161-167 return await FreeSql.Select() .Where(t => t.EmailMessageId == emailMessageId) .OrderBy(t => t.OccurredAt) .ToListAsync(); ``` #### 4.2 获取指定邮件的交易记录数量 ```csharp /// 位置: TransactionRecordRepository.cs:169-174 return (int)await FreeSql.Select() .Where(t => t.EmailMessageId == emailMessageId) .CountAsync(); ``` --- ### 5. 未分类账单查询 #### 5.1 获取未分类的账单列表 ```csharp /// 位置: TransactionRecordRepository.cs:176-183 return await FreeSql.Select() .Where(t => string.IsNullOrEmpty(t.Classify)) .OrderByDescending(t => t.OccurredAt) .Page(1, pageSize) .ToListAsync(); ``` --- ### 6. 智能分类相关查询 #### 6.1 根据关键词查询已分类的账单 ```csharp /// 位置: TransactionRecordRepository.cs:185-204 if (keywords.Count == 0) { return []; } var query = FreeSql.Select() .Where(t => t.Classify != ""); // 构建OR条件:Reason包含任意一个关键词 if (keywords.Count > 0) { query = query.Where(t => keywords.Any(keyword => t.Reason.Contains(keyword))); } return await query .OrderByDescending(t => t.OccurredAt) .Limit(limit) .ToListAsync(); ``` --- ### 7. 待确认分类查询 #### 7.1 获取待确认分类的账单列表 ```csharp /// 位置: TransactionRecordRepository.cs:206-212 return await FreeSql.Select() .Where(t => t.UnconfirmedClassify != null && t.UnconfirmedClassify != "") .OrderByDescending(t => t.OccurredAt) .ToListAsync(); ``` --- ### 8. 批量更新操作 #### 8.1 按摘要批量更新交易记录的分类 ```csharp /// 位置: TransactionRecordRepository.cs:214-221 return await FreeSql.Update() .Set(t => t.Type, type) .Set(t => t.Classify, classify) .Where(t => t.Reason == reason) .ExecuteAffrowsAsync(); ``` #### 8.2 更新分类名称 ```csharp /// 位置: TransactionRecordRepository.cs:223-229 return await FreeSql.Update() .Set(a => a.Classify, newName) .Where(a => a.Classify == oldName && a.Type == type) .ExecuteAffrowsAsync(); ``` #### 8.3 确认待确认的分类 ```csharp /// 位置: TransactionRecordRepository.cs:231-241 return await FreeSql.Update() .Set(t => t.Classify == t.UnconfirmedClassify) .Set(t => t.Type == (t.UnconfirmedType ?? t.Type)) .Set(t => t.UnconfirmedClassify, null) .Set(t => t.UnconfirmedType, null) .Where(t => t.UnconfirmedClassify != null && t.UnconfirmedClassify != "") .Where(t => ids.Contains(t.Id)) .ExecuteAffrowsAsync(); ``` --- ## 其他仓储中的账单查询 ### BudgetRepository #### 1. 获取预算当前金额 ```csharp /// 位置: BudgetRepository.cs:12-33 var query = FreeSql.Select() .Where(t => t.OccurredAt >= startDate && t.OccurredAt <= endDate); if (!string.IsNullOrEmpty(budget.SelectedCategories)) { var categoryList = budget.SelectedCategories.Split(','); query = query.Where(t => categoryList.Contains(t.Classify)); } if (budget.Category == BudgetCategory.Expense) { query = query.Where(t => t.Type == TransactionType.Expense); } else if (budget.Category == BudgetCategory.Income) { query = query.Where(t => t.Type == TransactionType.Income); } return await query.SumAsync(t => t.Amount); ``` --- ### TransactionCategoryRepository #### 1. 检查分类是否被使用 ```csharp /// 位置: TransactionCategoryRepository.cs:53-63 var count = await FreeSql.Select() .Where(r => r.Classify == category.Name && r.Type == category.Type) .CountAsync(); return count > 0; ``` --- ## 服务层中的SQL查询 ### SmartHandleService #### 1. 智能分析账单 - 执行AI生成的SQL ```csharp /// 位置: SmartHandleService.cs:351 queryResults = await transactionRepository.ExecuteDynamicSqlAsync(sqlText); ``` **说明**: 此方法接收AI生成的SQL语句并执行,SQL内容由AI根据用户问题动态生成,例如: ```sql SELECT COUNT(*) AS TransactionCount, SUM(ABS(Amount)) AS TotalAmount, Type, Classify FROM TransactionRecord WHERE OccurredAt >= '2025-01-01' AND OccurredAt < '2026-01-01' GROUP BY Type, Classify ORDER BY TotalAmount DESC ``` --- ### BudgetService #### 1. 获取归档摘要 - 年度交易统计 ```csharp /// 位置: BudgetService.cs:239-252 var yearTransactions = await transactionRecordRepository.ExecuteDynamicSqlAsync( $""" SELECT COUNT(*) AS TransactionCount, SUM(ABS(Amount)) AS TotalAmount, Type, Classify FROM TransactionRecord WHERE OccurredAt >= '{year}-01-01' AND OccurredAt < '{year + 1}-01-01' GROUP BY Type, Classify ORDER BY TotalAmount DESC """ ); ``` #### 2. 获取归档摘要 - 月度交易统计 ```csharp /// 位置: BudgetService.cs:254-267 var monthYear = new DateTime(year, month, 1).AddMonths(1); var monthTransactions = await transactionRecordRepository.ExecuteDynamicSqlAsync( $""" SELECT COUNT(*) AS TransactionCount, SUM(ABS(Amount)) AS TotalAmount, Type, Classify FROM TransactionRecord WHERE OccurredAt >= '{year}-{month:00}-01' AND OccurredAt < '{monthYear:yyyy-MM-dd}' GROUP BY Type, Classify ORDER BY TotalAmount DESC """ ); ``` --- ### BudgetSavingsService #### 1. 获取按分类分组的交易金额(用于存款预算计算) ```csharp /// 位置: BudgetSavingsService.cs:62-65 var transactionClassify = await transactionsRepository.GetAmountGroupByClassifyAsync( new DateTime(year, month, 1), new DateTime(year, month, 1).AddMonths(1) ); ``` --- ## 总结 ### 查询方法分类 | 分类 | 方法数 | 说明 | |------|--------|------| | 基础查询 | 2 | 检查记录是否存在(去重) | | 核心构建器 | 1 | BuildQuery() 私有方法,统一查询逻辑 | | 分页查询 | 2 | 分页列表 + 总数统计 | | 分类查询 | 1 | 获取所有不同分类 | | 邮件相关 | 2 | 按邮件ID查询列表和数量 | | 未分类查询 | 1 | 获取未分类账单列表 | | 智能分类 | 1 | 关键词匹配查询 | | 待确认分类 | 1 | 获取待确认账单列表 | | 批量更新 | 3 | 批量更新分类和确认操作 | | 其他仓储查询 | 2 | 预算/分类仓储中的账单查询 | | 服务层SQL | 3 | AI生成SQL + 归档统计 | ### 关键发现 1. **简化的架构**:新实现移除了复杂的统计方法,专注于核心的CRUD操作和查询功能。 2. **统一的查询构建**:`BuildQuery()` 私有方法(第53-92行)被 `QueryAsync()` 和 `CountAsync()` 共享使用,确保查询逻辑一致性。 3. **去重检查**:`ExistsByEmailMessageIdAsync()` 和 `ExistsByImportNoAsync()` 用于防止重复导入。 4. **灵活的查询条件**:支持按年月、日期范围、交易类型、分类、关键词等多维度筛选。 5. **批量操作优化**:提供批量更新分类、确认待确认记录等高效操作。 6. **服务层SQL保持不变**:AI生成SQL和归档统计等高级查询功能仍然通过 `ExecuteDynamicSqlAsync()` 实现。 ### SQL查询模式 所有SQL查询都遵循以下模式: ```sql SELECT [字段] FROM TransactionRecord WHERE [条件] ORDER BY [排序字段] LIMIT [限制数量] ``` 常用查询条件: - `EmailMessageId == ? AND OccurredAt == ?` - 精确匹配去重 - `ImportNo == ? AND ImportFrom == ?` - 导入记录去重 - `Classify != ""` - 已分类记录 - `Classify == "" OR Classify IS NULL` - 未分类记录 - `UnconfirmedClassify != ""` - 待确认记录 - `Reason.Contains(?) OR Classify.Contains(?)` - 关键词搜索 ### 字段说明 | 字段 | 类型 | 说明 | |------|------|------| | Id | bigint | 主键 | | Card | nvarchar | 卡号 | | Reason | nvarchar | 交易原因/摘要 | | Amount | decimal | 交易金额(支出为负数,收入为正数) | | OccurredAt | datetime | 交易发生时间 | | Type | int | 交易类型(0=支出, 1=收入, 2=不计入收支) | | Classify | nvarchar | 交易分类(空字符串表示未分类) | | EmailMessageId | bigint | 关联邮件ID | | ImportNo | nvarchar | 导入编号 | | ImportFrom | nvarchar | 导入来源 | | UnconfirmedClassify | nvarchar | 待确认分类 | | UnconfirmedType | int? | 待确认类型 | ### 接口方法总览 **ITransactionRecordRepository 接口定义(17个方法):** 1. `ExistsByEmailMessageIdAsync()` - 邮件去重检查 2. `ExistsByImportNoAsync()` - 导入去重检查 3. `QueryAsync()` - 分页查询(支持多维度筛选) 4. `CountAsync()` - 总数统计(与QueryAsync条件相同) 5. `GetDistinctClassifyAsync()` - 获取所有分类 6. `GetByEmailIdAsync()` - 按邮件ID查询记录 7. `GetCountByEmailIdAsync()` - 按邮件ID统计数量 8. `GetUnclassifiedAsync()` - 获取未分类记录 9. `GetClassifiedByKeywordsAsync()` - 关键词匹配查询 10. `GetUnconfirmedRecordsAsync()` - 获取待确认记录 11. `BatchUpdateByReasonAsync()` - 按摘要批量更新 12. `UpdateCategoryNameAsync()` - 更新分类名称 13. `ConfirmAllUnconfirmedAsync()` - 确认待确认记录 **私有辅助方法:** - `BuildQuery()` - 统一查询构建器(被QueryAsync和CountAsync使用)