در دنیای توسعه نرم‌افزار، به‌ویژه در برنامه‌های کاربردی که با داده‌های زیادی سروکار دارند، عملکرد کارآمد کوئری‌های پایگاه داده از اهمیت بالایی برخوردار است. Entity Framework Core، به عنوان یک Object-Relational Mapper (ORM) محبوب و قدرتمند برای توسعه‌دهندگان.NET، ابزارهای متنوعی را برای تسهیل تعامل با پایگاه داده‌های رابطه‌ای فراهم می‌کند.
کینگتو - آموزش برنامه نویسی تخصصصی - دات نت - سی شارپ - بانک اطلاعاتی و امنیت

بهینه‌ سازی کوئری‌های دیتابیس با Entity Framework Core

34 بازدید 0 نظر ۱۴۰۴/۰۲/۱۵

 این فریم‌ورک با انتزاع پیچیدگی‌های دسترسی به داده‌ها و فراهم کردن امکان کار با پایگاه داده از طریق اشیاء.NET، فرآیند توسعه را تسریع می‌بخشد و نیاز به نوشتن کدهای تکراری دسترسی به داده را به میزان قابل توجهی کاهش می‌دهد.

 با این حال، استفاده نادرست از EF Core می‌تواند منجر به گلوگاه‌های عملکردی شود، به‌خصوص زمانی که با حجم زیادی از داده‌ها سروکار داریم. بنابراین، درک و به کارگیری تکنیک‌های بهینه‌سازی کوئری در EF Core برای حفظ عملکرد و مقیاس‌پذیری برنامه‌های کاربردی ضروری است. در پروژه‌های واقعی، توسعه‌دهندگان اغلب با چالش‌های مربوط به عملکرد کوئری‌ها مواجه می‌شوند که نیازمند توجه دقیق و استفاده از راهکارهای مناسب است.
استفاده از ORMها مانند EF Core یک لایه انتزاعی بین منطق برنامه و پایگاه داده ایجاد می‌کند. این انتزاع، در حالی که بهره‌وری توسعه‌دهندگان را افزایش می‌دهد، می‌تواند جزئیات مربوط به عملیات پایگاه داده را پنهان کند. در نتیجه، ممکن است توسعه‌دهندگان بدون آگاهی کامل از نحوه ترجمه کوئری‌های LINQ به SQL و چگونگی اجرای آن توسط موتور پایگاه داده، کوئری‌های ناکارآمدی بنویسند. از طرفی، ماهیت چندسکویی EF Core ایجاب می‌کند که در استراتژی‌های بهینه‌سازی، ارائه‌دهنده پایگاه داده خاص مورد استفاده نیز در نظر گرفته شود، زیرا پایگاه داده‌های مختلف دارای ویژگی‌های عملکردی و قابلیت‌های فهرست‌گذاری متفاوتی هستند.

Entity Framework Core به عنوان یک ORM، یک مکانیزم خودکار برای دسترسی و ذخیره داده‌ها در پایگاه داده فراهم می‌کند. این فریم‌ورک به توسعه‌دهندگان این امکان را می‌دهد که با استفاده از مدل‌ها با پایگاه داده تعامل داشته باشند. یک مدل در EF Core از کلاس‌های موجودیت (Entity Classes) که نمایانگر جداول پایگاه داده هستند و یک شیء контекст (DbContext) که نشان‌دهنده یک جلسه با پایگاه داده است، تشکیل می‌شود. شیء DbContext امکان پرس و جو و ذخیره داده‌ها را فراهم می‌کند. برای توسعه مدل در EF Core، دو رویکرد اصلی وجود دارد: Code-First که در آن مدل از روی کد ایجاد می‌شود و Database-First که در آن مدل از روی یک پایگاه داده موجود تولید می‌گردد.

یکی از ویژگی‌های کلیدی EF Core، استفاده از LINQ (Language Integrated Query) برای نوشتن کوئری‌ها است. LINQ به توسعه‌دهندگان اجازه می‌دهد تا کوئری‌های strongly-typed را با استفاده از سینتکس C# بنویسند، که سپس توسط EF Core به SQL خاص پایگاه داده ترجمه می‌شود. EF Core همچنین قابلیت‌های دیگری مانند Change Tracking (ردیابی تغییرات)، Migrations (مدیریت تغییرات طرحواره پایگاه داده) و پشتیبانی از تراکنش‌ها را ارائه می‌دهد. درک این قابلیت‌ها و تأثیر بالقوه آن‌ها بر عملکرد برای بهینه‌سازی کوئری‌ها ضروری است.

