واسه همین همیشه مجبور بودم که خودم دوباره بیام دستی روابط رو اصلاح کنم و مثلا رابطه هایی که بین جداول پایه و بقیه رو به صورت چند به چند ایجاد میکنه پاک کنم!
آیا این مشکل منطقی هست یا اینکه فقط این ابزار این مشکل رو داره؟
CATNETCmd /file:"I:\prog\bin\prog.dll" /search:"I:\prog" /report:"I:\prog\report.xsl" /rule:"J:\microsoft\cat.net\Rules"
using System.Reflection;
Assembly.GetExecutingAssembly().Location
new System.EnterpriseServices.Internal.Publish().GacInstall(path);
Assembly.LoadFile(path).GetName().GetPublicKey().Length
%GIT_HOME%\cmd;C:\Program Files (x86)\nodejs\;%JAVA_HOME%\bin;%ANT_HOME%\bin; %ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools; C:\ProgramData\Oracle\Java\javapath;
در قسمتهای قبلی (^ و ^) راهکارهایی جهت بالا بردن کارآیی، ارائه شد. در ادامه، به آخرین قسمت این سری اشاره خواهم کرد.
یادآوری: قبل از هر چیز با توجه به این مقاله دانستن این نکته الزامی است که فراخوانی برخی متدها مانند DbSet.Add سبب فراخوانی DataContext.ChangeTracker.DetectChanges خواهند شد.
فرض کنید قصد افزودن 2000 موجودیت دانش آموز را دارید:
for (int i = 0; i < 2000; i++) { Pupil pupil = GetNewPupil(); db.Pupils.Add(pupil); } db.SaveChanges();
اگر به تصویر بالا دقت کنید بیش از 34 ثانیه (خط 193 قسمت سوم شکل) جهت افزودن 2000 موجودیت به کانتکست سپری شده است. در حالی که درج این 2000 موجودیت کمی بیش از 1 ثانیه (خط 195 قسمت سوم شکل) که 379 میلی ثانیه (قسمت دوم شکل) آن مربوط به اجرای کوئری اختصاص یافته طول کشیده است.
همانطور که در شکل بالا مشخص است همان 34 ثانیه جهت ردیابی تغییرات صرف شده است. EF ردیابی تغییرات را بصورت پیش فرض هر زمانی که قصد افزودن یا ویرایش موجودیتی را داشته باشید، انجام خواهد داد و هر چه موجودیتهای بیشتری را بخواهید ویرایش یا اضافه نمایید، این زمان نیز بیشتر خواهد شد. در حقیقت زمان لازم برای الگوریتم ردیابی تغییرات بصورت نمایی با رشد موجودیتها افزوده میشود. به عبارت دیگر اگر این تعداد موجودیتها را به 4000 عدد برسانید، مدت زمان لازم بیش از 2 برابر افزوده خواهد شد.
var list = new List<Pupil>(); for (int i = 0; i < 2000; i++) { Pupil pupil = GetNewPupil(); list.Add(pupil); } db.Pupils.AddRange(list); db.SaveChanges();
db.Configuration.AutoDetectChangesEnabled = false;
string city = "New York"; List<School> schools = db.Schools .AsNoTracking() .Where(s => s.City == city) .Take(100) .ToList();
استفاده از متد AsNoTracking در کد بالا سبب خواهد شد 100 مدرسه که در شهر نیویورک وجود دارد توسط EF، بدون تحت نظر گرفتن آنها از بانک اطلاعاتی دریافت شوند و سرباری نیز تحمیل نشود.
معمولا، هنگامی که از EF برای اولین بار استفاده مینمایید، ویوهایی ایجاد میگردد که برای ایجاد کوئریها مورد استفاده قرار میگیرند. این ویوها در طول حیات برنامه فقط یکبار ایجاد میشوند. ولی همین یکبار هم زمانبر هستند. خوشبختانه راههایی وجود دارد که ایجاد این ویوها را در زمان runtime انجام نداد و آن راه، استفاده از ویوهای از پیش کامپایل شده است. یکی از راههای ایجاد این ویوها استفاده از Entity Framework Power Tools است. بعد از نصب اکستنشن، بر روی فایل کانتکست راست کلیک کرده و سپس گزینهی Generate Views را از منوی Entity Framework انتخاب کنید.
توجه داشته باشید که هر تغییری را بعد از ایجاد این ویوها بر روی کانتکست اعمال نمایید، باید آنها را مجددا تولید کنید. برای آشنایی بیشتر با این ویوها به این لینک مراجعه کنید. هم چنین پکیج نیوگتی بنام EFInteractiveViews نیز برای این منظور تهیه و توزیع شده است.
در هنگام شروع به کار با EF، چندین کوئری آغازین بر روی دیتابیس اجرا میشوند. یکی از کوئریهای آغازین جهت تشخیص نسخهی دیتابیس است که همانطور در تصویر زیر مشاهده میکنید، در حدود چند میلی ثانیه میباشد.
با توجه به توضیحات، در صورتیکه اطلاعی از نسخهی دیتابیس دارید، میتوانید این کوئری ابتدایی را تحریف نمایید. برای اینکار میتوان توسط متد ()ResolveManifestToken کلاسی که اینترفیس IManifestTokenResolver را پیاده سازی کرده است، نسخهی دیتابیس را برگردانیم و از یک رفت و برگشت به دیتابیس جلوگیری نماییم.
public class CustomManifestTokenResolver : IManifestTokenResolver { public string ResolveManifestToken(DbConnection connection) { return "2014"; } }
public class CustomDbConfiguration : DbConfiguration { public CustomDbConfiguration() { SetManifestTokenResolver(new CustomManifestTokenResolver()); } }
تخریب و از بین بردن کانتکست هنگامی که به آن نیاز نداریم بسیار ضروری است. یکی از روشهای اصولی برای Disposing کانتکست، محصور کردن آن بوسیله دستور Using است (البته فرض بر این است که قرار نیست از الگوهای اشاره شده استفاده نماییم). در صورت عدم تخریب صحیح کانتکست باید منتظر آسیب جدی به کارایی Garbage Collector جهت آزاد سازی منابع مورد استفاده کانتکست و هم چنین باز نمودن اتصالات جدید به دیتابیس باشید.
EF از قابلیتی بنام Multiple Result Sets میتواند بهره ببرد که این قابلیت باعث میشود بر روی یک کانکشن ایجاد شده، یک یا چند درخواست از دیتابیس ارسال و یا دریافت شود که سبب کاهش تعداد رفت و برگشت به دیتابیس میشود. کاربرد دیگر این قابلیت، زمانی است که تاخیر زیادی (latency) بین اپلیکیشن و دیتابیس وجود دارد.
برای فعالسازی کافی است مقدار زیر را در کانکشن استرینگ اضافه نمایید:
MultipleActiveResultSets=True;
در C#5 و EF6 پشتیبانی خوبی از متدهای ناهمگام فراهم شده است و اکثر متدهایی مانند ToListAsync, CountAsync, FirstAsync, SaveChangesAsync و غیره که باعث رفت و برگشت به دیتابیس میشوند امکان پشتیبانی ناهمگام را نیز دارند. البته این قابلیت برای برنامههای یک درخواست در یک زمان شاید مفید نباشد؛ ولی برای برنامههای وبی برعکس. در برنامه وب جهت پشتیبانی از بارگذاری همزمان (concurrent) قابلیت ناهمگام (Async) سبب خواهد شد منابع تا زمان اجرای کوئری به ThreadPool بازگردانده شود و برای سرویس دهی مهیا باشند که باعث افزایش scalability خواهد شد.
در اکثر مواقع کارآیی با حجیم شدن دادهها کاهش پیدا میکند (البته در صورت عدم رعایت اصول استاندارد). بنابراین بررسی کارآیی در محیط هایی با حجم دادههای بالا ضروری است. هیچ چیز بدتر از آن نیست که همه چیز در محیط توسعه خوب و بی نقص باشد ولی در محیط عملیاتی به شکست بیانجامد. به همین جهت سعی کنید از ابزارهای تولید داده (^ و ^ و ^) برای ایجاد دادههای آزمایشی استفاده نمایید. سپس کارآیی کوئری خود را مورد بررسی و آزمایش قرار دهید.
$(<Macro_Name>)
$(OutDir) یا $(ProjectName)
Copy $(OutDir)*.* %WinDir%
Copy bin\Debug\*.* %WinDir%
Copy "$(ProjectDir)$(OutDir)*.*" c:\test
"$(ProjectDir)postBuild.bat" "$(SolutionPath)"
echo --------------------------------------------------------------------------- echo Copy "$(ProjectDir)$(OutDir)*.*" c:\test --Starting... Copy "$(ProjectDir)$(OutDir)*.*" c:\test if errorlevel 1 goto error echo Copy "$(ProjectDir)$(OutDir)*.*" c:\test --DONE! echo --------------------------------------------------------------------------- echo --------------------------------------------------------------------------- echo Copy $(OutDir)*.* c:\test --Starting... Copy $(OutDir)*.* c:\test if errorlevel 1 goto error echo Copy $(OutDir)*.* c:\test --DONE! echo --------------------------------------------------------------------------- goto ok :error echo POSTBUILDSTEP for $(ProjectName) FAILED notepad.exe exit 1 :ok echo POSTBUILDSTEP for $(ProjectName) COMPLETED OK
if $(ConfigurationName) == Release ( gacutil.exe /i "$(SolutionDir)$(OutDir)$(TargetFileName)" )
... <PropertyGroup> <PostBuildEvent>echo --------------------------------------------------------------------------- echo Copy "$(ProjectDir)$(OutDir)*.*" c:\test --Starting... Copy "$(ProjectDir)$(OutDir)*.*" c:\test if errorlevel 1 goto error echo Copy "$(ProjectDir)$(OutDir)*.*" c:\test --DONE! echo --------------------------------------------------------------------------- echo --------------------------------------------------------------------------- echo Copy $(OutDir)*.* c:\test --Starting... Copy $(OutDir)*.* c:\test if errorlevel 1 goto error echo Copy $(OutDir)*.* c:\test --DONE! echo --------------------------------------------------------------------------- goto ok :error echo POSTBUILDSTEP for $(ProjectName) FAILED notepad.exe exit 1 :ok echo POSTBUILDSTEP for $(ProjectName) COMPLETED OK</PostBuildEvent> </PropertyGroup> ...
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <LangVersion>9.0</LangVersion> </PropertyGroup> </Project>
<TargetFramework>netstandard2.1</TargetFramework> <LangVersion>9</LangVersion>