Xamarin项目SQLite.NET Filter谓词使用自定义方法报错解决方案咨询
问题原因
SQLite.NET 的 Where 方法接收的参数类型是 Expression<Func<T, bool>>,它会把传入的表达式树直接翻译为 SQL 语句执行。如果在表达式中调用自定义 C# 方法,SQLite.NET 的表达式解析器无法将自定义方法转换为对应的 SQL 语法,就会抛出 System.NotSupportedException 异常。
解决方案
方案1:封装为返回表达式树的方法(推荐)
将筛选逻辑直接封装为返回 Expression<Func<Intervention, bool>> 的静态方法,返回的表达式可以直接被 SQLite.NET 识别转换,代码如下:
private static Expression<Func<Intervention, bool>> ShouldInterventionBeConsideredInMonth(int month, int year) { DateTime startOfMonth = new DateTime(year, month, 1).StartOfDay(); DateTime endOfMonth = new DateTime(year, month, DateTime.DaysInMonth(year, month)).EndOfDay(); return intervention => // 先排除已删除数据 !intervention.IsDeleted && ( // 有结束时间:时间段和当月重叠 (intervention.EndDate != null && intervention.StartDate <= endOfMonth && intervention.EndDate >= startOfMonth) // 无结束时间:开始时间早等于当月月底 || (intervention.EndDate == null && intervention.StartDate <= endOfMonth) ); }
调用时直接传入方法返回的表达式即可:
public async Task<IEnumerable<Intervention>> GetAMonthsInterventionsAsync(int month, int year) { var interventions = await this.dataProviderService.GetAsync<Intervention>( ShouldInterventionBeConsideredInMonth(month, year), false); return interventions; }
这种方案性能最优,所有筛选逻辑都在 SQL 层面执行,也实现了代码封装复用的需求。
方案2:表达式树拼接(适合多条件动态组合场景)
如果需要动态拼接多个筛选条件,可以自己实现表达式参数替换逻辑,或者使用轻量的表达式拼接工具类,将多个独立的筛选表达式合并为一个表达式传入 Where 方法。
方案3:内存过滤(仅适合小数据量场景)
如果筛选逻辑非常复杂,难以转换为表达式树,可以先将基础范围的数据查询到内存中,再用 Linq to Objects 执行自定义方法过滤:
// 原有返回bool的自定义方法不需要修改 private bool ShouldInterventionBeConsideredInMonth(int month, int year, Intervention intervention) { DateTime startOfMonth = new DateTime(year, month, 1).StartOfDay(); DateTime endOfMonth = new DateTime(year, month, DateTime.DaysInMonth(year, month)).EndOfDay(); bool inMonth = false; if (intervention.StartDate <= endOfMonth && intervention.EndDate != null && intervention.EndDate >= startOfMonth) { inMonth = true; } else if (intervention.StartDate <= endOfMonth && intervention.EndDate == null) { inMonth = true; } if (intervention.IsDeleted) { inMonth = false; } return inMonth; } public async Task<IEnumerable<Intervention>> GetAMonthsInterventionsAsync(int month, int year) { // 先查所有未删除的数据(可以加基础过滤减少返回数据量) var allInterventions = await this.dataProviderService.GetAsync<Intervention>(i => !i.IsDeleted, false); // 内存中执行自定义方法过滤 var filtered = allInterventions.Where(i => ShouldInterventionBeConsideredInMonth(month, year, i)); return filtered; }
注意该方案会把所有符合基础条件的数据加载到内存,数据量大时性能会明显下降,不推荐生产环境大表使用。
内容的提问来源于stack exchange,提问作者Pat Long - Munkii Yebee
相关产品推荐
相关产品推荐

