Linq

اشتباهات رایج LINQ

لیستی از اشتباهات رایج در استفاده از LINQ که در حال افزایش هستند، و جایگزین صحیح آنها را در این مطلب خواهید دید.
اگر به بعضی از این موارد اعتراض کنید که «کامپایلر مشکل آن را حل خواهد کرد»، باید بگویم که: «برنامه نویس باید از ابزارش باهوش‌تر باشد». ضمن این که در مواردی ممکن است نتایج غلطی نیز در پی داشته باشد.

اجرای با تعویق

این موضوع با اختلاف در صدر جدول اشتباهات قرار دارد. وقتی یک کوئری LINQ می‌نویسید، تا زمانی که از آن استفاده نکرده‌اید، اجرا نمی‌شود. با مثال زیر متوجه قضیه خواهید شد:

// کوئری اینجا اجرا نمی‌شود
var query = from customer in db.Customers
    where customer.City == "Paris"
    select customer;

foreach (var customer in query) // اینجا اجرا می‌شود
{
    Console.WriteLine(customer.CompanyName);
}

.Where.First()

غلط:

var person = persons.Where(x => x.LastName == "Smith").First();

درست:

var person = persons.First(x => x.LastName == "Smith");

در حالت غلط، LINQ همه رکوردهایی که نام خانوادگی Smith دارند را دریافت می‌کند و اولین آن‌ها را برمی‌گرداند. مسلما چنین چیزی را نمی‌خواهیم.

این قضیه برای مواردی مثل .Where(x => x.LastName == “Smith”).Count() هم اتفاق می‌افتد.

استفاده از .SingleOrDefault()/Single() به جای .FirstOrDefault()/First()

غلط:

var person = persons.SingleOrDefault(x => x.Id == 210);

درست:

var person = persons.FirstOrDefault(x => x.Id == 210);

Single()/SingleOrDefault() اطمیان حاصل می‌کنند که فقط و فقط یک رکورد مطابق با شرط در پایگاه داده وجود داشته باشد، در حالی که First()/FirstOrDefault() اولین تطابق را برمی‌گردانند.

Single همه رکوردها را بررسی می‌کند تا از وجود فقط یک رکورد مطابق با شرط مطمئن شود و اگر بیش از یک رکورد با شرط داده شده داشته باشیم خطایی برمی‌گرداند.

اگر با زبان SQL نگاه کنیم، Single عبارت است از: select top 2 … در حالی که First معادل select top 1 … است. اگر شما ۱۰ میلیون رکورد داشته باشید، و اولین رکورد جدول مطابق شرط شما باشد، Single کل ۱۰ میلیون رکورد را بررسی می‌کند تا از وجود فقط یک رکورد مطابق شرط اطمینان پیدا کند، در حالی که First فورا همان اولین رکورد را برخواهد گرداند.

عدم فهم تفاوت First() و FirstOrDefault()

این موضوع به شدت وابسته به موقعیت است. گاهی استفاده First() درست است و گاهی درست نیست.

First() و Single() در صورت عدم وجود رکورد مطابق شرط، استثناء (exception) ایجاد خواهند کرد، در حالی که FirstOrDefault() و SingleOrDefault() به جای استثناء بسته به نوع داده، مقدار پیش فرض یا null برخواهند گرداند.

مثلا:

var person = persons.First(x => x.Id == 210);

اگر واقعا فردی با Id فوق باید وجود داشته باشد و ندارد، صدور استثنا لازم است.

از سوی دیگر:

var login = users.First(x => x.Username == "john@email.com");

در این مورد به جای استفاده از First و صدور استثنا شاید بهتر باشد از FirstOrDefault() استفاده کنیم و در صورت عدم وجود رکورد مورد نظر، نال بودن متغیر login را بررسی کنیم و در صورت لزوم یک سعی در ورود ناموفق را لاگ کنیم.

استفاده از Count() به جای Any() و All()

غلط:

if (persons.Count() > 0) ...
if (persons.Count(x => x.LastName == "Smith") > 0)...
if (persons.Count(x => x.LastName == "Smith") == persons.Count())...

درست:

