一、背景说明
在实际业务开发中,经常会遇到以下场景:
-
查询条件中包含一个 ID 列表
-
数据库对
IN条数有限制,或单次查询性能较差 -
需要将 ID 列表 按固定数量分批查询
-
查询条件对象除了 ID 列表,还有其他多个过滤字段(时间、状态等)
常见但存在问题的做法包括:
-
在方法中 反射查找字段名
-
将条件对象拆散成多个参数
-
针对不同条件对象写多套分批逻辑
这些方案普遍存在以下缺陷:
-
类型不安全
-
维护成本高
-
性能不可控
-
可扩展性差
本文介绍一种 基于接口 + 泛型约束 + 定点复制 的通用解决方案,用于统一处理"带分批字段的查询条件"。
二、核心设计思想
核心目标
让分批逻辑只关心"如何分批",而不关心"字段叫什么、条件对象是什么"。
为此,我们引入三个关键设计点:
-
用接口抽象"可分批能力"
-
用泛型 + where 约束保证类型安全
-
用 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; }
}
设计要点
-
实体字段可以叫
Ids、OrderIds、UserIds等 -
分批逻辑统一使用
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();
}
}
MemberwiseClone是object的受保护方法,用于生成 浅拷贝对象,性能极高,适合条件对象场景。
针对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 层
十一、结语
通过 接口抽象能力 + 泛型约束 + 定点复制 的方式,可以构建一套:
-
类型安全
-
高性能
-
低耦合
-
可长期维护
的通用分批查询基础设施。
该方案尤其适合作为 项目公共层 / 基础组件 使用,避免重复造轮子。