linq2db中IEnumerable<T>作为控制器参数的枚举与SQL执行次数疑问
关于linq2db中IEnumerable.Contains多次调用的枚举次数与SQL执行问题
你的判断验证
- SQL查询仅执行一次:这个判断完全正确。因为
IQueryable是延迟执行模式,只有调用ToListAsync时才会触发数据库请求。linq2db会把两个Contains的逻辑合并成单条SQL,形式大概是WHERE Id IN (...) OR DepartmentId IN (...),所以只会向数据库发送一次查询。 - ids是否被枚举两次:分两种情况:
- 如果ids是未缓存的IEnumerable(比如来自未执行的IQueryable、yield生成的序列、自定义延迟枚举集合),两次
Contains调用确实会触发两次完整枚举——因为每次生成SQL的IN参数时,linq2db都需要遍历整个集合获取元素。 - 如果ids是已缓存的集合类型(比如
List<long>、long[]),虽然编译器会报警告,但所谓的"枚举"只是遍历内存中的集合,不会有额外的数据源读取开销,这种情况多次枚举几乎没影响。
- 如果ids是未缓存的IEnumerable(比如来自未执行的IQueryable、yield生成的序列、自定义延迟枚举集合),两次
测试ids枚举次数的方法
自己实现一个带计数的IEnumerable,就能直观看到枚举次数:
public class CountedEnumerable<T> : IEnumerable<T> { private readonly IEnumerable<T> _source; public int EnumerationCount { get; private set; } public CountedEnumerable(IEnumerable<T> source) { _source = source; } public IEnumerator<T> GetEnumerator() { EnumerationCount++; return _source.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
使用时替换原ids:
var countedIds = new CountedEnumerable<long>(ids); IQueryable<PersonDto> query = from person in dbContext.GetTable<DbPerson>() where countedIds.Contains(person.Id) || countedIds.Contains(person.DepartmentId) select new PersonDto { Name = person.Name, Id = person.Id, DepartmentId = person.DepartmentId }).Distinct(); await query.ToListAsync(); // 查看countedIds.EnumerationCount的值,就是实际枚举次数
消除警告的最佳实践
提前把ids转换成List或数组,这样只会枚举一次原始数据源,后续操作都是基于内存集合:
var idsList = ids.ToList(); // 这里仅枚举一次原始数据源 IQueryable<PersonDto> query = from person in dbContext.GetTable<DbPerson>() where idsList.Contains(person.Id) || idsList.Contains(person.DepartmentId) select new PersonDto { Name = person.Name, Id = person.Id, DepartmentId = person.DepartmentId }).Distinct();
内容的提问来源于stack exchange,提问作者Learner
相关产品推荐
相关产品推荐

