خلاصه رفتار
| متد | اگر هیچ آیتمی پیدا نشه | اگر بیش از یک آیتم پیدا بشه | نوع کوئری پشت صحنه |
|---|---|---|---|
Single() |
Exception پرتاب میشه | Exception پرتاب میشه | LIMIT 2 (یا TOP 2) |
SingleOrDefault() |
default برمیگرده (مثلاً null) |
Exception پرتاب میشه | LIMIT 2 (یا TOP 2) |
First() |
Exception پرتاب میشه | اولین آیتم رو برمیگردونه (بقیه نادیده گرفته میشن) | LIMIT 1 (یا TOP 1) |
FirstOrDefault() |
default برمیگرده |
اولین آیتم رو برمیگردونه | LIMIT 1 (یا TOP 1) |
Find() |
null برمیگرده |
(بیمعناست، چون فقط بر اساس Primary Key کار میکنه) | اول Change Tracker رو چک میکنه، بعد در صورت نیاز WHERE Id = @p0 |
شکلها (رسم متنی)
Single / SingleOrDefault
دقت میکنن که دقیقاً یک ردیف مچ داشته باشه. برای همین حتی وقتی آیتم اول رو پیدا میکنن، متوقف نمیشن؛ باید مطمئن بشن ردیف دومی وجود نداره.
دیتابیس: [A] [B] [C] [D] ...
│
LIMIT 2 (دو تا ردیف اول که شرط رو دارن میاره)
│
┌───────┴───────┐
۰ ردیف ۱ ردیف ۲+ ردیف
│ │ │
Single: Exception برگردون Single: Exception
SingleOrDefault: (هر دو) SingleOrDefault:
default Exception
First / FirstOrDefault
فقط دنبال اولین ردیفن، پس همین که یکی پیدا شد کارشون تمومه؛ اهمیتی نداره چندتای دیگه هم هست.
دیتابیس: [A] [B] [C] [D] ...
│
LIMIT 1 (فقط اولین ردیف مچ)
│
┌───────┴───────┐
۰ ردیف ۱ ردیف پیدا شد
│ │
First: Exception همون رو برگردون
FirstOrDefault: (بقیهی ردیفها
default اصلاً fetch نمیشن)
Find
مخصوص DbSet<T> هست، نه یه متد عمومی LINQ. فقط با Primary Key کار میکنه و اول توی حافظه (Change Tracker) دنبالش میگرده، قبل از اینکه بره سراغ دیتابیس.
Find(id)
│
▼
Change Tracker (توی Context، در حافظه) رو چک کن
│
├── پیدا شد؟ → برگردون (بدون هیچ کوئریای به دیتابیس!)
│
└── پیدا نشد؟
│
▼
SELECT ... WHERE Id = @id
(یه رفتوبرگشت به دیتابیس)
کوئری واقعی که EF Core میسازه (مثال)
فرض کن context.Users رو داریم:
context.Users.Single(u => u.Email == email);
context.Users.SingleOrDefault(u => u.Email == email);
SELECT TOP(2) [u].[Id], [u].[Email], ...
FROM [Users] AS [u]
WHERE [u].[Email] = @email
→ دو تا ردیف میاره تا اگه بیشتر از یکی بود، در حافظه Exception بده.
context.Users.First(u => u.Email == email);
context.Users.FirstOrDefault(u => u.Email == email);
SELECT TOP(1) [u].[Id], [u].[Email], ...
FROM [Users] AS [u]
WHERE [u].[Email] = @email
→ فقط یک ردیف.
context.Users.Find(userId);
- اگه Entity با همون Id توی Change Tracker باشه (مثلاً قبلاً Load یا Add شده)، هیچ کوئریای به دیتابیس نمیره.
- در غیر این صورت:
SELECT [u].[Id], [u].[Email], ...
FROM [Users] AS [u]
WHERE [u].[Id] = @id
نکتههای مهم برای یادداشت
Findفقط رویDbSetقابل استفادهست، نه رویIQueryableعمومی یا.Where(...)— و فقط با Primary Key کار میکنه، نه هر شرط دلخواه.Single/SingleOrDefaultاز نظر Performance گرونتر ازFirst/FirstOrDefaultهستن چون همیشه حداقل ۲ ردیف رو از دیتابیس میخونن تا مطمئن بشن یکتاست؛ استفادهشون رو نگه دار برای جایی که واقعاً یکتا بودن یه Business Rule هست (مثلEmailیکتا).First/FirstOrDefaultبدونOrderByروی دیتابیس رفتار غیرقطعی (non-deterministic) دارن — یعنی ترتیب ردیفها تضمینشده نیست، مگر Order مشخص کنی.Findبه خاطر چک کردن Change Tracker قبل از دیتابیس، برای Lookup ساده بر اساس Id معمولاً سریعتر و ارزونتره — مخصوصاً وقتی Entity قبلاً Load شده باشه.