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

一、背景说明

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

  • 查询条件中包含一个 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 层


十一、结语

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

  • 类型安全

  • 高性能

  • 低耦合

  • 可长期维护

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

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

相关推荐
唐宋元明清218819 小时前
.NET 磁盘管理-技术方案选型
windows·c#·存储
故事不长丨19 小时前
C#正则表达式完全攻略:从基础到实战的全场景应用指南
开发语言·正则表达式·c#·regex
哈库纳玛塔塔20 小时前
放弃 MyBatis,拥抱新一代 Java 数据访问库
java·开发语言·数据库·mybatis·orm·dbvisitor
phltxy20 小时前
从零入门JavaScript:基础语法全解析
开发语言·javascript
天“码”行空21 小时前
java面向对象的三大特性之一多态
java·开发语言·jvm
odoo中国1 天前
Odoo 19 模块结构概述
开发语言·python·module·odoo·核心组件·py文件按
代码N年归来仍是新手村成员1 天前
【Java转Go】即时通信系统代码分析(一)基础Server 构建
java·开发语言·golang
Z1Jxxx1 天前
01序列01序列
开发语言·c++·算法
沐知全栈开发1 天前
C语言中的强制类型转换
开发语言
关于不上作者榜就原神启动那件事1 天前
Java中大量数据Excel导入导出的实现方案
java·开发语言·excel