using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Public.ViewModel.Response;

namespace Dpz.Core.Service.RepositoryServiceImpl;

public class UserHistoryService(IRepository<UserHistory> repository) : IUserHistoryService
{
    public async Task<IPagedList<UserHistoryResponse>> GetPagedListAsync(UserHistoryRequest request)
    {
        var filterEmpty = Builders<UserHistory>.Filter.Empty;
        var filters = new List<FilterDefinition<UserHistory>>();
        if (!string.IsNullOrEmpty(request.Account))
        {
            var accountFilter = Builders<UserHistory>.Filter.Regex(
                x => x.Account,
                new BsonRegularExpression(request.Account, "i")
            );
            filters.Add(accountFilter);
        }

        if (request.ChangeTimeStart.HasValue)
        {
            var startFilter = Builders<UserHistory>.Filter.Gte(
                x => x.ChangeTime,
                request.ChangeTimeStart.Value
            );
            filters.Add(startFilter);
        }

        if (request.ChangeTimeEnd.HasValue)
        {
            var endFilter = Builders<UserHistory>.Filter.Lte(
                x => x.ChangeTime,
                request.ChangeTimeEnd.Value
            );
            filters.Add(endFilter);
        }

        if (!string.IsNullOrEmpty(request.ChangeProperty))
        {
            var propertyFilter = Builders<UserHistory>.Filter.AnyIn(
                x => x.Changes.Select(y => y.Field),
                [request.ChangeProperty]
            );
            filters.Add(propertyFilter);
        }

        var filterResult =
            filters.Count > 0 ? Builders<UserHistory>.Filter.And(filters) : filterEmpty;
        return await repository
            .SearchFor(filterResult)
            .SortByDescending(x => x.Id)
            .ToPagedListAsync<UserHistory, UserHistoryResponse>(
                request.PageIndex,
                request.PageSize
            );
    }

    public async Task<List<string>> GetChangedPropertiesAsync()
    {
        return await repository
            .MongodbQueryable.SelectMany(x => x.Changes)
            .GroupBy(x => x.Field)
            .Select(x => x.Key)
            .ToListAsync();
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个基于 MongoDB 驱动和仓储(repository)模式实现的服务类,用来查询用户历史(UserHistory)记录并导出已变更的字段名。类实现了两个异步方法:GetPagedListAsync 和 GetChangedPropertiesAsync。下面分项说明其功能、流程以及几点注意/改进建议。

总体结构

  • 构造函数注入:IRepository repository(通过记录可对 MongoDB 集合进行查询)。
  • 目标:根据请求参数筛选、分页并返回 UserHistory 的分页响应;以及获取所有变更字段名的列表。

方法 1 — GetPagedListAsync(UserHistoryRequest request) 功能:根据 request 中的筛选条件(账号模糊匹配、变更时间范围、变更属性)构建 MongoDB 过滤器,按 Id 降序分页并把结果映射为 UserHistoryResponse 返回 IPagedList。

流程(代码逻辑):

  1. 初始化空过滤(filterEmpty)和一个 FilterDefinition 列表 filters。
  2. 如果 request.Account 非空,添加一个正则(不区分大小写)过滤,匹配 Account 字段(模糊匹配)。
  3. 如果 request.ChangeTimeStart 有值,添加 ChangeTime >= start 的过滤。
  4. 如果 request.ChangeTimeEnd 有值,添加 ChangeTime <= end 的过滤。
  5. 如果 request.ChangeProperty 非空,期望添加“Changes 列表中存在 Field 等于该值”的过滤(原代码试图用 AnyIn)。
  6. 将多个过滤器用 Builders<...>.Filter.And 组合(若无条件则使用 Empty)。
  7. 用 repository.SearchFor(filterResult) 获取查询,按 Id 降序 SortByDescending(x => x.Id),然后调用 ToPagedListAsync<UserHistory, UserHistoryResponse>(pageIndex, pageSize) 做分页并把实体映射到响应模型,最终返回分页结果。

方法 2 — GetChangedPropertiesAsync() 功能:返回集合中 Changes 子文档里所有不同的 Field 值(即所有曾被更改过的属性名)。

实现思路:

  • 通过 repository.MongodbQueryable.SelectMany(x => x.Changes) 将所有 UserHistory 的 Changes 展开为一个元素序列。
  • GroupBy 按 Field 分组并 Select Key,从而得到不同的字段名列表,最后 ToListAsync() 返回 List

需要注意的问题与改进建议

  1. 原代码中针对 ChangeProperty 的过滤写法有错误/不可用:

    • 表达式 Builders.Filter.AnyIn(x => x.Changes.Select(y => y.Field), [request.ChangeProperty]) 既不是合法的 C# 语法([request.ChangeProperty] 不是数组字面量),也不是 MongoDB 驱动期望的字段表达方式。
    • 推荐正确写法(任选其一): a) 使用 ElemMatch: var propertyFilter = Builders.Filter.ElemMatch(u => u.Changes, c => c.Field == request.ChangeProperty); b) 使用字段路径匹配简单等值(当只需存在某个子文档的 Field 值时): var propertyFilter = Builders.Filter.Eq("Changes.Field", request.ChangeProperty); c) 如果想用 AnyIn(给定多个值),传入数组并用字段表达式(更复杂,通常不必): var propertyFilter = Builders.Filter.AnyIn("Changes.Field", new[] { request.ChangeProperty });
  2. 性能与索引建议:

    • 如果频繁按 Account、ChangeTime 或 Changes.Field 查询,应在这些字段上建立索引(特别是 Changes.Field 是数组元素字段时,建立子字段索引)。
    • SelectMany + GroupBy 会扫描大量数据,若数据量很大,考虑用 MongoDB 的 distinct 或聚合管道(Aggregate)来做去重以节省内存/IO:collection.DistinctAsync("Changes.Field", filter)。
  3. 其它可改进点:

    • 验证并规范化 request.PageIndex/PageSize,防止异常或非常大的 pageSize。
    • 支持 CancellationToken 以便取消长时间查询。
    • 如果 ToPagedListAsync 包含映射逻辑,确认映射配置(AutoMapper 等)正确无误。
    • 考虑对正则查询(Account)进行预处理或只在必要时使用,正则查询可能无法利用普通索引(除非为前缀查询或建立 text 索引)。

总结

  • GetPagedListAsync:按可选条件(账号模糊、时间区间、变更属性)构建过滤器,按 Id 降序分页并映射到响应模型返回。
  • GetChangedPropertiesAsync:返回 Changes 子文档中所有不同的 Field 名称。
  • 代码中关于 Changes.Field 的过滤写法需要修正,且在大数据量时需注意索引和查询效率。
评论加载中...