بهینهسازی عملکرد LINQ در داتنت با استفاده از کلمه کلیدی let
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۱۶ ۱۲:۴۴
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
let یکی از قابلیتهای کمتر شناختهشده LINQ است که میتواند با ذخیرهسازی نتایج محاسبات سنگین، از تکرار بیمورد آنها جلوگیری کند. در ادامه، به بررسی این ابزار، نحوه عملکرد آن و چگونگی استفاده از آن در سناریوهای واقعی میپردازیم.let در سینتکس کوئری LINQ به توسعهدهندگان اجازه میدهد تا یک متغیر میانی (Intermediate Variable) را در داخل یک کوئری تعریف کنند. این متغیر میتواند نتیجه یک محاسبه، یک فراخوانی متد یا هر عبارت دیگری را ذخیره کند تا در بخشهای بعدی کوئری (مانند where یا select) مورد استفاده قرار گیرد. این قابلیت در نگاه اول ممکن است صرفاً برای بهبود خوانایی کد به نظر برسد، اما مزیت اصلی آن در بهینهسازی عملکرد نهفته است.let، میتوان نتیجه این محاسبه را یکبار محاسبه و ذخیره کرد و سپس در سایر بخشهای کوئری از آن استفاده نمود. این رویکرد نهتنها باعث کاهش زمان اجرا میشود، بلکه میتواند در سناریوهایی که با دادههای بزرگ یا عملیات پیچیده سروکار داریم، تأثیر قابلتوجهی بر عملکرد داشته باشد.let، ابتدا به یک نمونه ناکارآمد نگاه میکنیم. فرض کنید یک مجموعه داده از کاربران (users) داریم و میخواهیم امتیاز هر کاربر را بر اساس یک متد پرهزینه به نام CalculateExpensiveScore() محاسبه کنیم. این متد ممکن است شامل عملیاتهای پیچیده مانند دسترسی به پایگاه داده، محاسبات ریاضی سنگین یا فراخوانی APIهای خارجی باشد. در یک کوئری ناکارآمد، ممکن است این متد در چندین بخش از کوئری تکرار شود:var query = from user in users
where user.CalculateExpensiveScore() > 50
select new {
user.Name,
Bonus = user.CalculateExpensiveScore() * 1.5
};CalculateExpensiveScore() برای هر کاربر دو بار فراخوانی میشود: یکبار در شرط where و یکبار در بخش select برای محاسبه Bonus. این تکرار غیرضروری باعث میشود که برنامه دو برابر محاسبات مورد نیاز را انجام دهد، که در مجموعههای داده بزرگ میتواند منجر به کاهش قابلتوجه عملکرد شود.let این مشکل را برطرف کرد:var query = from user in users
let score = user.CalculateExpensiveScore()
where score > 50
select new { user.Name, Bonus = score * 1.5 };CalculateExpensiveScore() تنها یکبار برای هر کاربر فراخوانی میشود و نتیجه آن در متغیر میانی score ذخیره میگردد. سپس این متغیر در شرط where و بخش select مورد استفاده قرار میگیرد. این رویکرد نهتنها تعداد فراخوانیهای متد پرهزینه را به نصف کاهش میدهد، بلکه کد را خواناتر و قابلنگهداریتر میکند.let فراتر از یک بهینهسازی ساده است و میتواند در سناریوهای مختلف توسعه نرمافزار ارزشمند باشد. به عنوان مثال، در برنامههایی که با دادههای بزرگ (Big Data) یا منابع محدود (مانند برنامههای موبایل) کار میکنند، کاهش تعداد محاسبات میتواند تأثیر چشمگیری بر عملکرد داشته باشد. علاوه بر این، در برنامههایی که از LINQ برای دسترسی به پایگاه داده (مانند Entity Framework) استفاده میکنند، استفاده از let میتواند تعداد کوئریهای ارسالی به پایگاه داده را کاهش دهد.let، میتوانید این محاسبات را یکبار انجام دهید و نتایج را در کوئریهای بعدی استفاده کنید.let، یک سؤال چهارگزینهای طراحی شده است:let چه مزیتی در مقایسه با تکرار یک متد پرهزینه در بخشهای مختلف کوئری دارد؟let در LINQ، اگرچه ممکن است در نگاه اول ساده به نظر برسد، ابزاری قدرتمند برای بهینهسازی عملکرد کوئریها در پلتفرم داتنت است. با ذخیرهسازی نتایج محاسبات پرهزینه و استفاده مجدد از آنها، توسعهدهندگان میتوانند از اجرای غیرضروری کدها جلوگیری کنند و عملکرد برنامههای خود را بهبود بخشند. این تکنیک نهتنها در پروژههای کوچک، بلکه در برنامههای بزرگ و پیچیدهای که با دادههای حجیم یا منابع محدود سروکار دارند، ارزشمند است. با ترکیب let با سایر ابزارها و قابلیتهای مدرن داتنت، میتوان به سطح جدیدی از کارایی و خوانایی در کدنویسی دست یافت. توسعهدهندگان، چه مبتدی و چه حرفهای، میتوانند با استفاده هوشمندانه از این ابزار، کیفیت کدهای خود را ارتقا دهند و تجربه بهتری برای کاربران نهایی فراهم کنند.let با ذخیره نتیجه یک محاسبه در یک متغیر میانی، از اجرای مکرر متدهای پرهزینه در بخشهای مختلف کوئری جلوگیری میکند و عملکرد برنامه را بهبود میبخشد.let در متدهایی که برای دریافت اطلاعات از بانک اطلاعاتی (DbContext) از طریق DbSetها انجام میشه امکان پذیره؟ در حال حاضر جهت دریافت اطلاعات بصورت from user in users انجام نمیدم بلکه بصورت dbContext.Users.Where پیادهسازی شده. آیا باید به روشی که در توضیحات بالا عنوان کردید اقدام کنیم (استفاده از from روی DbSet) یا برای حالت دوم(Users.Where)، let قابل استفاده هست؟let در LINQ با سینتکس متد (Method Syntax) مانند dbContext.Users.Where(...) استفاده کنید، اما نه به صورت مستقیم با کلمه کلیدی let. کلمه کلیدی let به طور خاص برای سینتکس کوئری (Query Syntax) طراحی شده است که با from ... in ... شروع میشود. در سینتکس متد، شما میتوانید با استفاده از متد .Select() به همراه ایجاد یک نوع ناشناس (Anonymous Type)، به همان نتیجه دست پیدا کنید.var query = from user in dbContext.Users
let fullName = user.FirstName + " " + user.LastName
where fullName.Contains("John")
select new { user.Id, FullName = fullName };let یک متغیر موقت به نام fullName ایجاد میکند. برای پیادهسازی همین منطق با سینتکس متد، میتوانید از متد Select برای ایجاد یک شیء جدید که شامل متغیر موقت شماست، استفاده کنید و سپس روی آن فیلتر یا عملیات دیگر را انجام دهید.var query = dbContext.Users
.Select(user => new { User = user, FullName = user.FirstName + " " + user.LastName })
.Where(x => x.FullName.Contains("John"))
.Select(x => new { x.User.Id, x.FullName });.Select(user => new { User = user, FullName = user.FirstName + " " + user.LastName }): این بخش معادل let عمل میکند. ما یک نوع ناشناس جدید ایجاد میکنیم که شامل خود موجودیت user و متغیر جدید FullName است..Where(x => x.FullName.Contains("John")): در اینجا ما میتوانیم به راحتی از FullName که در مرحله قبل ایجاد کردیم، برای فیلتر کردن استفاده کنیم..Select(x => new { x.User.Id, x.FullName }): در نهایت، نتیجه را به فرمت دلخواه خود در میآوریم.select اول مادامی که هنوز شروط where اعمال نشده، آیا ابتدا محاسبات یکبار برای تمام ردیفها در بانک انجام میشه و سپس بانک اطلاعاتی برحسب شروط موجود در where یکبار دیگه اطلاعات را از جدول واکشی میکنه (دو مرتبه خوانش ردیفها)؟SELECT
[user].[Id],
[user].[FirstName] + N' ' + [user].[LastName] AS [FullName]
FROM
[Users] AS [user]
WHERE
([user].[FirstName] + N' ' + [user.LastName]) LIKE N'%John%'FirstName + ' ' + LastName) بسیار ساده بود، بهینهساز کوئری SQL Server به راحتی میتوانست آن را مدیریت کند و تکرار آن تأثیر منفی خاصی بر عملکرد نداشت. در نتیجه، Entity Framework سادهترین ترجمه ممکن را انتخاب کرد.let زمانی مشخص میشود که شما یک محاسبه پیچیده دارید و میخواهید از نتیجه آن چندین بار در کوئری خود (مثلاً در where، orderby و select) استفاده کنید. در این حالت، let میتواند به Entity Framework کمک کند تا یک کوئری SQL بهینهتر و خواناتر تولید کند.letمفید واقع میشودOrderDetails (جزئیات سفارش) با ستونهای Quantity (تعداد)، UnitPrice (قیمت واحد) و Discount (تخفیف) داریم. ما میخواهیم سفارشاتی را پیدا کنیم که قیمت نهایی هر قلم (با احتساب تخفیف) بیشتر از ۱۰۰۰ دلار باشد و آنها را بر اساس همین قیمت نهایی مرتب کنیم.let:finalPrice ذخیره میکنیم.var query = from od in dbContext.OrderDetails
// محاسبه پیچیده فقط یک بار انجام میشود
let finalPrice = (od.Quantity * od.UnitPrice) * (1 - od.Discount)
// استفاده اول از متغیر در شرط
where finalPrice > 1000
// استفاده دوم از متغیر برای مرتبسازی
orderby finalPrice descending
// استفاده سوم از متغیر در خروجی نهایی
select new
{
od.OrderDetailId,
od.ProductId,
CalculatedPrice = finalPrice
};SELECT
t.OrderDetailId,
t.ProductId,
t.FinalPrice AS CalculatedPrice
FROM (
-- زیرکوئری که محاسبه را انجام میدهد
SELECT
od.OrderDetailId,
od.ProductId,
(od.Quantity * od.UnitPrice) * (1.0 - od.Discount) AS FinalPrice -- محاسبه فقط یک بار در اینجا رخ میدهد
FROM
OrderDetails AS od
) AS t
WHERE
t.FinalPrice > 1000 -- شرط روی نتیجه محاسبه شده اعمال میشود
ORDER BY
t.FinalPrice DESC; -- مرتبسازی نیز روی نتیجه محاسبه شده انجام میشود(Quantity * UnitPrice) * (1 - Discount) فقط یک بار در زیرکوئری داخلی محاسبه شده و نتیجه آن با نام FinalPrice در دسترس قرار گرفته است.t.FinalPrice) عملیات فیلتر (WHERE) و مرتبسازی (ORDER BY) را انجام میدهد.let در LINQ فقط یک ابزار برای خوانایی کد #C نیست؛ بلکه در سناریوهایی که شامل محاسبات پیچیده و استفاده مجدد از نتایج آنهاست، میتواند مستقیماً به تولید ساختارهای SQL بهینهتر (مانند زیرکوئریها) منجر شود و از تکرار غیرضروری کد در کوئری نهایی جلوگیری کند.