Как я могу запрашивать данные, используя список составных ключей в Entity Framework? [Дубликат]

Конструктор - это метод .. специальный метод, вызываемый «конструкцией» класса.

Определение: конструктор - это функция-член класса в C ++ и C #, которая имеет то же имя, что и сам класс.

Цель конструктора - инициализировать все переменные-члены при создании объекта этого класса. Любые ресурсы, такие как память или открытые файлы, обычно выпускаются в деструкторе класса.

From About.com

20
задан sternr 5 October 2014 в 02:48
поделиться

5 ответов

Это неприятная проблема, для которой я не знаю никакого элегантного решения.

Предположим, что у вас есть эти комбинации клавиш, и вы хотите только выделить отмеченные (*).

Id1 Id2 --- --- 1 2 * 1 3 1 6 2 2 * 2 3 * ... (many more)

Как сделать так, чтобы Entity Framework была счастлива?

Решение 1: Join (или Contains) с парами

Лучшим решением было бы создать список пар, который вы хотите, например, Tuples, (List<Tuple<int,int>>) и соединить данные базы данных с этим списком:

from entity in db.Table // db is a DbContext join pair in Tuples on new { entity.Id1, entity.Id2 } equals new { Id1 = pair.Item1, Id2 = pair.Item2 } select entity

В LINQ для объектов это было бы прекрасно, но слишком плохо, EF будет генерировать исключение, подобное

