asp.net-mvc - 尝试排序时无法翻译 LINQ 表达式
问题描述
我正在尝试根据附近的位置对场地进行排序,但这里出现 LINQ 错误。我有一个名为 GetDistanceM 的方法,它接受 4 个参数 lat1、lat2、long1、long2 和两个地理坐标之间的返回距离。在这里,我试图根据距离对场地进行排序。
public async Task<IActionResult> Explore(string sortOrder){
var venues = from v in _context.Venues where v.IsApproved select new VenueModel{
Category = v.Category,
Price = v.Price,
Name = v.Name,
Id = v.Id,
City = v.City,
GetDistance = GetDistanceM(v.Latitude, v.Longitude, 27.692387, 85.318110)
};
switch (sortOrder)
{
case "Price":
venues = venues.OrderBy(s => s.Price);
break;
case "Name":
venues = venues.OrderBy(s => s.Name);
break;
case "Location":
venues = venues.OrderBy(x => x.GetDistance);
break;
default:
venues = venues.OrderByDescending(s => s.Name);
break;
}
当我使用“名称”和价格进行排序时,这非常有效。但它不适用于位置。我收到 InvalidOperationException。
解决方案
问题的原因_context.Venues
是IQueryable<...>
您必须了解IQueryables和IEnumerables之间的区别。
IEnumerable
实现 IEnumerable 的对象表示一个序列。它包含获取序列的第一个元素的所有内容,一旦你有了一个元素,你就可以请求下一个元素,只要有下一个元素。
在最低级别,这是通过请求枚举器,使用GetEnumerator
,然后重复调用MoveNext()
直到没有更多元素来完成的。每次使用 MoveNext 获得元素时,都可以使用属性 Current 访问该元素。
在更高级别上,您可以使用不返回 IEnumerable 的 foreach 或 LINQ 函数进行枚举,例如 ToList()、FirstOrDefault()、Count()、Any() 等。这些函数的深处将调用 GetEnumerator 和 MoveNext / Current
可查询的
实现 IQueryable 的对象并不代表序列本身,它代表一个查询:获得 IEnumerable的可能性。为此, IQueryable 包含 anExpression
和 a Provider
。表达式以某种通用格式保存查询,提供者知道谁必须执行查询(通常是数据库管理系统)以及该 DBMS 使用的语言(通常是 SQL)
连接 IQueryable 的 LINQ 语句不会执行查询,它只会更改表达式。
IQueryable 还实现了 IEnumerable。一旦您开始枚举,Expression 就会发送给 Provider,Provider 会将 Expression 转换为 SQL 并在 DBMS 上执行查询。返回的数据表示为 IEnumerable,因此您可以调用 MoveNext / Current(显式调用或使用 ToList()、Any()、FirstOrDefault() 等)。
但这与我的问题有什么关系?
问题是您的 IQueryable 的提供者不知道函数 GetDistance(),因此它无法将其转换为 SQL。事实上,有几个标准的 LINQ 函数无法翻译成 SQL。请参阅支持和不支持的 LINQ 方法 (LINQ to Entities)。
不太好的解决方案:使用 AsEnumerable
解决此问题的最简单方法是选择为您的 GetDistance 公式输入的数据,然后调用 AsEnumerable,它将执行您的查询。您的本地进程知道 GetDistance,因此您可以在 AsEnumerable 之后调用它:
var venues = context.Venues.Where(venue => venue.IsApproved)
.Select(venue => new
{
Category = v.Category,
Price = v.Price,
Name = v.Name,
Id = v.Id,
City = v.City,
// can't call GetDistance yet, select the input parameters:
Latitude = v.Latitude,
Longitude= v.Longitude,
});
// execute the query:
.AsEnumerable()
// now you can call GetDistance:
.Select(v => new VenueModel
{
Category = v.Category,
Price = v.Price,
Name = v.Name,
Id = v.Id,
City = v.City,
Distance = GetDistanceM(v.Latitude, v.Longitude, 27.692387, 85.318110)
})
.OrderBy(venueModel => venuModel.Distance);
更好的解决方案:为 IQueryable 创建一个扩展方法
虽然这解决了您的问题,但它的缺点是您获取的数据必须按本地进程进行排序。DBMS 在排序数据方面比本地进程更优化。
唉,你没有告诉我们 GetDistance() 是做什么的。似乎它从场地获取纬度和经度,并使用公式返回到某个点的距离。
您可以做的是将函数 GetDistance 转换为 IQueryable 的扩展方法,该方法将 IQueryable 作为输入,并返回选定的数据。请参阅揭秘的扩展方法
public IQueryable<VenueModel> SelectVenueModels(this IQueryable<Venue> venues,
double X, double Y)
{
return venues.Select(venue => new VenueModel()
{
Category = v.Category,
Price = v.Price,
Name = v.Name,
Id = v.Id,
City = v.City,
// Calculate the distance as you would do in GetDistanceM:
Distance = Math.Sqrt(
Math.Pow( (v.Latitude-X), 2) + Math.Pow( (v.Longitude-Y), 2) ),
});
用法:
var venues = _context.Venues.Where(venue => venue.IsApproved)
.SelectVenueModels(27.692387, 85.318110)
.OrderBy(venueModel => ...);
注意:可能是您使用的实体框架类型不知道如何翻译 SQRT 和 POW 等函数,在这种情况下,您必须将它们翻译成其他方法。对于实体框架,您可以使用SQLFunctions 类
Sort 的扩展方法
既然我们已经掌握了扩展方法,为什么不创建一个扩展方法来输入一个IQueryable<VenueModel>
和一个 sortOrder 字符串,并返回正确排序的IQueryable<VenueModel>
?
public IOrderedQueryable<VenueModel> OrderBy(this IQueryable<VenueModel> venueModels,
string sortOrder)
{
switch (sortOrder)
{
case "Price":
venues = venues.OrderBy(s => s.Price);
break;
case "Name":
venues = venues.OrderBy(s => s.Name);
break;
case "Location":
venues = venues.OrderBy(x => x.Distance);
break;
default:
venues = venues.OrderByDescending(s => s.Name);
break;
}
}
用法:
string sortOrder = ...;
var venues = _context.Venues.Where(venue => venue.IsApproved)
.SelectVenueModels(27.692387, 85.318110)
.OrderBy(sortOrder);
但为什么会这样呢?
不同的是,这个 OrderBy 不执行查询,它只改变表达式。表达式的输入是一个 VenueModel,不再调用 GetDistance,只使用您的 Provider 知道如何转换为 SQL 的函数
推荐阅读
- ios - 如何在 Swift 包管理器中添加 Reality 文件?
- react-native - React-navigation - 嵌套导航器中的 goBack 行为(抽屉内的堆栈)
- python - 有没有办法可以将字符串转换为整数并添加整数
- reactjs - Ag-Grid-React & React-测试库
- android - ExoPlayer 通知管理器,从通知中隐藏搜索栏
- c# - DataTable 对表格没有影响
- sql - SQL 查询需要永远运行
- javascript - 如何通过鼠标拖动水平滚动?
- angular - 通过防护装置保护角度偏差
- wordpress - 如何在 wordpress 循环中显示一个或另一个类别的帖子?