معماری Entity Framework Core به صورت لایه‌ای طراحی شده است و شامل چندین جزء کلیدی است.لایه Application شامل منطق کسب‌وکار و کد UI/API است که از EF Core برای انجام عملیات CRUD (Create, Read, Update, Delete) بر روی پایگاه داده استفاده می‌کند. جزء مرکزی EF Core، DbContext است که به عنوان یک پل ارتباطی بین دامنه (کلاس‌های موجودیت) و پایگاه داده عمل می‌کند. DbContext نشان‌دهنده یک جلسه با پایگاه داده است و یک انتزاع برای پرس و جو و ذخیره داده‌ها فراهم می‌کند. Model در EF Core یک نمایش درون حافظه‌ای از طرحواره پایگاه داده است که از کلاس‌های موجودیت و پیکربندی‌های آن‌ها ساخته می‌شود. Data Providers نقش مهمی در اتصال به پایگاه داده‌های مختلف دارند. هر ارائه‌دهنده، دستورات EF Core را به SQL خاص پایگاه داده ترجمه می‌کند و ارتباط با پایگاه داده را مدیریت می‌کند. در نهایت، Database Connection جزئی است که اتصال واقعی به پایگاه داده را برقرار می‌کند.

چرخه اجرای یک کوئری LINQ در EF Core شامل چندین مرحله است. ابتدا، کوئری LINQ توسط EF Core پردازش می‌شود تا یک نمایش قابل پردازش توسط ارائه‌دهنده پایگاه داده ساخته شود.این نمایش معمولاً یک درخت عبارت (Expression Tree) است. سپس، ارائه‌دهنده پایگاه داده این درخت عبارت را به SQL خاص پایگاه داده ترجمه می‌کند.در مرحله بعد، SQL تولید شده در پایگاه داده اجرا می‌شود و نتایج به برنامه بازگردانده می‌شود.در نهایت، EF Core نتایج پایگاه داده را به اشیاء موجودیت (Entity Objects) تبدیل می‌کند که این فرآیند Materialization نامیده می‌شود.

در کوئری‌های LINQ، مفهوم Deferred Execution (اجرای تأخیری) وجود دارد.این بدان معناست که یک کوئری LINQ تا زمانی که نتایج آن واقعاً مورد نیاز نباشد، اجرا نمی‌شود. این رفتار می‌تواند از سفرهای غیرضروری به پایگاه داده جلوگیری کرده و عملکرد را بهبود بخشد. با این حال، برخی از عملیات LINQ مانند ToList()، ToArray()، Single() و Count() باعث اجرای فوری کوئری می‌شوند. درک زمان اجرای کوئری برای پیش‌بینی و کنترل بار پایگاه داده مهم است. فرآیند اجرای کوئری شامل مراحل متعددی است که عملکرد می‌تواند در هر یک از آن‌ها تحت تأثیر قرار گیرد.بهینه‌سازی باید کارایی ترجمه LINQ به SQL، عملکرد SQL تولید شده در پایگاه داده و سربار Materialization نتایج را در نظر بگیرد. EF Core همچنین دارای مکانیزم‌های کشینگ مانند Query Plan Caching است که هدف آن بهبود عملکرد با استفاده مجدد از کوئری‌های کامپایل شده قبلی است. آگاهی از نحوه عملکرد این کش‌ها و نحوه استفاده از آن‌ها می‌تواند مفید باشد.

یکی از مشکلات رایج عملکرد کوئری در EF Core، مشکل N+1 Query است. این مشکل زمانی رخ می‌دهد که برای واکشی یک مجموعه از موجودیت‌ها (N موجودیت)، به ازای هر موجودیت، یک کوئری جداگانه برای بارگیری موجودیت‌های مرتبط اجرا می‌شود (+1 کوئری). این امر معمولاً در هنگام استفاده از Lazy Loading (بارگیری تنبل) رخ می‌دهد، جایی که موجودیت‌های مرتبط تنها زمانی بارگیری می‌شوند که برای اولین بار به آن‌ها دسترسی پیدا شود. Lazy Loading می‌تواند منجر به تعداد زیادی درخواست غیرضروری به پایگاه داده شود و عملکرد برنامه را به شدت کاهش دهد.

