【设计】分批查询数据通用方法(基于接口 + 泛型 + 定点复制)

一、背景说明

在实际业务开发中,经常会遇到以下场景:

  • 查询条件中包含一个 ID 列表

  • 数据库对 IN 条数有限制,或单次查询性能较差

  • 需要将 ID 列表 按固定数量分批查询

  • 查询条件对象除了 ID 列表,还有其他多个过滤字段(时间、状态等)

常见但存在问题的做法包括:

  • 在方法中 反射查找字段名

  • 将条件对象拆散成多个参数

  • 针对不同条件对象写多套分批逻辑

这些方案普遍存在以下缺陷:

  • 类型不安全

  • 维护成本高

  • 性能不可控

  • 可扩展性差

本文介绍一种 基于接口 + 泛型约束 + 定点复制 的通用解决方案,用于统一处理"带分批字段的查询条件"。

二、核心设计思想

核心目标

让分批逻辑只关心"如何分批",而不关心"字段叫什么、条件对象是什么"。

为此,我们引入三个关键设计点:

  1. 用接口抽象"可分批能力"

  2. 用泛型 + where 约束保证类型安全

  3. 用 Copy / Clone 实现"定点复制"

三、定义分批能力接口

首先定义一个接口,用于统一描述"可分批字段"。就是不管传过来的id具体叫什么,继承这个接口之后都叫BatchList

cs 复制代码
public interface IBatchCondition<T> { IList<T> BatchList { get; set; } }

接口说明

  • BatchList:用于分批的集合

  • T:集合元素类型(如 string / int / Guid)

  • 接口不关心字段原名,只定义能力

四、条件实体实现接口(字段映射)

具体的查询条件对象实现该接口,并将接口字段映射到真实字段。

cs 复制代码
public class QueryCondition : IBatchCondition<string>
{
    public IList<string> BatchList
    {
        get => Ids;
        set => Ids = value?.ToList();
    }

    public List<string> Ids { get; set; }

    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
}

设计要点

  • 实体字段可以叫 IdsOrderIdsUserIds

  • 分批逻辑统一使用 BatchList

  • 实体内部字段命名不影响通用代码

五、分批查询方法设计

方法签名,通用方法定义

cs 复制代码
public DataTable BatchQueryForDataTable<TCondition, T>(
    TCondition condition,
    string queryName,
    int limit,
    int parallelNum,
    string sort = ""
)
where TCondition : class, IBatchCondition<T>

泛型约束说明

cs 复制代码
where TCondition : class, IBatchCondition<T>

表示:

  • TCondition 必须是 引用类型

  • 必须实现 IBatchCondition<T>

  • 方法内部可安全访问 condition.BatchList

六、定点复制(Copy / Clone)的必要性

为什么不能直接修改原 condition?

  • 分批是并发执行的

  • 多线程下修改同一个条件对象会产生数据污染

  • 每一批都应有一个独立的条件副本

目标

复制条件对象本身,只替换分批字段,其余条件保持不变

七、FastCloneHelper(简化版)

cs 复制代码
public static class FastCloneHelper
{
    public static T Clone<T>(T source) where T : class
    {
        return (T)source.MemberwiseClone();
    }
}

MemberwiseCloneobject 的受保护方法,

用于生成 浅拷贝对象,性能极高,适合条件对象场景。

针对C#7.0,可以这么写

cs 复制代码
public static class FastCloneHelper
{
    public static T Clone<T>(T source) where T : class
    {
        if (source == null)
            return null;

        var methodInfo = typeof(object).GetMethod(
            "MemberwiseClone",
            BindingFlags.Instance | BindingFlags.NonPublic
        );

        if (methodInfo == null)
            throw new InvalidOperationException("无法获取 MemberwiseClone 方法");

        var clone = (T)methodInfo.Invoke(source, null);

        if (clone is IPostCloneFixup fixup)
            fixup.FixupAfterClone();

        return clone;
    }
}