if (persons.Any())...
if (persons.Any(x => x.LastName == "Smith")...
if (persons.All(x => x.LastName == "Smith")...

Count() همه رکوردها را بررسی می‌کند. اگر هدف شما این است فقط بدانید آیا رکوردی مطابق شرط وجود دارد یا باید از Any() استفاده کنید. Any() به محض این که به اولین رکورد مطابق شرط برسد، true برمی‌گرداند. با Any() بدون شرط، در صورت وجود رکورد true برگردانده می‌شود.

مثلا اگر لیستی از ۱۰ میلیون نفر داشته باشیم، و بخواهید بدانید که آیا فردی با نام خانوادگی Smith وجود دارد، persons.Count( x => x.LastName == “Smith”) در تمام رکوردها خواهد گشت تا تعداد همه افراد با نام خانوادگی Smith را بیابد. persons.Any( x => x.LastName == “Smith”) بعد از برخورد با اولین رکورد با نام خانوادگی Smith مقدار  true برخواهد گرداند.

All() نقطه مقابل Any() است. اگر شما بخواهید بدانید که آیا نام خانوادگی تمام افراد Smith است، باید از All() استفاده کنید. All() در صورتی که با اولین رکورد با نام خانوادگی غیر از Smith برخورد کند، false برخواهد گرداند، و به بررسی بقیه رکوردها ادامه نخواهد داد.

Where().Where()

غلط:

persons.Where(x => x.LastName == "Smith").Where(x => x.FirstName == "John");

درست:

persons.Where(x => x.LastName == "Smith" && x.FirstName == "John");

استفاده از Where() به صورت زنجیره‌ای باعث می‌شود که کد SQL تولید شده شامل چند کوئری تودرتو باشد (select * from (select * from persons where lastname = ‘Smith’) where firstname = ‘John’، به جای select * from persons where lastname=’smith’ and firstname=’john’)  – یا ممکن است چندین بار کوئری روی جدول رکوردها اجرا کند.

ضمنا این نوع کد می‌تواند در هدف کوئری نیز ابهام ایجاد کند.

OrderBy().OrderBy()

غلط:

persons.OrderBy(x => x.LastName).OrderBy(x => x.FirstName);

درست:

persons.OrderBy(x => x.LastName).ThenBy(x => x.FirstName);

نسخه «غلط» ابتدا لیست را بر اساس نام خانوادگی مرتب می‌کند، سپس آن مرتب سازی را نادیده می‌گیرد و دوباره جدول را با نام مرتب سازی مجدد می‌کند. نسخه «درست» ابتدا با نام خانوادگی، جدول را مرتب می‌کند، سپس با نام، و این زنجیره می‌تواند با ThenBy() ادامه پیدا کند.

این موضوع در مورد OrderByDescending() و ThenByDescending() نیز برقرار است.

Select(x => x)

غلط:

persons.Where(x => x.LastName == "Smith").Select(x => x);

درست:

persons.Where(x => x.LastName == "Smith");

مطمئن نیستم که چرا بعضی این کار را انجام می‌دهند! شاید قصد داشته‌اند کاری با بخش Select انجام بدهند و فراموش کرده‌اند، یا فکر می‌کنند Select() مانند ToList() باعث اجرای کوئری می‌شود.

 

Count() خالی برای آرایه‌ها، لیست و دیکشنری

غلط:

var count = peopleArray.Count();
var count = peopleList.Count();
var count = peopleDictionary.Count();

درست:

var count = peopleArray.Length;
var count = peopleList.Count;
var count = peopleDictionary.Count;

برای آرایه‌ها یا هر مجموعه‌ای که ICollection<T>/ICollection را پیاده سازی می‌کنند از قبیل List<T> و Dictionary<T> از متد Enumerable.Count() استفاده نکنید. برای آرایه‌ها از مشخصه Length و برای ICollection ها از مشخصه Count (نه متد Count()) استفاده کنید.

بسته به پلتفرم، بهینه‌سازی‌هایی برای جلوگیری از عملیات با پیچیدگی O(n) متد Count() وجود دارد که مقدار فعلی مشخصه تعداد را برمی‌گردانند.

منبع: https://goo.gl/yZHrvz


منتشر شده

در

,

توسط

دیدگاه‌ها

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *