2009-12-30 3 views
10

У меня есть этот LINQ-запрос:Почему этот оператор объединения 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(); 

Что генерирует эту ошибку:

Unable to create a constant value of type 'System.Collections.Generic.IEnumerable`1'.
Only primitive types ('such as Int32, String, and Guid') are supported in this context.

[EDIT] Вот код WeightedItem:

public class WeightedItem 
{ 
    public int _id; 
    public decimal? _score; 

    public WeightedItem(int id, decimal? score) 
    { 
     _id = id; 
     _score = score; 
    } 
} 

Вы можете увидеть что я сделал неправильно? Код отлично компилируется, и те элементы _ctx.Items и itemScores содержат правильные значения.

+0

Можете ли вы разместить код для WeightedItem – Lazarus

+0

Видимо, WeightedItem не является примитивным типом. – DOK

+0

Lazarus, все готово. DOK, что означает? – Mickel

ответ

21

Да, это скомпилировалось бы хорошо - проблема в том, что он не может перевести его в SQL. Когда вы ссылаетесь на «локальные» значения, инфраструктура сущности должна решить, что с ними делать, когда нужно создать SQL-запрос. В основном он не может справиться с выполнением объединения между коллекцией памяти и таблицей базы данных.

Одна вещь, которая может работа будет использовать Contains вместо этого. Я не знаю, будет ли LinkedList<T> работать для этого, но я считаю List<T> делает, по крайней мере, в LINQ к 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])); 
+0

имеет смысл, поэтому .AsEnumerable() выполняет запрос и сохраняет результат в памяти? Если нет, какая часть кода? – Mickel

+2

@Mickel: 'AsEnumerable' не выполняет сразу же запрос, но возвращает' IEnumerable ', а не 'IQueryable ', поэтому остальная часть запроса будет выполнена с использованием' Enumerable.xxx' вместо 'Queryable. xxx'. Когда этот запрос, наконец, должен быть выполнен, он будет делать первую часть в базе данных, а вторую часть - в памяти. –

3

Вы не можете соединиться между списком в памяти и запрашиваемым объектом. Вам нужно сделать что-то вроде этого:

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); 
+5

Ah shucks - Jon Skeet beat me :) –

+1

Он всегда выигрывает ... – Ragepotato

+0

Jon Skeet - Chuck Norris из StackOverflow –

1

Только в случае, если таблица представлена ​​_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 
) 

в то время как в выгружена одном вызове перечислимого метод расширения:

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 к объектам, к списку itemScores (Я не знаю о LinkedList, я попробовал его со списком).

Я добавил этот ответ главным образом потому, что кто-то мог ввести объединение в обратном порядке и иметь работу , даже не осознавая, что произойдет в базе данных.

Я бы не предложил присоединиться к этому пути, хотя он может быть полезен для приложений backoffice, когда вовлеченные таблицы состоят из нескольких записей, и приложение не страдает от ухудшения характеристик. Это решение, в конце концов, сохраняет код чище.

Смежные вопросы