Причина проблемы в том, что _context.Venues
- это IQueryable<...>
. Вы должны знать о разнице между IQueryables и IEnumerables .
IEnumerable
Объект, который реализует IEnumerable, представляет последовательность. Он содержит все, чтобы получить первый элемент последовательности, и когда у вас есть элемент, вы можете запросить следующий элемент, если есть следующий элемент.
На самом низком уровне это делается запрашивая перечислитель, используя GetEnumerator
, а затем повторно вызывая MoveNext()
, пока не останется больше элементов. Каждый раз, когда у вас есть элемент, использующий MoveNext, доступ к этому элементу можно получить с помощью свойства Current.
На более высоком уровне вы перечисляете, используя функции foreach или LINQ, которые не возвращают IEnumerable, например ToList (), FirstOrDefault (), Count (), Any () и др. c. Глубоко внутри этих функций будут вызываться GetEnumerator и MoveNext / Current
IQueryable
Объект, который реализует IQueryable, не представляет саму последовательность, он представляет запрос: потенциал для получить IEnumerable. IQueryable имеет выражение и поставщика. Выражение содержит запрос в каком-то общем формате c, поставщик знает, кто должен выполнять запрос (обычно это система управления базами данных), и язык, который использует эта СУБД (обычно SQL)
Конкатенация операторов LINQ IQueryable не выполняет запрос, он только изменит выражение.
IQueryable также реализует IEnumerable. Как только вы начинаете перечисление, выражение отправляется поставщику, который преобразует выражение в SQL и выполнит запрос в СУБД. Возвращенные данные представлены в виде IEnumreable, поэтому вы можете вызвать MoveNext / Current (либо явно, либо с помощью ToList (), Any (), FirstOrDefault () и т. Д. c).
Но Какое отношение это имеет к моему вопросу?
Проблема в том, что провайдер вашего 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
Хотя это решает вашу проблему, недостатком является то, что выбранные вами данные не сортируются системой управления базами данных. СУБД гораздо более оптимизирована для сортировки ваших данных, чем локальный процесс.
Увы, вы не сказали нам, что делает 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
Метод расширения для сортировки
Теперь, когда мы освоили методы расширения, почему бы не создать расширение метод, который вводит IQueryable<VenueModel>' and a sortOrder string, and returns the properly ordered
IQueryable '?
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 больше не вызывается, используются только те функции, которые ваш провайдер знает, как перевести в SQL