برای رفع مشکل N+1 Query و بهبود عملکرد، می‌توان از استراتژی‌های جایگزین مانند Eager Loading (بارگیری مشتاقانه) و Explicit Loading (بارگیری صریح) استفاده کرد. Eager Loading با استفاده از متدهای Include() و ThenInclude()، موجودیت‌های مرتبط را همراه با موجودیت اصلی در یک کوئری واحد واکشی می‌کند. Explicit Loading به توسعه‌دهندگان اجازه می‌دهد تا به صورت دستی و در صورت نیاز، موجودیت‌های مرتبط را بارگیری کنند. انتخاب استراتژی بارگیری مناسب برای جلوگیری از مشکل N+1 و بهینه‌سازی بازیابی داده‌ها حیاتی است.

مشکل دیگری که می‌تواند بر عملکرد کوئری‌ها تأثیر بگذارد، بارگیری بیش از حد داده‌ها (Over-fetching) است. این مشکل زمانی رخ می‌دهد که کوئری‌ها ستون‌ها یا موجودیت‌های مرتبط بیشتری از آنچه واقعاً مورد نیاز است را بازیابی می‌کنند. برای حل این مشکل، می‌توان از Projection با استفاده از متد Select() استفاده کرد تا تنها ستون‌های مورد نیاز را انتخاب کنیم. این کار باعث کاهش میزان داده‌های منتقل شده از پایگاه داده و بهبود سرعت کوئری می‌شود.

عدم استفاده صحیح از Indexها (فهرست‌ها) در پایگاه داده نیز می‌تواند منجر به کاهش سرعت کوئری‌ها شود. Indexها ساختارهای داده‌ای هستند که جستجو و بازیابی سریع داده‌ها را در پایگاه داده امکان‌پذیر می‌سازند. کوئری‌هایی که از Indexهای مناسب استفاده نمی‌کنند، ممکن است منجر به اسکن کامل جداول شوند که برای جداول بزرگ بسیار کند است. بهینه‌سازی Indexهای پایگاه داده برای عملکرد کارآمد کوئری‌ها ضروری است.

گاهی اوقات، EF Core ممکن است به جای اجرای کوئری در پایگاه داده (Server-Side Evaluation)، آن را در سمت کلاینت (Client-Side Evaluation) اجرا کند.این امر معمولاً زمانی اتفاق می‌افتد که برخی از عملیات LINQ قابل ترجمه به SQL نباشند. Client-Side Evaluation می‌تواند به خصوص با مجموعه‌های داده بزرگ بسیار ناکارآمد باشد. بنابراین، اطمینان از اینکه تا حد امکان منطق کوئری در سمت سرور اجرا می‌شود، برای عملکرد بهتر ضروری است. استفاده نادرست از متدهای LINQ مانند قرار دادن متد Where() بعد از ToList() نیز می‌تواند منجر به مشکلات عملکردی شود.در این حالت، ابتدا تمام داده‌ها از پایگاه داده بارگیری شده و سپس در حافظه فیلتر می‌شوند، در حالی که بهتر است فیلتر کردن در سطح پایگاه داده با استفاده از Where() انجام شود.

هنگام استفاده از Include() برای بارگیری چندین Collection Navigation (ویژگی‌های ناوبری که به مجموعه‌ها اشاره می‌کنند)، ممکن است مشکلی به نام Cartesian Explosion رخ دهد. این مشکل زمانی اتفاق می‌افتد که تعداد ردیف‌های بازگشتی به طور غیرمنتظره‌ای زیاد می‌شود، زیرا تمام ترکیب‌های ممکن بین موجودیت‌های مرتبط ایجاد می‌شوند. برای جلوگیری از این مشکل، می‌توان از متد AsSplitQuery() استفاده کرد تا مجموعه‌های مرتبط با استفاده از کوئری‌های جداگانه بارگیری شوند.

