LINQ查询中用运算符比较SQLite的decimal失败,CompareTo可行
问题背景
我在SQLite数据库中有一张Foo表,其中包含数据类型为decimal的Bar列。想要检索Bar值大于100的所有行,执行以下LINQ查询时出现翻译错误:
var result = from r in db.Foo where r.Bar > 100 select r.Bar;
错误信息
The LINQ expression 'DbSet
.Where(r => r.Bar > 100)' could not be translated. Either rewrite the query in a form that can be translated,
or switch to client evaluation explicitly by inserting a call to either
AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
改用CompareTo方法改写查询后,却能正常执行:
var result = from r in db.Foo where r.Bar.CompareTo(100) > 0 select r;
我疑惑二者逻辑一致但结果不同,想知道CompareTo是否触发了客户端求值?
解答
1. EF Core对SQLite Decimal类型的翻译限制
SQLite没有原生的decimal/numeric数据类型,EF Core会将.NET的decimal映射为SQLite的TEXT或REAL类型(具体取决于配置)。当直接使用>运算符比较decimal列与数值时,EF Core的查询翻译器无法将这个操作转换为SQLite兼容的语法,导致翻译失败。
2. CompareTo方法的翻译逻辑
EF Core的查询翻译器对CompareTo方法有专门的处理规则,它可以将r.Bar.CompareTo(100) > 0翻译为SQLite能识别的比较语句:
- 如果
Bar映射为TEXT类型,会生成字符串比较的SQL(SQLite中存储decimal为文本时,字典序比较可保证数值顺序); - 如果映射为REAL类型,则生成数值比较的SQL。
这个过程是服务器端求值,没有触发客户端求值。
3. 验证是否触发客户端求值
你可以通过查看EF Core生成的SQL语句确认:
var query = from r in db.Foo where r.Bar.CompareTo(100) > 0 select r; Console.WriteLine(query.ToQueryString());
输出的SQL会包含针对Bar列的比较逻辑,说明查询是在数据库端执行的,而非拉取所有数据到本地过滤。
内容的提问来源于stack exchange,提问作者mamen

