.NET Core 2中纯ADO.NET用Lambda生成WHERE子句及相关方案咨询
刚好之前做过类似的需求,给你分享两个问题的实用解决方案:
你自己手写Expression解析确实容易踩坑还费时间,推荐用SqlKata这个轻量的SQL构建库,它完美支持Lambda表达式转WHERE条件,还自动处理参数化查询(避免SQL注入),完全不需要完整ORM的冗余功能,刚好匹配你的场景。
实现步骤:
- 先安装NuGet包:
Install-Package SqlKata(或者根据你的数据库选对应的扩展包,比如SqlKata.SqlServer) - 写通用的
GetRecords方法:
using SqlKata; using SqlKata.Compilers; using System.Data.SqlClient; using System.Linq.Expressions; // 假设这是你的服务类 public class DataService { private readonly string _connectionString; public DataService(string connectionString) { _connectionString = connectionString; } public IEnumerable<T> GetRecords<T>(Expression<Func<T, bool>> predicate) { // 根据你的数据库类型选择编译器,比如SqlServerCompiler/MySqlCompiler等 var compiler = new SqlServerCompiler(); // 假设实体类名和数据库表名一致,不一致的话可以加特性或者手动指定表名 var query = new Query(typeof(T).Name).Where(predicate); // 编译生成带参数化的SQL和绑定参数 var sqlResult = compiler.Compile(query); // 执行你的SQL读取逻辑(这里用SqlConnection示例,替换成你自己的连接方式) using (var connection = new SqlConnection(_connectionString)) { connection.Open(); using (var reader = connection.ExecuteReader(sqlResult.Sql, sqlResult.Bindings)) { // 这里替换成你自己的IDataReader到实体的映射逻辑 return MapReaderToEntities<T>(reader); } } } // 示例:简单的手动映射方法,也可以用AutoMapper等工具简化 private IEnumerable<T> MapReaderToEntities<T>(IDataReader reader) where T : new() { var properties = typeof(T).GetProperties(); while (reader.Read()) { var entity = new T(); foreach (var prop in properties) { if (!reader.IsDBNull(reader.GetOrdinal(prop.Name))) { prop.SetValue(entity, reader[prop.Name]); } } yield return entity; } } }
这样你就可以像service.GetRecords<Person>(p => p.FirstName == "Jack" && p.City == "NY")这样调用,SqlKata会自动生成类似WHERE FirstName = @p0 AND City = @p1的参数化WHERE子句,安全又省心。
另外还有个备选方案是System.Linq.Dynamic.Core,它可以把Lambda表达式解析成字符串条件,但需要自己处理参数化,不如SqlKata直接。
这里分两种场景:
1. 内存中对已读取的IDataReader数据进行IQueryable查询
如果IDataReader已经把数据从数据库拉到本地了,你可以先把数据转成IEnumerable<T>,再调用AsQueryable(),这样就能用Linq查询了:
using (var reader = connection.ExecuteReader(sql)) { var data = MapReaderToEntities<Person>(reader).AsQueryable(); // 现在可以用IQueryable的各种方法 var filtered = data.Where(p => p.Age > 30).OrderBy(p => p.LastName); }
这种方式的缺点是查询是在内存中进行的,适合小数据集。
2. 数据库层面结合IQueryable与IDataReader(即先构建IQueryable再用IDataReader读取)
如果想让IQueryable的表达式树转换成SQL在数据库执行,然后用IDataReader读取结果,其实大部分Micro ORM都支持这个。比如用LinqToDB:
using LinqToDB; using LinqToDB.Data; public IQueryable<T> BuildQuery<T>(Expression<Func<T, bool>> predicate) { using var dataContext = new DataConnection(_connectionString, new SqlServerProvider()); return dataContext.GetTable<T>().Where(predicate); } // 执行查询并返回IDataReader public IDataReader ExecuteQueryToReader<T>(IQueryable<T> query) { var dataContext = ((IQueryable)query).Provider as DataContext; return dataContext.ExecuteReader(query.Expression); }
LinqToDB会把IQueryable的表达式树编译成SQL,然后用IDataReader读取结果,兼顾了IQueryable的便捷和IDataReader的高性能。
如果不想用第三方库,自己实现IQueryProvider和IQueryable<T>也可以,但工作量很大,不推荐,除非有特殊定制需求。
内容的提问来源于stack exchange,提问作者Adam Jachocki

