Почему эта команда LINQ join не работает?
У меня есть этот LINQ-запрос:
// types...
LinkedList<WeightedItem> itemScores = new LinkedList<WeightedItem>();
var result = from i in _ctx.Items
join s in itemScores on i.Id equals s._id
orderby s._score descending
select new ItemSearchResult(i, s._score);
// this fails:
return result.ToList();
Что порождает эту ошибку:
Невозможно создать постоянное значение типа "System.Collections.Generic.IEnumerable`1".
В этом контексте поддерживаются только примитивные типы (такие как Int32, String и Guid).
[EDIT] Здесь код WeightedItem
:
public class WeightedItem
{
public int _id;
public decimal? _score;
public WeightedItem(int id, decimal? score)
{
_id = id;
_score = score;
}
}
Вы видите, что я сделал неправильно? Код отлично компилируется, и как _ctx.Items, так и itemScores содержат правильные значения.
Ответы
Ответ 1
Да, это скомпилировалось бы хорошо - проблема в том, что он не может перевести его в SQL. Когда вы ссылаетесь на "локальные" значения, инфраструктура сущности должна решить, что с ними делать, когда нужно создать SQL-запрос. В принципе он не может справиться с выполнением объединения между коллекцией памяти и таблицей базы данных.
Одна вещь, которая могла бы работать, заключалась бы в использовании Contains
. Я не знаю, будет ли LinkedList<T>
работать для этого, но я считаю, что List<T>
делает, по крайней мере, в LINQ to SQL:
List<int> requiredScoreIds = itemScores.Select(x => x._id).ToList();
var tmp = (from i in _ctx.Items
where requiredScoreIds.Contains(i.Id)
orderby s._score descending
select i).AsEnumerable();
// Now do the join in memory to get the score
var result = from i in tmp
join s in itemScores on i.Id equals s._id
select new ItemSearchResult(i, s._score);
Теперь, когда вы выполняете соединение в запросе в памяти, что несколько не нужно. Вместо этого вы можете использовать словарь:
List<int> requiredScoreIds = itemScores.Select(x => x._id).ToList();
var tmp = (from i in _ctx.Items
where requiredScoreIds.Contains(i.Id)
orderby s._score descending
select i).AsEnumerable();
// Create a map from score ID to actual score
Dictionary<int, decimal?> map = itemScores.ToDictionary(x => x._id,
x => x._score);
var result = tmp.Select(i => new ItemSearchResult(i, map[i.Id]));
Ответ 2
Вы не можете подключиться между списком в памяти и запрашиваемым объектом. Вам нужно сделать что-то вроде этого:
var criteria = itemScores.Select(x => x._id).ToList();
var result_tag = (from i in _ctx.Items
where criteria.Contains(i.ID)
select i).ToList();
var result = from i in result_tag
join s in itemScores on i.ID equals s._id
orderby s._score descending
select new ItemSearchResult(i, s._score);
Ответ 3
На всякий случай таблица, представленная _ctx.Items не является большой, и вы не заботитесь о загрузке
всю таблицу в памяти, а затем отфильтровать ее в памяти, вы можете просто поменять порядок элементов в
оператор соединения, как в следующем фрагменте:
LinkedList<WeightedItem> itemScores = new LinkedList<WeightedItem>();
var result = from s in itemScores
join i in _ctx.Items on s._id equals i.Id
orderby s._score descending
select new ItemSearchResult(i, s._score);
return result.ToList();
В исходном заявлении вызывается метод расширения Queryable:
IQueryable<TResult> Queryable.Join<TOuter, TInner, TKey, TResult>(
this IQueryable<TOuter> outer,
IEnumerable<TInner> inner,
Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
Expression<Func<TOuter, TInner, TResult>> resultSelector
)
в то время как в swapped one вызывается метод Enumerable extension:
IEnumerable<TResult> Enumerable.Join<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector
)
поэтому в последнем выражении полная таблица _ctx.Items загружается в память и затем соединяется,
через Linq to Objects, в список itemScores (я не знаю о LinkedList, я попробовал его со списком).
Я добавил этот ответ главным образом потому, что кто-то мог ввести соединение в обратном порядке и иметь его
работайте, даже не осознавая, что произойдет в базе данных.
Я бы не предложил присоединиться к этому пути, хотя он может быть полезен для приложений backoffice всякий раз
вовлеченные таблицы состоят из нескольких записей, и приложение не страдает от ухудшения характеристик.
Это решение, в конце концов, сохраняет код чистым.