能否使用Array.IndexOf或代码派生数据在数据库端对EF查询结果排序
可行实现方案
方案1:EF Core原生支持的Array.IndexOf翻译(EF Core 3.0+ 适用)
EF Core 3.0及以上版本已经内置了对Array.IndexOf方法的SQL翻译支持,你直接去掉ToList()调用即可,排序逻辑会自动生成对应的CASE WHEN语句下推到数据库执行:
// 按优先级排序的ID(数量有限,通常为1到10个值) var ids = new [] { 3, 1, 2 }; var firstItemByPriority = context.Items .Where(i => ids.Contains(i.Id)) .OrderBy(i => Array.IndexOf(ids, i.Id)) .FirstOrDefault();
生成的SQL大概是如下结构,完全在数据库端执行:
SELECT TOP(1) [i].[Id], [i].[其他字段] FROM [Items] AS [i] WHERE [i].[Id] IN (3, 1, 2) ORDER BY CASE WHEN [i].[Id] = 3 THEN 0 WHEN [i].[Id] = 1 THEN 1 WHEN [i].[Id] = 2 THEN 2 END
这个方案不需要引入任何额外依赖,适配大多数场景,且你给出的id数组长度只有1-10,生成的CASE语句性能没有任何问题。
方案2:手动构造CASE排序表达式
如果用的是旧版本EF Core或者EF6,不支持Array.IndexOf自动翻译,可以手动构建OrderBy的表达式树,生成和上面一样的CASE WHEN逻辑:
var ids = new [] { 3, 1, 2 }; // 构建排序表达式 var parameter = Expression.Parameter(typeof(Item), "i"); var idProperty = Expression.Property(parameter, nameof(Item.Id)); // 不在数组里的默认排最后,前面已经加了过滤条件实际不会出现该值 Expression caseExpr = Expression.Constant(ids.Length); for (int i = 0; i < ids.Length; i++) { caseExpr = Expression.Condition( Expression.Equal(idProperty, Expression.Constant(ids[i])), Expression.Constant(i), caseExpr); } var orderLambda = Expression.Lambda<Func<Item, int>>(caseExpr, parameter); // 执行查询 var firstItemByPriority = context.Items .Where(i => ids.Contains(i.Id)) .OrderBy(orderLambda) .FirstOrDefault();
方案3:SQL Server专属OPENJSON关联排序(针对超大id列表场景)
如果id列表长度远大于10,CASE语句会比较长,这时候可以用SQL Server的OPENJSON函数把数组转为带序号的临时结果集,关联后排序:
var ids = new [] { 3, 1, 2 }; // 把数组序列化为JSON数组字符串 var idsJson = JsonSerializer.Serialize(ids); var firstItemByPriority = context.Items .Join( // 需提前定义匹配返回字段的JsonIdOrder模型,或使用EF Core 7.0+无类型查询 context.Set<JsonIdOrder>().FromSql($"SELECT [key] as [Order], [value] as [Id] FROM OPENJSON('{idsJson}')"), item => item.Id, j => j.Id, (item, j) => new { item, j.Order } ) .OrderBy(x => x.Order) .Select(x => x.item) .FirstOrDefault();
内容的提问来源于stack exchange,提问作者user2864740
相关产品推荐
相关产品推荐