Невозможно создать постоянное значение типа «System.Tuple`2 (...) В этом контексте поддерживаются только примитивные типы или типы перечислений.

, который является довольно неуклюжим способом сказать вам, что он не может перевести этот оператор в SQL, потому что Tuples не является списком примитивных значений (например, int или string). 1 , По этой же причине аналогичное утверждение, использующее Contains (или любой другой оператор LINQ), потерпит неудачу.

Решение 2: In-memory

Конечно, мы могли бы превратить проблему в простой LINQ для таких объектов:

from entity in db.Table.AsEnumerable() // fetch db.Table into memory first join pair Tuples on new { entity.Id1, entity.Id2 } equals new { Id1 = pair.Item1, Id2 = pair.Item2 } select entity

Излишне говорить, что это нехорошее решение. db.Table может содержать миллионы записей.

Решение 3: Два Contains оператора

Итак, давайте предложим EF два списка примитивных значений, [1,2] для Id1 и [2,3] для Id2. Мы не хотим использовать join (см. Примечание к стороне), поэтому давайте использовать Contains:

from entity in db.Table where ids1.Contains(entity.Id1) && ids2.Contains(entity.Id2) select entity

Но теперь результаты также содержат сущность {1,3}! Ну, конечно, эта сущность идеально соответствует двум предикатам. Но давайте иметь в виду, что мы приближаемся.

Решение 4: Один Contains с вычисленными значениями

Решение 3 потерпело неудачу, поскольку два отдельных Contains не только фильтруют комбинации их значений. Что делать, если мы сначала создадим список комбинаций и попытаемся сопоставить эти комбинации? Из решения 1 известно, что этот список должен содержать примитивные значения. Например:

var computed = ids1.Zip(ids2, (i1,i2) => i1 * i2); // [2,6]

и оператор LINQ:

from entity in db.Table where computed.Contains(entity.Id1 * entity.Id2) select entity

Есть некоторые проблемы с этим подходом. Во-первых, вы увидите, что это также возвращает объект {1,6}. Комбинационная функция (a * b) не дает значений, которые однозначно идентифицируют пару в базе данных. Теперь мы могли бы создать список строк, таких как ["Id1=1,Id2=2","Id1=2,Id2=3]" и do

from entity in db.Table where computed.Contains("Id1=" + entity.Id1 + "," + "Id2=" + entity.Id2) select entity

(Это будет работать в EF6, а не в более ранних версиях).

Это становится довольно грязным. Но более важная проблема заключается в том, что это решение не поддается сопоставлению, а это означает: он обходит любые индексы базы данных на Id1 и Id2, которые могли быть использованы иначе. Это будет очень плохо.

Решение 5: Лучшее из 2 и 3

. Единственное жизнеспособное решение, которое я могу представить, это комбинация Contains и join в памяти: сначала сделайте оператор contains, как в решении 3. Помните, что он приблизился к тому, что мы хотели. Затем уточните результат запроса, объединив результат как список в памяти:

var rawSelection = from entity in db.Table where ids1.Contains(entity.Id1) && ids2.Contains(entity.Id2) select entity; var refined = from entity in rawSelection.AsEnumerable() join pair in Tuples on new { entity.Id1, entity.Id2 } equals new { Id1 = pair.Item1, Id2 = pair.Item2 } select entity;

Это не изящно, беспорядочно все-таки возможно, но пока это единственный масштабируемый 1 решение этой проблемы, которое я нашел, и применяется в моем собственном коде.

Решение 6: Построить запрос с предложениями OR

Используя построитель Predicate, такой как Linqkit или альтернативы, вы можете создать запрос, содержащий предложение OR для каждого элемента в списке комбинаций. Это может быть жизнеспособным вариантом для списков . С несколькими сотнями элементов запрос начнет работать очень плохо. Поэтому я не считаю это хорошим решением, если вы не можете быть на 100% уверены, что всегда будет небольшое количество элементов. [D33]

1 Как забавная заметка, EF создает инструкцию SQL, когда вы присоединяетесь к примитивному списку, так

from entity in db.Table // db is a DbContext join i in MyIntegers on entity.Id1 equals i select entity

Но сгенерированный SQL, ну, абсурд. Пример в реальной жизни, где MyIntegers содержит только 5 (!) Целых чисел, выглядит следующим образом:

SELECT [Extent1].[CmpId] AS [CmpId], [Extent1].[Name] AS [Name], FROM [dbo].[Company] AS [Extent1] INNER JOIN (SELECT [UnionAll3].[C1] AS [C1] FROM (SELECT [UnionAll2].[C1] AS [C1] FROM (SELECT [UnionAll1].[C1] AS [C1] FROM (SELECT 1 AS [C1] FROM ( SELECT 1 AS X ) AS [SingleRowTable1] UNION ALL SELECT 2 AS [C1] FROM ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1] UNION ALL SELECT 3 AS [C1] FROM ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2] UNION ALL SELECT 4 AS [C1] FROM ( SELECT 1 AS X ) AS [SingleRowTable4]) AS [UnionAll3] UNION ALL SELECT 5 AS [C1] FROM ( SELECT 1 AS X ) AS [SingleRowTable5]) AS [UnionAll4] ON [Extent1].[CmpId] = [UnionAll4].[C1]

Есть n-1 UNION s. Конечно, это не масштабируется вообще.

Позднее дополнение: где-то по дороге к версии EF 6.1.3 это значительно улучшилось. [F36] s стали проще, и они больше не вложены. Раньше запрос отказывался от менее 50 элементов в локальной последовательности (исключение SQL: делает .) Не вложенные UNION позволяют локальные последовательности до нескольких тысяч (!) Элементов ,

1 Поскольку оператор Contains является масштабируемым: Масштабируемый Содержит метод LINQ для SQL-бэкэнд

32
ответ дан Community 15 August 2018 в 14:01
поделиться
  • 1
    Это (решение 5) - это то, что я в конце концов сделал, но это похоже на ужасный способ сделать это ... – sternr 5 October 2014 в 14:43
  • 2
    Это. Корень проблемы заключается в том, что нам приходится иметь дело с отчаянно устаревшей спецификацией языка (SQL), которая никогда не предоставляла способ присоединиться к ad hoc многомерному списку в одном утверждении (как мы можем сделать с простым списком с помощью оператора IN), , Существуют специфические для RDBMS задачи или исправления (у Oracle действительно хороший), но EF, вероятно, не будет инвестировать в их реализацию. – Gert Arnold 5 October 2014 в 18:06
  • 3
    Для того, чтобы быть ясным, .Contains выдаст исключение, если коллекция, в которой вы его используете, превышает 2100 элементов ( здесь ). – Daniel 8 April 2015 в 18:12
  • 4
    Большое спасибо за этот очень проницательный ответ. – Dirk Boer 16 October 2015 в 21:07
  • 5

Вы можете создать коллекцию строк с такими ключами, как это (я предполагаю, что ваши ключи являются int-типом):

var id1id2Strings = listOfIds.Select(p => p.Id1+ "-" + p.Id2);

Тогда вы можете просто использовать «Содержит» на вашем db: [ ! d1] using (dbEntities context = new dbEntities()) { var rec = await context.Table1.Where(entity => id1id2Strings .Contains(entity.Id1+ "-" + entity.Id2)); return rec.ToList(); }

0
ответ дан juanora 15 August 2018 в 14:01
поделиться

Вам нужен набор объектов, представляющих ключи, которые вы хотите запросить.

class Key { int Id1 {get;set;} int Id2 {get;set;}

Если у вас есть два списка, и вы просто проверяете, что каждое значение отображается в их соответствующем списке, тогда вы получаете декартову продукт списков, что, скорее всего, не то, что вы хотите. Вместо этого вам нужно запросить конкретные комбинации, необходимые

List<Key> keys = // get keys; context.Table.Where(q => keys.Any(k => k.Id1 == q.Id1 && k.Id2 == q.Id2));

Я не совсем уверен, что это действительное использование Entity Framework; у вас могут возникнуть проблемы с отправкой типа Key в базу данных. Если это произойдет, вы можете быть творческими:

var composites = keys.Select(k => p1 * k.Id1 + p2 * k.Id2).ToList(); context.Table.Where(q => composites.Contains(p1 * q.Id1 + p2 * q.Id2));

Вы можете создать изоморфную функцию (простые числа хороши для этого), что-то вроде хэш-кода, которое вы можете использовать для сравнения пары значений. Пока мультипликативные факторы являются совместными, этот шаблон будет изоморфным (взаимно однозначным), т. Е. Результат p1*Id1 + p2*Id2 однозначно идентифицирует значения Id1 и Id2, если простые числа правильно выбран.

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

0
ответ дан Kirk Broadhurst 15 August 2018 в 14:01
поделиться
  • 1
    Я должен проверить ваше первое решение, но что касается второго - пока он будет работать, это приведет к полному сканированию таблицы вместо того, чтобы использовать прямой запрос к ключу – sternr 5 October 2014 в 09:28
  • 2
    @sternr абсолютно правильно. Это очень неприятно. Обратите внимание на мое последнее замечание, что вам лучше писать хранимую процедуру. – Kirk Broadhurst 5 October 2014 в 22:26

В отсутствие общего решения, я думаю, есть две вещи, которые следует учитывать:

Избегайте многоколоночных первичных ключей (также упростит модульное тестирование). Но если вам нужно, возможно, что один из них уменьшит размер результата запроса до O (n), где n - размер идеального результата запроса. Отсюда его Решение 5 от Герда Арнольда выше.

Например, проблема, ведущая меня к этому вопросу, заключалась в том, чтобы запросить строки заказа, где ключ - идентификатор заказа + номер строки заказа + тип заказа, а источник имел тип заказа неявным. То есть тип заказа был константой, идентификатор заказа уменьшал бы запрос, заданный для порядка строк соответствующих заказов, и обычно их было бы 5 или меньше.

Перефразировать: если у вас есть составной ключ, изменения в том, что один из них имеет очень мало дубликатов. Примените решение 5 сверху.

-1
ответ дан Robert Jørgensgaard Engdahl 15 August 2018 в 14:01
поделиться

В случае составного ключа вы можете использовать другой идентификатор и добавить условие для этого в свой код

context.Table.Where(q => listOfIds.Contains(q.Id) && listOfIds2.Contains(q.Id2));

или вы можете использовать трюк друг друга, создавая список своих ключей, добавляя их

listofid.add(id+id1+......) context.Table.Where(q => listOfIds.Contains(q.Id+q.id1+.......));
0
ответ дан user 15 August 2018 в 14:01
поделиться
  • 1
    Первый запрос неверен (поскольку он предполагает уникальность для обоих значений), а второй ошибочный, но приведет к полному сканированию таблицы – sternr 5 October 2014 в 09:27
  • 2
    да, я знаю, что первый из них неправильный, но второй, я думаю, что это сработает, дайте мне пример данных и результат u want ... – user 5 October 2014 в 14:07
  • 3
    Извините, я имею в виду, что второй не ошибается, но это приведет к полному сканированию таблицы, что неприемлемо ... – sternr 5 October 2014 в 14:42
Другие вопросы по тегам:

Похожие вопросы: