سی شارپ

تفاوت در پیدا کردن است!

خلاصه رفتار متد اگر هیچ آیتمی پیدا نشه اگر بیش از یک آیتم پیدا بشه نوع کوئری پشت صحنه Single() Exception پرتاب میشه Exception پرتاب میشه LIMIT 2 (یا TOP 2) Sing…

خلاصه رفتار

متد اگر هیچ آیتمی پیدا نشه اگر بیش از یک آیتم پیدا بشه نوع کوئری پشت صحنه
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 شده باشه.
نوشته‌شده توسط محمد نظری من اینجا هستم تا دانشم رو با شما به اشتراک بذارم.

دیدگاه‌ها 0

برای ارسال دیدگاه یا پسند، وارد شوید.

ورود / ثبت‌نام

هنوز دیدگاهی نیست — اولین نفر باشید.