Question:
If you need to get a record from a database by its primary key, you can use either method. Both methods will return an entity object if the record is present in the database, otherwise they will return null
.
What is the difference and when should you use which method?
Answer:
To find and get an entity object, you can use the following methods:
Find()
, First()
, FirstOrDefault()
, Single()
, SingleOrDefault()
.
Consider each:
-
The
Find()
method takes the primary key of the record as a parameter.Its peculiarity is that, unlike other methods, it first accesses memory and looks for a record among the context objects tracked by the EntityFramework, and only then (if nothing is found) it performs a query to the database. If an object is found in the context that has not yet been saved in the database, the
Find()
method will still return it (with theAdded
state). If the entry is not found either there or there, it will returnnull
.Generated SQL query:
SELECT TOP (2) [Extent1].[Id] AS [Id], [Extent1].[Title] AS [Title], FROM [dbo].[Topics] AS [Extent1] WHERE [Extent1].[Id] = @p0
It should be noted that the selection includes not one record, but two
SELECT TOP (2)
. This is used internally by EF to validate record uniqueness. If the result is more than one record with the same primary key, EF will throw an exception:System.InvalidOperationException: "The sequence contains more than one element"
-
The
FirstOrDefault()
method accepts a predicate as a parameter, which makes it possible to search not only by the primary key, but also by any specified condition.For example, let's find a record with
Title == "test"
:var topic = context.Topics.FirstOrDefault(topic => topic.Title == "test");
FirstOrDefault()
, unlikeFind()
executes a query to the database every time, regardless of whether there is data in the context. If a record comes from the database that differs from the one in the context, EF will return the record from the context. If the record is not in the database, it will returnnull
, even if it is in the context.Generated SQL query:
SELECT TOP (1) [Extent1].[Id] AS [Id], [Extent1].[Title] AS [Title], FROM [dbo].[Topics] AS [Extent1] WHERE [Extent1].[Id] = @p__linq__0
It searches for only one
SELECT TOP (1)
record. If it turns out that there are several records, it will return the first one. -
The
First()
method is similar toFirstOrDefault()
with one difference – if no entry is found, an exception will be thrown:System.InvalidOperationException: "The sequence contains no elements"
-
The
SingleOrDefault()
method is similar toFirstOrDefault()
and returns an entity object, ornull
. However, the request generates something similar to theFind()
method:SELECT TOP (2) [Extent1].[Id] AS [Id], [Extent1].[Title] AS [Title], FROM [dbo].[Topics] AS [Extent1] WHERE [Extent1].[Id] = @p__linq__0
And if more than one entry is found, it throws an exception:
System.InvalidOperationException: "The sequence contains more than one element"
-
The
Single()
method is similar toSingleOrDefault()
and differs in that if no entry is found, an exception will be thrown:System.InvalidOperationException: "The sequence contains no elements"
Let's summarize:
-
When to use
Find()
?When you need to search for the primary key and select all the entity data.
Find()
does not execute the request if the record is already loaded into the context and, as a result, will outperform all other methods in performance. However, if you need to select some specific fields (for example, onlyId
andTitle
), you will have to use the rest of the methods. The same applies to the loading of dependent data (for example, viaInclude()
) –Find()
will not let you do this. -
When to use the rest of the methods?
If you need to select specific fields of an entity or you also need to load dependent data. Which methods to use depends on individual requirements: if you need to check for the existence of a record (without
*OrDefault()
exceptions), the*OrDefault()
methods will*OrDefault()
:var topic = context.Topics .Select(topic => new { topic.Id, topic.Title }) .FirstOrDefault(topic => topic.Id == 44); if (topic == null) { // ... }
If it is assumed that the context can contain local copies of the data, you can get them from there without having to query the database. To do this, you need to refer to the
Local
property of theDbSet
object: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