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

一、背景说明

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

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


十一、结语

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

  • 类型安全

  • 高性能

  • 低耦合

  • 可长期维护

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

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

相关推荐
无小道1 小时前
Qt——事件简单介绍
开发语言·前端·qt
devmoon1 小时前
在 Paseo 测试网上获取 Coretime:On-demand 与 Bulk 的完整实操指南
开发语言·web3·区块链·测试用例·智能合约·solidity
kylezhao20191 小时前
C# 中的 SOLID 五大设计原则
开发语言·c#
凡人叶枫2 小时前
C++中输入、输出和文件操作详解(Linux实战版)| 从基础到项目落地,避坑指南
linux·服务器·c语言·开发语言·c++
啦啦啦_99992 小时前
Redis-5-doFormatAsync()方法
数据库·redis·c#
春日见2 小时前
车辆动力学:前后轮车轴
java·开发语言·驱动开发·docker·计算机外设
锐意无限2 小时前
Swift 扩展归纳--- UIView
开发语言·ios·swift
低代码布道师2 小时前
Next.js 16 全栈实战(一):从零打造“教培管家”系统——环境与脚手架搭建
开发语言·javascript·ecmascript
念何架构之路2 小时前
Go进阶之panic
开发语言·后端·golang
亓才孓2 小时前
[Properties]写配置文件前,必须初始化Properties(引用变量没执行有效对象,调用方法会报空指针错误)
开发语言·python