بسیاری از مشکلات عملکردی در برنامه‌های EF Core ناشی از عدم درک کافی از نحوه ترجمه کوئری‌های LINQ به SQL و چگونگی اجرای این کوئری‌ها توسط موتور پایگاه داده است.توسعه‌دهندگان باید از SQL تولید شده توسط EF Core آگاه باشند و LINQ خود را بر اساس آن بهینه کنند. همچنین، رفتار پیش‌فرض EF Core مانند ردیابی موجودیت‌ها می‌تواند سربار ایجاد کند، به خصوص در سناریوهای فقط خواندنی. درک زمان و نحوه غیرفعال کردن ردیابی می‌تواند منجر به بهبود عملکرد شود.

برای بهینه‌سازی کوئری‌ها در Entity Framework Core، تکنیک‌های مختلفی وجود دارد. استفاده از Eager Loading با متدهای Include() و ThenInclude() یکی از مهم‌ترین این تکنیک‌ها است که به جلوگیری از مشکل N+1 Query کمک می‌کند. متد Include() برای بارگیری مستقیم موجودیت‌های مرتبط و متد ThenInclude() برای بارگیری موجودیت‌های مرتبط در سطوح عمیق‌تر استفاده می‌شود.

به کارگیری Projection با متد Select() روش دیگری برای بهبود عملکرد است. با استفاده از Select()، توسعه‌دهندگان می‌توانند تنها ستون‌های مورد نیاز را انتخاب کنند، که این امر باعث کاهش میزان داده‌های منتقل شده از پایگاه داده و افزایش سرعت کوئری می‌شود.

برای کوئری‌هایی که فقط برای خواندن داده‌ها استفاده می‌شوند و نیازی به به‌روزرسانی آن‌ها نیست، استفاده از متد AsNoTracking() توصیه می‌شود. این متد باعث غیرفعال شدن قابلیت Change Tracking می‌شود و سربار ناشی از آن را کاهش می‌دهد، که می‌تواند منجر به بهبود قابل توجه عملکرد در برنامه‌های کاربردی با بار خواندن زیاد شود.

در مواردی که نیاز به بارگیری چند Collection Navigation با استفاده از Include() وجود دارد و این امر منجر به مشکل Cartesian Explosion می‌شود، می‌توان از متد AsSplitQuery() استفاده کرد. این متد باعث می‌شود که مجموعه‌های مرتبط با استفاده از کوئری‌های جداگانه بارگیری شوند و از تولید ردیف‌های تکراری جلوگیری شود.

اعمال فیلتر در سطح پایگاه داده با استفاده از متد Where() قبل از استفاده از Include() نیز یک تکنیک مهم بهینه‌سازی است.این کار باعث می‌شود که تنها موجودیت‌های اصلی که با شرط فیلتر مطابقت دارند و موجودیت‌های مرتبط با آن‌ها بارگیری شوند، که این امر تعداد داده‌های بارگیری شده را کاهش می‌دهد.

برای کوئری‌هایی که به طور مکرر اجرا می‌شوند، استفاده از Compiled Queries (کوئری‌های کامپایل شده) می‌تواند مفید باشد.کوئری‌های کامپایل شده یک نسخه از پیش کامپایل شده از یک کوئری LINQ هستند که برای اجرای سریع‌تر در حافظه ذخیره می‌شوند و سربار کامپایل مجدد در هر بار اجرا را کاهش می‌دهند.

در سناریوهای پیچیده‌تر که LINQ ممکن است کارآمدترین راه حل نباشد، می‌توان از Raw SQL (SQL خام) با استفاده از متدهای FromSqlRaw() یا SqlQuery() برای اجرای کوئری‌های سفارشی یا بهینه استفاده کرد. این روش انعطاف‌پذیری بیشتری را برای نوشتن کوئری‌های خاص فراهم می‌کند.

EF Core به طور خودکار درخواست‌های پایگاه داده را برای برخی از عملیات گروه‌بندی می‌کند (Batching) تا تعداد رفت و برگشت‌ها به پایگاه داده را کاهش دهد. بهینه‌سازی ارتباطات بین جداول و تعریف کلیدهای خارجی مناسب در طرحواره پایگاه داده نیز برای عملکرد کارآمد EF Core بسیار مهم است. استفاده از متدهای Asynchronous (Async) برای عملیات ورودی/خروجی (I/O) به پایگاه داده می‌تواند از مسدود شدن نخ‌ها جلوگیری کرده و پاسخگویی برنامه را بهبود بخشد، به ویژه در برنامه‌های کاربردی وب.