//实体类代码示例
public class OrderQueryCondition : IBatchCondition<string>, IPostCloneFixup
{
    public string[] OrderIds { get; set; }
    public int Status { get; set; }

    public IList<string> BatchList
    {
        get => OrderIds;
        set => OrderIds = value?.ToArray();
    }

    // 修正 MemberwiseClone 后的引用类型
    public void FixupAfterClone()
    {
        OrderIds = OrderIds?.ToArray();
    }
}

八、完整分批实现示例(核心逻辑)

cs 复制代码
public DataTable BatchQueryForDataTable<TCondition, T>(
    TCondition condition,
    string queryName,
    int limit,
    int parallelNum,
    string sort = ""
)
where TCondition : class, IBatchCondition<T>
{
    if (condition?.BatchList == null || condition.BatchList.Count == 0)
        return null;

    DataTable result = null;
    var locker = new object();

    condition.BatchList
        .Chunk(limit)
        .AsParallel()
        .WithDegreeOfParallelism(parallelNum)
        .ForAll(part =>
        {
            var clone = FastCloneHelper.Clone(condition);
            clone.BatchList = part.ToList();

            var temp = QueryForDataTable(queryName, clone);

            lock (locker)
            {
                if (result == null)
                    result = temp;
                else
                    result.Merge(temp);
            }
        });

    if (result != null && !string.IsNullOrEmpty(sort))
    {
        var view = result.DefaultView;
        view.Sort = sort;
        return view.ToTable();
    }

    return result;
}

九、设计重点总结

1️⃣ 接口隔离

  • 分批逻辑只依赖接口

  • 与具体实体字段解耦

2️⃣ 编译期安全

  • 泛型 + where 保证合法类型

  • 不存在运行时反射失败

3️⃣ 高性能

  • 无反射

  • 无 Expression

  • MemberwiseClone 成本极低

4️⃣ 易扩展

  • 新增条件对象 → 实现接口即可

  • 分批逻辑无需修改


十、适用场景

  • 大批量 ID 查询

  • 数据库 IN 条数受限

  • 并发分批查询

  • 通用 DAO / Repository 层


十一、结语

通过 接口抽象能力 + 泛型约束 + 定点复制 的方式,可以构建一套:

  • 类型安全

  • 高性能

  • 低耦合

  • 可长期维护

的通用分批查询基础设施。

该方案尤其适合作为 项目公共层 / 基础组件 使用,避免重复造轮子。

相关推荐
平常心的技术小牛37 分钟前
Qt-快速上手-QMenuBar
开发语言·qt
青 春 记 忆1 小时前
零基础入门Python11|Git实战:为任务管理器建立版本历史
开发语言·git·vscode·python·python3.11
To_OC1 小时前
从一行入口代码啃透 NestJS:工厂模式、装饰器和依赖注入是怎么串起来的
后端·设计模式·nestjs
余额瞒着我当琳3 小时前
C++--深拷贝三件套 + swap + 写时拷贝 + vector 扩容 + reserve
java·开发语言·c++
坐吃山猪3 小时前
WebClient内存配置解析
开发语言·springboot·webflux
东方小月3 小时前
从零开发一个 Coding Agent(十二):实现版本化 JSONL 与真实 CLI 入口
前端·设计模式·前端框架
jdksjw4 小时前
同步与异步、阻塞与非阻塞、多线程、协程超详细讲解(Python并发编程从入门到精通)
开发语言·python
Zach_菠萝侠4 小时前
【DeepSeek Harness 研究】进化方向4:安全加固 思考、设计与实现
开发语言·安全·deepseek
SL-staff4 小时前
技术实践:HR如何用JVS-Logic可视化编排实现考勤数据同步(含节点配置与异常处理)
开发语言·python·低代码·钉钉·可视化编排·jvs-logic·hr技术
曹牧5 小时前
C#:数字的定义和表示方式
算法·c#