Страницы

Поиск по вопросам

среда, 10 октября 2018 г.

Различия методов Find(), FirstOrDefault() при использовании с Entity Framework

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


Ответ

Для поиска и получения объекта сущности можно воспользоваться методами:
Find(), First(), FirstOrDefault(), Single(), SingleOrDefault().
Рассмотрим каждый:
Метод Find() принимает в качестве параметра первичный ключ записи.
Его особенность состоит в том, что в отличие от остальных методов, он сначала обращается к памяти и ищет запись среди объектов контекста, отслеживаемых EntityFramework'ом, и только потом (если ничего не нашел) выполняет запрос к бд. Если в контексте найдется объект, который еще не сохранен в базе, метод Find() все равно вернет его (с состоянием Added). Если запись не будет найдена ни там ни там, вернет null
Генерируемый SQL запрос:
SELECT TOP (2) [Extent1].[Id] AS [Id], [Extent1].[Title] AS [Title], FROM [dbo].[Topics] AS [Extent1] WHERE [Extent1].[Id] = @p0
Стоит обратить внимание, что в выборку попадает не одна запись, а две SELECT TOP (2). Это используется внутренними механизмами EF для проверки уникальности записи. Если в результате окажется более одной записи с одним и тем же первичным ключем, EF сгенерирует исключение:
System.InvalidOperationException: "Последовательность содержит более одного элемента" Метод FirstOrDefault() принимает в качестве параметра предикат, что дает возможность искать не только по первичному ключу, но и по любому составленному условию.
Например, найдем запись, у которой Title == "test"
var topic = context.Topics.FirstOrDefault(topic => topic.Title == "test");
FirstOrDefault() в отличии от Find() каждый раз выполняет запрос к базе, не зависимо от того есть ли данные в контексте. Если из базы придет запись, отличающаяся от той что лежит в контексте, EF вернет запись из контекста. Если записи не окажется в базе, вернет null, даже если она есть в контексте.
Генерируемый SQL запрос:
SELECT TOP (1) [Extent1].[Id] AS [Id], [Extent1].[Title] AS [Title], FROM [dbo].[Topics] AS [Extent1] WHERE [Extent1].[Id] = @p__linq__0
Тут ищет только одну запись SELECT TOP (1). Если окажется что записей несколько, вернет первую. Метод First() аналогичен FirstOrDefault() с одним отличием - если запись не найдена, будет сгенерировано исключение:
System.InvalidOperationException: "Последовательность не содержит элементов" Метод SingleOrDefault() аналогичен FirstOrDefault() и возвращает объект сущности, либо null. Однако запрос генерирует подобный методу Find()
SELECT TOP (2) [Extent1].[Id] AS [Id], [Extent1].[Title] AS [Title], FROM [dbo].[Topics] AS [Extent1] WHERE [Extent1].[Id] = @p__linq__0
И при обнаружении более одной записи выкидывает исключение:
System.InvalidOperationException: "Последовательность содержит более одного элемента" Метод Single() аналогичен SingleOrDefault() и отличается тем, что если запись не найдена, будет сгенерировано исключение:
System.InvalidOperationException: "Последовательность не содержит элементов"

Подведем итоги:
Когда использовать Find()?
Когда искать нужно по первичному ключу и выбрать нужно все данные сущности. Find() не выполняет запрос, если запись уже загружена в контекст и, как следствие, будет превосходить в производительности все остальные методы. Однако если нужно выбрать какие-то определенные поля (например только Id и Title), придется воспользоваться остальными методами. То же касается и подгрузки зависимых данных (например через Include()) - Find() это сделать не даст. Когда использовать остальные методы?
Если нужна выборка определенных полей сущности или нужно также загрузить зависимые данные. Какие именно методы использовать зависит от индивидуальных требований: если вам нужно проверить существование записи (без генерации исключений), подойдут методы *OrDefault()
var topic = context.Topics .Select(topic => new { topic.Id, topic.Title }) .FirstOrDefault(topic => topic.Id == 44); if (topic == null) { // ... }
Если предполагается, что контекст может содержать локальные копии данных, можно получить их оттуда, не выполняя запроса к базе. Для этого нужно обратиться к свойству Local объекта DbSet
Var topic = context.Topics.Local.FirstOrDefault(topic => topic.Id == topicId) ?? context.Topics.FirstOrDefault(topic => topic.Id == topicId);

Используемые источники:
MSDN: Entity Framework Querying and Finding Entities Primer on Selecting Data Using Entity Framework StackOverflow: Are Find and Where().FirstOrDefault() equivalent? StackOverflow: Using .Find() & .Include() on the same query

Комментариев нет:

Отправить комментарий