انتخاب تکنیک بهینه‌سازی به شدت به کوئری خاص، داده‌های مورد دسترسی و الزامات برنامه بستگی دارد. هیچ راه حل یکسانی برای همه موارد وجود ندارد، و توسعه‌دهندگان باید کوئری‌های خود را تجزیه و تحلیل کرده و مناسب‌ترین استراتژی‌ها را انتخاب کنند. در حالی که EF Core بسیاری از ویژگی‌های بهینه‌سازی داخلی را فراهم می‌کند، توسعه‌دهندگان همچنین باید درک کاملی از اصول پایگاه داده مانند فهرست‌گذاری و طرح‌های اجرای کوئری داشته باشند تا بتوانند کوئری‌های خود را به طور مؤثر بهینه کنند.

برای نوشتن کوئری‌های کارآمد در EF Core، رعایت برخی از بهترین روش‌ها ضروری است. همواره سعی کنید تنها ستون‌های مورد نیاز را با استفاده از Select() انتخاب کنید. تا حد امکان از فیلتر کردن در سطح پایگاه داده با استفاده از Where() استفاده کنید. از بارگیری غیرضروری داده‌های مرتبط اجتناب کنید و استراتژی بارگیری مناسب (Eager Loading، Explicit Loading یا Lazy Loading در صورت لزوم) را انتخاب کنید. بهینه‌سازی Indexهای پایگاه داده را در نظر بگیرید، زیرا این امر تأثیر بسزایی در سرعت کوئری‌ها دارد. از اجرای کوئری‌های تکراری در حلقه‌ها خودداری کنید، زیرا این می‌تواند منجر به مشکلات عملکردی جدی شود. عملکرد کوئری‌های تولید شده را با استفاده از ابزارهای Profiling بررسی کنید تا گلوگاه‌ها را شناسایی و رفع کنید. برای عملیات I/O، از متدهای Asynchronous استفاده کنید تا از مسدود شدن نخ‌ها جلوگیری شود. درک درستی از Deferred Execution در LINQ داشته باشید تا از اجرای غیرمنتظره کوئری‌ها جلوگیری کنید. برای کوئری‌های با کارایی بالا که به طور مکرر استفاده می‌شوند، از Compiled Queries استفاده کنید. در صورت نیاز و برای سناریوهای پیچیده، از Raw SQL استفاده کنید. در برنامه‌های ASP.NET Core، از DbContextPool برای بهبود عملکرد با استفاده مجدد از نمونه‌های DbContext استفاده کنید.اتخاذ یک رویکرد فعالانه برای بهینه‌سازی کوئری، شامل برنامه‌ریزی دقیق، نوشتن کوئری‌های LINQ کارآمد و استفاده از ویژگی‌های EF Core، بسیار مؤثرتر از بهینه‌سازی واکنشی پس از بروز مشکلات عملکردی است.

برای تشخیص و تحلیل عملکرد کوئری در EF Core، ابزارهای مختلفی در دسترس هستند. استفاده از Logging در EF Core به شما امکان می‌دهد تا SQL تولید شده توسط EF Core را مشاهده کنید. این امر برای درک نحوه ترجمه کوئری‌های LINQ و شناسایی ناکارآمدی‌های احتمالی در SQL تولید شده ضروری است. همچنین، استفاده از ابزارهای Profiling پایگاه داده مانند SQL Server Profiler برای تحلیل طرح اجرای کوئری و شناسایی گلوگاه‌ها بسیار مفید است. در EF Core  و بالاتر، متد ToQueryString() معرفی شده است که به توسعه‌دهندگان اجازه می‌دهد تا SQL تولید شده توسط یک کوئری LINQ را بدون اجرای واقعی آن در پایگاه داده مشاهده کنند.استفاده از Performance Counters برای نظارت بر جنبه‌های مختلف عملکرد برنامه و پایگاه داده نیز می‌تواند داده‌های ارزشمندی را برای شناسایی روندها و مشکلات احتمالی عملکرد در طول زمان فراهم کند. به طور کلی، بهینه‌سازی مؤثر کوئری نیازمند درک تکنیک‌ها و همچنین داشتن ابزارهایی برای تشخیص و تحلیل مشکلات عملکرد است. Logging و Profiling برای به دست آوردن بینش در مورد نحوه تعامل EF Core با پایگاه داده ضروری هستند.

 

