Entity framework where несколько
Совсем недавно при разработке проекта с версионностью, я столкнулся с проблемой выборки элементов по списку составных ключей.
Описание проблемы:
При разработке «в условиях» версионности primary key таблиц состоит из Id и Revision. Нужно получить выборку из таблицы БД по передаваемому списку составных ключей (Id, Revision). Такой SQL запрос выглядел бы так (для пяти элементов в списке):
Но Entity Framework не позволяет написать такой запрос для списка составных ключей. Максимум что можно сделать стандартными средствами это:
что превратится в такой запрос (концептуально):
Этот запрос будет выдавать неверные результаты, если таблице Items есть элементы с такими идентификаторами:
Id = 3, Revision = 2
Id = 3, Revision = 4
А в списке составных ключей есть такие строчки:
Id = 5, Revision = 4
Id = 3, Revision = 2
Так как же быть?
На просторах интернета, для ORM, распространен следующий метод:
Нужно объединить Id и Revision в БД и в списке и сравнивать по получившемуся значению. Под объединением подразумевается конкатенация строк, или, если Id и Revision имеют тип int, то смещение и получение типа long (bigint).
Для случая с конкатенацией:
Если сравнивать «Запрос 1» и «Запрос 3», то для выполнения последнего нужно строить дополнительный столбец (причем для его построения нужно провести 2 операции преобразования типа и 2 операции конкатенации). А в «Запрос 1» используются только операции сравнения. Исходя из этого, я предполагаю что «Запрос 1» дешевле.
Но MSSQL 2008 R2 выдает по обоим запросам абсолютно одинаковый Execution Plan (для запросов в том виде, в котором они представлены здесь).
Итак, как же заставить Entity Framework составить запрос в таком же виде как «Запрос 1».
Entity Framework позволяет написать такой запрос для определенного набора составных ключей:
Затем скопировать его необходимое количество раз, подставляя вместо list.Id и list.Revision необходимые значения из списка в виде констант и потом собрать их в одно через операцию, например, ||.
- Left (тип Expression) — левая часть
- Right (тип Expression) — правая часть
- NodeType (тип ExpressionType) — операция (OR, AND, и т.д.)
После этого у нас получится список выражений, которые нужно будет собрать в одно.
Самый простой вариант – это собирать их поочередно:
Но возникает одна серьезная проблема. Каждое выражений, подставляемое справа сравнивается со всем выражением слева, т.е. получается вот такое выражение:
В этом выражении количество дополнительных скобок в начале, будет равно количеству элементов в списке. И все бы ничего, то при разборе этого выражения, для построения SQL запроса, Entity Framework использует рекурсию для прохода вглубь выражения, и, при ~1000 элементах в списке (практические наблюдения) вылетает StackOverflowException. Кстати этой же проблемой страдает довольно интересный проект LINQ Dynamic Query Library который я пытался использовать у себя, но отказался из-за вышеуказанной проблемы.
Но эту проблему можно победить! Для этого нужно строить выражение не подставляя элементы справа, и строя его как бинарное дерево:
Выражение, полученное этим способом, не вызывает StackOverflowException даже при 1 000 000 элементов в списке (больше не проверял, так как уже при таком количестве параметров SQL Server отказывается выполнять запрос за адекватное время).
На основе всего этого я сделал extension методы, перегружающие метод Where и пользуюсь им в своих проектах.
Чтобы поделиться этими наработками я создал проект на codeplex, куда выложил исходники EFCompoundkeyWhere
В этом разделе рассматриваются различные способы запроса данных с помощью Entity Framework, включая LINQ и метод Find. Методы, представленные в этом разделе, также применимы к моделям, созданным с помощью Code First и конструктора EF.
GroupJoin
The LINQ GroupJoin operator allows you to connect two data sources similar to Join, but it creates a group of inner values for matching outer elements. Executing a query like the following example generates a result of Blog & IEnumerable . Since databases (especially relational databases) don't have a way to represent a collection of client-side objects, GroupJoin doesn't translate to the server in many cases. It requires you to get all of the data from the server to do GroupJoin without a special selector (first query below). But if the selector is limiting data being selected then fetching all of the data from the server may cause performance issues (second query below). That's why EF Core doesn't translate GroupJoin.
SelectMany
Оператор SelectMany в LINQ позволяет перечислять селектор коллекции для каждого внешнего элемента и создавать кортежи значений из каждого источника данных. Таким образом, это соединение, но без каких-либо условий, поэтому каждый внешний элемент соединяется с элементом из источника коллекции. В зависимости от того, как селектор коллекции связан с внешним источником данных, оператор SelectMany может преобразовываться в различные запросы на стороне сервера.
Left Join
While Left Join isn't a LINQ operator, relational databases have the concept of a Left Join which is frequently used in queries. A particular pattern in LINQ queries gives the same result as a LEFT JOIN on the server. EF Core identifies such patterns and generates the equivalent LEFT JOIN on the server side. The pattern involves creating a GroupJoin between both the data sources and then flattening out the grouping by using the SelectMany operator with DefaultIfEmpty on the grouping source to match null when the inner doesn't have a related element. The following example shows what that pattern looks like and what it generates.
The above pattern creates a complex structure in the expression tree. Because of that, EF Core requires you to flatten out the grouping results of the GroupJoin operator in a step immediately following the operator. Even if the GroupJoin-DefaultIfEmpty-SelectMany is used but in a different pattern, we may not identify it as a Left Join.
При выборке данных выбирать нужно ровно столько сколько нужно за один раз. Никогда не извлекайте все данные из таблицы!
Встроенная проверка данных может быть выполнена, когда запрос вернул какие-то записи.
Вау, вау, вау, разогнался.
Самое время немного освежить знания по методам LINQ.
Давайте рассмотрим отличия между ToList AsEnumerable AsQueryable
Итак, ToList
- Выполняет запрос немедленно.
- Используйте .ToList() для форсирования получения данных и выхода из режима поздней загрузки (lazy loading), так что этот метод полезен перед тем как вы пройдетесь по данным.
- Выполнение с задержкой (lazy loading)
- Принимает параметр: Func
- Загружает каждую запись в память приложения и управляет фильтрует его (в том числе Where/Take/Skip приведут к тому, что, например запрос select * from Table1,
- загрузит результирующий набор в память, затем выберет первые N элементов)
- В этом случает отрабатывает схема: Linq-to-SQL + Linq-to-Object.
- Используйте IEnumerable для получения списка из базы данных в режиме поздней загрузки (lazy loading).
- Выполнение с задержкой (lazy loading)
- Преобразует Expression в T-SQL (с учетом специфики провайдера), удаленное исполняет запрос и возвращает результат в память приложения.
- Вот почему DbSet (в Entity Framework) также наследуется от AsQueryable чтобы получать эффективные запросы.
- Не загружает каждую запись, например если Take(5) это сгенерирует запрос вида «select top 5 * SQL» в фоновом режиме. Это означает, что этот подход более дружественный для SQL базы данных, и дает более скоростной результат.Так что AsQueryable() обычно работает быстрее, чем AsEnumerable() так как сначала генерирует T-SQL включающий в себя все условия Linq определённые вами.
- Используйте AsQueryable если хотите запрос к базе данных который может быть улучшен перед запуском на стороне сервера.
Пример использования AsQueryable в простейшеем случае:
Волшебство простого чтения
Если вам не нужно менять данные, только отобразить используйте .AsNoTracking() метод.
Медленная выборка
Быстрая выборка (только на чтение)
Чувствую, вы немного уже размялись?
Типы загрузки связанных данных
Для тех, кто забыл, что такое lazy loading.
Ленивая загрузка (Lazy loading) означает, что связанные данные прозрачно загружаются из базы данных при обращении к свойству навигации. Подробнее читаем тут .
И заодно, напомню о других типах загрузки связанных данных.
Активная загрузка (Eager loading) означает, что связанные данные загружаются из базы данных как часть первоначального запроса.
Внимание! Начиная с версии EF Core 3.0.0, каждое Include будет вызывать добавление дополнительного JOIN к запросам SQL, создаваемым реляционными поставщиками, тогда как предыдущие версии генерировали дополнительные запросы SQL. Это может значительно изменить производительность ваших запросов, в лучшую или в худшую сторону. В частности, запросы LINQ с чрезвычайно большим числом операторов включения могут быть разбиты на несколько отдельных запросов LINQ.
Явная загрузка (Explicit loading) означает, что связанные данные явно загружаются из базы данных позднее.
Рывок и прорыв! Двигаемся дальше?
Готовы ускориться еще больше?
Чтобы резко ускориться при выборке сложно структурированных и даже ненормализованных данных из реляционной базы данных есть два способа сделать это: используйте индексированные представления (1) или что еще лучше – предварительно подготовленные(вычисленные) данные в простой плоской форме для отображения (2).
(1) Индексированное представление в контексте MS SQL Server
Индексированное представление имеет уникальный кластеризованный индекс. Уникальный кластерный индекс хранится в SQL Server и обновляется, как и любой другой кластерный индекс. Индексированное представление является более значительным по сравнению со стандартными представлениями, которые включают сложную обработку большого количества строк, например, агрегирование большого количества данных или объединение множества строк.
Если на такие представления часто ссылаются в запросах, мы можем повысить производительность, создав уникальный кластеризованный индекс для представления. Для стандартного представления набор результатов не сохраняется в базе данных, вместо этого набор результатов вычисляется для каждого запроса, но в случае кластеризованного индекса набор результатов сохраняется в базе данных точно так же, как таблица с кластеризованным индексом. Запросы, которые специально не используют индексированное представление, могут даже выиграть от существования кластеризованного индекса из представления.
Представление индекса имеет определенную стоимость в виде производительности. Если мы создаем индексированное представление, каждый раз, когда мы изменяем данные в базовых таблицах, SQL Server должен поддерживать не только записи индекса в этих таблицах, но также и записи индекса в представлении. В редакциях SQL Server для разработчиков и предприятий оптимизатор может использовать индексы представлений для оптимизации запросов, которые не указывают индексированное представление. Однако в других выпусках SQL Server запрос должен включать индексированное представление и указывать подсказку NOEXPAND, чтобы получить преимущество от индекса в представлении.
(2) Если нужно сделать запрос, требующий отображения более трех уровней связанных таблиц в количестве три и более c повышенной CRUD нагрузкой, лучшим способом будет задуматься о том, чтобы периодически вычислять результирующий набор, сохранять его в таблице и использовать для отображения. Результирующая таблица, в которой будут сохраняться данные должна иметь Primary Key и индексы по полям поиска в LINQ.
Что насчет асинхронности?
Да! Используем ее где только можно! Вот пример:
И да, ничего не забыли для повышения производительности? Бууум!
Внимание: метод Do() добавлен для демонстрационных целей только, с целью указать работоспособность метода GetFederalDistrictsAsync(). Как правильно заметили мои коллеги тутнужен другой пример чистой асинхронности.
Напомню, когда выполняются запросы в Entity Framework Core.
При вызове операторов LINQ вы просто создаете представление запроса в памяти. Запрос отправляется в базу данных только после обработки результатов.
Ниже приведены наиболее распространенные операции, которые приводят к отправке запроса в базу данных.
- Итерация результатов в цикле for.
- Использование оператора, например ToList, ToArray, Single, Count.
- Привязка данных результатов запроса к пользовательскому интерфейсу.
Как же организовать код EF Core с точки зрения архитектуры приложения?
(1) C точки зрения архитектуры приложения, нужно обеспечить чтобы код доступа к вашей базе данных был изолирован / отделен в четко определенном месте (в изоляции). Это позволяет найти код базы данных, который влияет на производительность.
(2) Не смешивать код доступа к вашей базе данных с другими частями приложения, такими как пользовательский интерфейс или API. Таким образом, код доступа к базе данных можно изменить, не беспокоясь о других проблемах, не связанных с базой данных.
Как правильно и быстро сохранять данные с помощью SaveChanges?
Если вставляемые записи одинаковые имеет смысл использовать одну операцию сохранения на все записи.
Неправильно
Всегда есть исключения из правила. Если контекст транзакции сложный, то есть состоит из нескольких независимых операций, то можно выполнять сохранение после выполнения каждой операции. А еще правильней использовать асинхронное сохранение в транзакции.
Триггеры, вычисляемые поля, пользовательские функции и EF Core
Для снижения нагрузки на приложения содержащим EF Core имеет смысл применять простые вычисляемые поля и триггеры баз данных, но лучше этим не увлекаться, так как приложение может оказаться очень запутанным. А вот пользовательские функции могут быть очень полезны особенно при операциях выборки!
Параллелизм в EF Core
Если ты хочешь все запараллелить чтобы ускориться, то обломись: EF Core не поддерживает выполнение нескольких параллельных операций в одном экземпляре контекста. Следует подождать завершения одной операции, прежде чем запускать следующую. Для этого обычно нужно указать ключевое слово await в каждой асинхронной операции.
EF Core использует асинхронные запросы, которые позволяют избежать блокирования потока при выполнении запроса в базе данных. Асинхронные запросы важны для обеспечения быстрого отклика пользовательского интерфейса в толстых клиентах. Они могут также увеличить пропускную способность в веб-приложении, где можно высвободить поток для обработки других запросов. Вот пример:
А что вы знаете про компилированные запросы LINQ?
Если у вас есть приложение, которое многократно выполняет структурно похожие запросы в Entity Framework, вы часто можете повысить производительность, компилируя запрос один раз и выполняя его несколько раз с различными параметрами. Например, приложению может потребоваться получить всех клиентов в определенном городе; город указывается во время выполнения пользователем в форме. LINQ to Entities поддерживает использование для этой цели скомпилированных запросов.
Много примеров можно посмотреть тут.
Не делайте больших контекстов DbContext!
В общем так, я знаю многие из вас, если не почти все — lazy f_u__c_k__e_r__s и всю базу данных вы размещаете в один контекст, особенно это свойственно для подхода Database-First. И зря вы это делаете! Ниже приведен пример как можно разделить контекст. Конечно, таблицы соединения между контекстами придется дублировать, это минус. Так или иначе если у вас в контексте более 50 таблиц лучше подумать о его разделении.
Использование группировки контекста (pooling DdContext)
Как избежать лишних ошибок при CRUD в EF Core?
Никогда не делайте вычисления в вставку в одном коде. Всегда разделяйте формирование/подготовку объекта и его вставку/обновление. Просто разнесите по функциям: проверку введенных данным пользователем, вычисления необходимые предварительных данных, картирование или создание объекта, и собственно CRUD операцию.
Что делать, когда совсем дела плохо с производительностью приложения?
Пиво тут точно не поможет. А вот что поможет, так это разделение чтение и записи в архитектуре приложения с последующего разнесением по сокетам этих операций. Задумайтесь об использовании Command and Query Responsibility Segregation (CQRS) pattern, а также попробуйте, разделить таблицы на вставку и чтение между двумя базами данных.
Collection selector references outer in a non-where case
When the collection selector references the outer element, which isn't in a where clause (as the case above), it doesn't translate to a database join. That's why we need to evaluate the collection selector for each outer element. It translates to APPLY operations in many relational databases. If the collection is empty for an outer element, then no results would be generated for that outer element. But if DefaultIfEmpty is applied on the collection selector then the outer element will be connected with a default value of the inner element. Because of this distinction, this kind of queries translates to CROSS APPLY in the absence of DefaultIfEmpty and OUTER APPLY when DefaultIfEmpty is applied. Certain databases like SQLite don't support APPLY operators so this kind of query may not be translated.
Селектор коллекции не ссылается на внешний источник
Если селектор коллекции не ссылается на элементы во внешнем источнике, результатом является декартово произведение обоих источников данных. В реляционных базах данных такой оператор преобразуется в CROSS JOIN .
Collection selector doesn't reference outer
When the collection selector isn't referencing anything from the outer source, the result is a cartesian product of both data sources. It translates to CROSS JOIN in relational databases.
GroupBy
Операторы GroupBy в LINQ создают результат типа IGrouping , где TKey и TElement могут быть произвольного типа. Кроме того, IGrouping реализует интерфейс IEnumerable . Это означает, что после группирования можно производить композицию с помощью любого оператора LINQ. Так как ни одна структура базы данных не может представлять IGrouping , в большинстве случаев операторы GroupBy не преобразуются. Если к каждой группе применяется статистический оператор, возвращающий скалярное значение, в реляционных базах данных его можно преобразовать в оператор SQL GROUP BY . Применение оператора SQL GROUP BY также ограничено. Он требует выполнять группирование только по скалярным значениям. Проекция может содержать только ключевые столбцы группирования или любое статистическое выражение, примененное к столбцу. EF Core распознает этот шаблон и преобразует его на стороне сервера, как показано в следующем примере:
EF Core также преобразует запросы, в которых статистический оператор, выполняющий действия над группированием, включен в оператор LINQ Where или OrderBy (либо другое упорядочение). Для предложения WHERE в SQL используется предложение HAVING . Часть запроса перед применением оператора GroupBy может быть любым сложным запросом, который может быть преобразован на стороне сервера. Кроме того, после применения статистических операторов к запросу группирования для удаления группирований из результирующего источника этот запрос можно использовать для композиции, как и любой другой запрос.
EF Core поддерживает следующие статистические операторы:
Селектор коллекции ссылается на внешний источник не в предложении WHERE
Если селектор коллекции ссылается на внешний элемент, который не входит в предложение WHERE (как в случае выше), он не преобразуется в соединение базы данных. Поэтому необходимо оценить селектор коллекции для каждого внешнего элемента. Во многих реляционных базах данных такой оператор преобразуется в операции APPLY . Если коллекция для внешнего элемента пуста, то для него результаты отсутствуют. Но если к селектору коллекции применяется DefaultIfEmpty , внешний элемент соединяется со значением внутреннего элемента по умолчанию. В связи с этим различием запросы такого типа преобразуются в CROSS APPLY при отсутствии DefaultIfEmpty и в OUTER APPLY , если DefaultIfEmpty применяется. Некоторые базы данных, такие как SQLite, не поддерживают операторы APPLY , поэтому запросы такого типа могут не преобразовываться.
GroupBy
LINQ GroupBy operators create a result of type IGrouping where TKey and TElement could be any arbitrary type. Furthermore, IGrouping implements IEnumerable , which means you can compose over it using any LINQ operator after the grouping. Since no database structure can represent an IGrouping , GroupBy operators have no translation in most cases. When an aggregate operator is applied to each group, which returns a scalar, it can be translated to SQL GROUP BY in relational databases. The SQL GROUP BY is restrictive too. It requires you to group only by scalar values. The projection can only contain grouping key columns or any aggregate applied over a column. EF Core identifies this pattern and translates it to the server, as in the following example:
EF Core also translates queries where an aggregate operator on the grouping appears in a Where or OrderBy (or other ordering) LINQ operator. It uses HAVING clause in SQL for the where clause. The part of the query before applying the GroupBy operator can be any complex query as long as it can be translated to server. Furthermore, once you apply aggregate operators on a grouping query to remove groupings from the resulting source, you can compose on top of it like any other query.
The aggregate operators EF Core supports are as follows
GroupJoin
Оператор GroupJoin в LINQ позволяет соединять два источника данных, так же как оператор JOIN, но создает группу внутренних значений для соответствующих внешних элементов. Выполнение приведенного ниже запроса дает результат Blog & IEnumerable . Так как базы данных (особенно реляционные) обычно не позволяют представлять коллекцию объектов на стороне клиента, GroupJoin во многих случаях не преобразуется на сервере. Для выполнения GroupJoin без специального селектора требуется получить все данные с сервера (первый запрос ниже). Но если селектор ограничивает выбор данных, то получение всех данных с сервера может вызвать проблемы с производительностью (второй запрос ниже). Вот почему EF Core не преобразует оператор GroupJoin.
Селектор коллекции ссылается на внешний источник в предложении WHERE
Если в селекторе коллекции есть предложение WHERE, которое ссылается на внешний элемент, то EF Core преобразует его в соединение базы данных и использует предикат в качестве условия соединения. Обычно такое бывает при использовании свойства навигации по коллекции для внешнего элемента в качестве селектора коллекции. Если коллекция для внешнего элемента пуста, то для него результаты отсутствуют. Но если к селектору коллекции применяется DefaultIfEmpty , внешний элемент соединяется со значением внутреннего элемента по умолчанию. В связи с этим различием запросы такого типа преобразуются в INNER JOIN при отсутствии DefaultIfEmpty и в LEFT JOIN , если DefaultIfEmpty применяется.
Поиск сущностей с помощью первичных ключей
Метод Find в классе DbSet использует значение первичного ключа, чтобы найти сущность, отслеживаемую контекстом. Если сущность не найдена в контексте, запрос отправляется в базу данных для поиска сущности там. Если сущность не найдена в контексте или в базе данных, возвращается значение NULL.
Метод Find имеет два важных отличия от запроса:
- Круговой путь к базе данных будет использоваться только в том случае, если сущность с указанным ключом не найдена в контексте.
- Метод Find возвращает сущности в состоянии "Добавлено". Это значит, что метод Find возвращает сущности, которые были добавлены в контекст, но еще не были сохранены в базе данных.
Левое соединение
Хотя в LINQ нет оператора левого соединения, в запросах к реляционным базам данных левое соединение используется часто. Определенный шаблон в запросах LINQ дает тот же результат, что и оператор LEFT JOIN на сервере. EF Core распознает такой шаблон и создает эквивалентный оператор LEFT JOIN на стороне сервера. Шаблон предполагает создание соединения GroupJoin между двумя источниками данных и последующее преобразование группирования в плоскую структуру с помощью оператора SelectMany с DefaultIfEmpty, применяемого к источнику группирования, для сопоставления со значением NULL, когда во внутреннем источнике нет соответствующего элемента. В приведенном ниже примере показано, как выглядит этот шаблон и какой результат он дает.
Приведенный выше шаблон создает сложную структуру в дереве выражения. По этой причине EF Core требует преобразовать результаты группирования с помощью оператора GroupJoin в плоскую структуру сразу после этого оператора. Если конструкция GroupJoin-DefaultIfEmpty-SelectMany используется в рамках другого шаблона, она может не распознаваться как левое соединение.
Language Integrated Query (LINQ) contains many complex operators, which combine multiple data sources or does complex processing. Not all LINQ operators have suitable translations on the server side. Sometimes, a query in one form translates to the server but if written in a different form doesn't translate even if the result is the same. This page describes some of the complex operators and their supported variations. In future releases, we may recognize more patterns and add their corresponding translations. It's also important to keep in mind that translation support varies between providers. A particular query, which is translated in SqlServer, may not work for SQLite databases.
You can view this article's sample on GitHub.
The LINQ Join operator allows you to connect two data sources based on the key selector for each source, generating a tuple of values when the key matches. It naturally translates to INNER JOIN on relational databases. While the LINQ Join has outer and inner key selectors, the database requires a single join condition. So EF Core generates a join condition by comparing the outer key selector to the inner key selector for equality.
Further, if the key selectors are anonymous types, EF Core generates a join condition to compare equality component-wise.
Поиск сущностей с помощью запроса
DbSet и IDbSet реализуют IQueryable, поэтому их можно использовать как начальную точку для написания запроса LINQ к базе данных. Здесь мы не будем подробно рассматривать LINQ, но приведем несколько простых примеров:
Обратите внимание, что DbSet и IDbSet всегда создают запросы к базе данных и всегда используют круговой путь к базе данных, даже если возвращаемые сущности уже присутствуют в контексте. Запрос выполняется в базе данных, если:
Когда из базы данных возвращаются результаты, объекты, отсутствующие в контексте, присоединяются к контексту. Если объект уже есть в контексте, возвращается существующий объект (текущие и исходные значения свойств объекта в записи не переписываются значениями из базы данных).
Когда вы выполняете запрос, сущности, которые были добавлены в контекст, но еще не были сохранены в базе данных, не возвращаются в составе результирующего набора. Чтобы получить данные из контекста, см. раздел Локальные данные.
Если запрос не возвращает строки из базы данных, результатом будет пустая коллекция, а не NULL.
Поиск сущности по первичному ключу
В коде ниже приведено несколько примеров использования метода Find.
Collection selector references outer in a where clause
When the collection selector has a where clause, which references the outer element, then EF Core translates it to a database join and uses the predicate as the join condition. Normally this case arises when using collection navigation on the outer element as the collection selector. If the collection is empty for an outer element, then no results would be generated for that outer element. But if DefaultIfEmpty is applied on the collection selector then the outer element will be connected with a default value of the inner element. Because of this distinction, this kind of queries translates to INNER JOIN in the absence of DefaultIfEmpty and LEFT JOIN when DefaultIfEmpty is applied.
SelectMany
The LINQ SelectMany operator allows you to enumerate over a collection selector for each outer element and generate tuples of values from each data source. In a way, it's a join but without any condition so every outer element is connected with an element from the collection source. Depending on how the collection selector is related to the outer data source, SelectMany can translate into various different queries on the server side.
Поиск сущности по составному первичному ключу
Платформа Entity Framework позволяет сущностям иметь составные ключи, то есть ключи, состоящие из нескольких свойств. Например, вы можете создать сущность BlogSettings, которая представляет собой параметры пользователей для конкретного блога. Так как пользователю необходима только одна сущность BlogSettings для каждого блога, первичный ключ для BlogSettings может состоять из комбинации идентификатора блога и имени пользователя. Следующий код пытается найти BlogSettings по идентификатору = 3 и имени пользователя = johndoe1987:
Если у вас есть составные ключи, вам нужно использовать ColumnAttribute или текучий API, чтобы указать порядок свойств составного ключа. В вызове метода Find эти значения ключа должны указываться в том же порядке.
В языке LINQ есть множество сложных операторов, которые объединяют несколько источников данных или производят сложную обработку. Не для всех операторов LINQ есть подходящие преобразования на стороне сервера. Иногда запрос в одной форме преобразуется на сервере, но в другой — не преобразуется, даже если результат совпадает. На этой странице описываются некоторые сложные операторы и их поддерживаемые варианты. В будущих выпусках, возможно, будет поддерживаться больше шаблонов и будут добавлены соответствующие преобразования. Также важно иметь в виду, что поддержка преобразований зависит от поставщика. Запрос, который преобразуется в SqlServer, может не работать в базах данных SQLite.
Для этой статьи вы можете скачать пример из репозитория GitHub.
Оператор Join в LINQ позволяет соединять два источника данных на основе селектора ключа для каждого источника, создавая кортеж значений при совпадении ключей. В реляционных базах данных он естественным образом преобразуется в INNER JOIN . Если оператор Join в LINQ имеет внешний и внутренний селекторы ключей, база данных требует одного условия соединения. Таким образом, EF Core создает условие соединения, сравнивая внешний и внутренний селекторы ключей на равенство.
Кроме того, если селекторы ключей являются анонимными типами, EF Core создает условие соединения для покомпонентного сравнения на равенство.
Читайте также: