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

393
18 июля 2017, 17:21

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

В чем их разница и когда какой метод использовать?

Answer 1

Для поиска и получения объекта сущности можно воспользоваться методами:

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: "Последовательность не содержит элементов"

Подведем итоги:

  1. Когда использовать Find()?

    Когда искать нужно по первичному ключу и выбрать нужно все данные сущности. Find() не выполняет запрос, если запись уже загружена в контекст и, как следствие, будет превосходить в производительности все остальные методы. Однако если нужно выбрать какие-то определенные поля (например только Id и Title), придется воспользоваться остальными методами. То же касается и подгрузки зависимых данных (например через Include()) - Find() это сделать не даст.

  2. Когда использовать остальные методы?

    Если нужна выборка определенных полей сущности или нужно также загрузить зависимые данные. Какие именно методы использовать зависит от индивидуальных требований: если вам нужно проверить существование записи (без генерации исключений), подойдут методы *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
READ ALSO
c# blend создание кнопок

c# blend создание кнопок

Как в Blend с помощью кода создать кнопку и присвоить ей шаблон программно ?

289
Альтернатива VBMath.Rnd() в C#

Альтернатива VBMath.Rnd() в C#

Рассматривал пример программы, в которой весь код написан на C#, а в одной функции используется фрагмент, написанный на VBВопрос в том, как перевести...

240
ELMA - передать значение переменной JS в контекстную

ELMA - передать значение переменной JS в контекстную

В общем, вопрос в комментариях

248
Изменения ячейки по индексу в dataGrid WPF

Изменения ячейки по индексу в dataGrid WPF

Подскажите пожалуйста как кодом изменить значение ячейки например при нажатии кнопки

359