مثال‌هایی از شیوه‌های غلط و درست در کوئری‌نویسی
برای درک بهتر تکنیک‌های بهینه‌سازی که در بخش‌های قبلی به آن‌ها اشاره شد، در ادامه چند مثال از کوئری‌های غیراصولی و روش‌های صحیح و بهینه برای نوشتن آن‌ها آورده شده است.

مشکل N+1 Query
شیوه غلط (بارگیری تنبل - Lazy Loading):

فرض کنید یک موجودیت Blog داریم که با موجودیت Post رابطه یک به چند دارد. کد زیر تلاش می‌کند تا تمام بلاگ‌ها و سپس پست‌های مربوط به هر بلاگ را نمایش دهد:

var blogs = context.Blogs.ToList();
foreach (var blog in blogs)
{
    Console.WriteLine($"Blog: {blog.Url}");
    foreach (var post in blog.Posts) // هر بار دسترسی به Posts یک کوئری جدید اجرا می‌کند
    {
        Console.WriteLine($"- Post: {post.Title}");
    }
}


در این حالت، ابتدا یک کوئری برای دریافت تمام بلاگ‌ها اجرا می‌شود. سپس، در داخل حلقه foreach و با دسترسی به blog.Posts برای هر بلاگ، یک کوئری جداگانه به پایگاه داده ارسال می‌شود تا پست‌های آن بلاگ را دریافت کند. اگر N بلاگ وجود داشته باشد، این کد منجر به اجرای N+1 کوئری خواهد شد که می‌تواند عملکرد برنامه را به شدت کاهش دهد.

شیوه درست (بارگیری مشتاقانه - Eager Loading):

برای حل مشکل N+1 Query، می‌توان از بارگیری مشتاقانه با استفاده از متد Include() استفاده کرد:

var blogs = context.Blogs.Include(blog => blog.Posts).ToList();
foreach (var blog in blogs)
{
    Console.WriteLine($"Blog: {blog.Url}");
    foreach (var post in blog.Posts)
    {
        Console.WriteLine($"- Post: {post.Title}");
    }
}


در این روش، تمام بلاگ‌ها و پست‌های مربوط به آن‌ها در یک کوئری واحد از پایگاه داده واکشی می‌شوند. این کار تعداد رفت و برگشت‌ها به پایگاه داده را به حداقل می‌رساند و عملکرد را بهبود می‌بخشد.

بارگیری بیش از حد داده‌ها (Over-fetching)
شیوه غلط (انتخاب تمام ستون‌ها):

فرض کنید فقط به نام و URL بلاگ‌ها نیاز داریم، اما کوئری زیر تمام ستون‌های جدول Blogs را واکشی می‌کند:

var blogs = context.Blogs.ToList();
foreach (var blog in blogs)
{
    Console.WriteLine($"Blog Name: {blog.Name}, URL: {blog.Url}");
}


این کار باعث انتقال داده‌های غیرضروری از پایگاه داده به برنامه می‌شود که می‌تواند منجر به افزایش مصرف حافظه و کاهش سرعت شود.

شیوه درست (Projection با استفاده از Select()):

برای جلوگیری از بارگیری بیش از حد داده‌ها، می‌توان از Projection با استفاده از متد Select() استفاده کرد تا فقط ستون‌های مورد نیاز را انتخاب کنیم:

var blogInfo = context.Blogs
   .Select(blog => new { blog.Name, blog.Url })
   .ToList();
foreach (var blog in blogInfo)
{
    Console.WriteLine($"Blog Name: {blog.Name}, URL: {blog.Url}");
}

در این روش، تنها ستون‌های Name و Url از جدول Blogs واکشی می‌شوند که باعث کاهش میزان داده‌های منتقل شده و بهبود عملکرد می‌شود.

استفاده نادرست از Where() و ToList()
شیوه غلط (فیلتر کردن در حافظه):

کد زیر ابتدا تمام سفارشات را از پایگاه داده بارگیری می‌کند و سپس آن‌ها را در حافظه فیلتر می‌کند:

var orders = context.Orders.ToList();
var highValueOrders = orders.Where(order => order.TotalAmount > 1000).ToList();

این روش می‌تواند برای جداول بزرگ بسیار ناکارآمد باشد، زیرا تمام داده‌ها ابتدا باید به حافظه منتقل شوند.

شیوه درست (فیلتر کردن در سطح پایگاه داده):

بهتر است فیلتر کردن را در سطح پایگاه داده با استفاده از متد Where() قبل از فراخوانی ToList() انجام دهیم:

var highValueOrders = context.Orders
   .Where(order => order.TotalAmount > 1000)
   .ToList();


در این حالت، تنها سفارشاتی که مبلغ کل آن‌ها بیشتر از 1000 است از پایگاه داده واکشی می‌شوند که باعث کاهش بار پایگاه داده و بهبود عملکرد می‌شود.

مشکل Cartesian Explosion با Include()
شیوه غلط (بارگیری چند مجموعه مرتبط با Include()):

وقتی یک موجودیت دارای چندین مجموعه مرتبط باشد و همه آن‌ها با استفاده از Include() بارگیری شوند، ممکن است با مشکل Cartesian Explosion مواجه شویم. برای مثال، اگر یک Blog دارای مجموعه‌ای از Posts و هر Post دارای مجموعه‌ای از Tags باشد:

var blogs = context.Blogs
   .Include(b => b.Posts)
       .ThenInclude(p => p.Tags)
   .ToList();


اگر هر بلاگ دارای تعداد زیادی پست و هر پست دارای تعداد زیادی تگ باشد، تعداد ردیف‌های بازگشتی از پایگاه داده می‌تواند به طور چشمگیری افزایش یابد.   

شیوه درست (استفاده از AsSplitQuery()):

برای جلوگیری از این مشکل در EF Core 5.0 و بالاتر، می‌توان از متد AsSplitQuery() استفاده کرد:

var blogs = context.Blogs
   .Include(b => b.Posts)
       .ThenInclude(p => p.Tags)
   .AsSplitQuery()
   .ToList();


این کار باعث می‌شود که EF Core برای هر مجموعه مرتبط یک کوئری جداگانه به پایگاه داده ارسال کند و از تولید ردیف‌های تکراری جلوگیری شود.

ردیابی تغییرات غیرضروری
شیوه غلط (ردیابی موجودیت‌ها برای عملیات فقط خواندنی):

به طور پیش‌فرض، EF Core موجودیت‌های واکشی شده را ردیابی می‌کند تا تغییرات احتمالی را برای ذخیره‌سازی در آینده پیگیری کند. اگر فقط به خواندن داده‌ها نیاز دارید و قصد به‌روزرسانی آن‌ها را ندارید، این ردیابی می‌تواند سربار ایجاد کند.

شیوه درست (استفاده از AsNoTracking()):

برای غیرفعال کردن ردیابی تغییرات، می‌توان از متد AsNoTracking() استفاده کرد:

var blogs = context.Blogs
   .AsNoTracking()
   .ToList();

این کار باعث بهبود عملکرد در سناریوهای فقط خواندنی می‌شود، زیرا EF Core نیازی به پیگیری وضعیت موجودیت‌ها ندارد.

 

بهینه‌سازی کوئری‌های پایگاه داده با Entity Framework Core یک جنبه حیاتی در توسعه برنامه‌های کاربردی کارآمد و مقیاس‌پذیر است. با درک مفاهیم اساسی EF Core، معماری و چرخه اجرای کوئری، و همچنین آگاهی از مشکلات رایج عملکرد و تکنیک‌های بهینه‌سازی، توسعه‌دهندگان می‌توانند کوئری‌های کارآمدتری بنویسند. رعایت بهترین روش‌ها و استفاده از ابزارهای تشخیص و تحلیل عملکرد نیز نقش مهمی در این فرآیند ایفا می‌کند. رویکرد مستمر به بهینه‌سازی عملکرد در طول چرخه توسعه نرم‌افزار و انجام تست و اندازه‌گیری عملکرد پس از اعمال تغییرات بهینه‌سازی، برای اطمینان از کارایی برنامه ضروری است. برای مطالعه و یادگیری عمیق‌تر در زمینه بهینه‌سازی EF Core، منابع متعددی در مستندات رسمی مایکروسافت و سایر منابع آنلاین موجود است.

لینک استاندارد شده: ZfhKnjxD

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.