نظرات اشتراکها
اشتراکها
پروژه مدیریت محتوای OrchardCore
آیا تا به حال لیستی از دیتا داشتهاید که بخواهید بر روی آنها کاری را انجام دهید؟ مثلا لیستی از مشتریان که باید برای تک تک آنها Pdf ای را بسازید، یا لیستی از مشتریان که باید برای تک تک آنها بیمه نامه صادر کنید، یا مثلا لیست اطلاعات بلیطهای قابل فروش را گرفتهاید و برای تک تک آنها میخواهید کمیسیون حساب کنید و ...
در اکثر مواقعی کاری که برای تک تک آیتمها قرار است انجام شود، ساده است و با استفاده از یک حلقه foreach کار تمام میشود. اما در بعضی مواقع کار زمانبر است؛ حال یا به علت وجود کاری CPU bound مثل درست کردن Pdf و محاسبات، یا کار IO Bound است مثل ارسال یک HTTP Request به ازای هر مشتری، یا ذخیره کردن چیزی در دیتابیس که هم CPU bound است و هم IO bound و ترکیبی از مواردی که گفتیم را دارد.
فرض کنیم صد مشتری داریم و برای تک تک آنها میخواهیم کاری انجام دهیم. اگر از یک foreach ساده استفاده کنیم و هر عمل یک ثانیه طول بکشد، کل روال 100 ثانیه طول میکشد که جالب نیست.
public async Task Sample() { var customers = await GetCustomersFromSomeWhere(); foreach (var customer in customers) { await DoSomethingWithCustomer(customer); } }
با اندکی جستجو در اینترنت به Task.WhenAll میرسیم و مشکلی که دارد این است که هر 100 کار را با هم شروع میکند که میتواند اثرات مخربی روی کلیت عملکرد سرور بگذارد:
public async Task Sample() { var customers = await GetCustomersFromSomeWhere(); await Task.WhenAll(customers.Select(c => DoSomethingWithCustomer(c))); }
public async Task Sample() { var customers = await GetCustomersFromSomeWhere(); await customers.Select(c => Observable.FromAsync(() => DoSomethingWithCustomer(c))).Merge(maxConcurrent: 25); }
استفاده از Rx وقتی که دستورات داخل DoSomethingWithCustomer به صورت IO bound باشند (اتصال به دیتابیس و ارسال Http Request و ...) به خوبی جواب میدهد. ولی اگر دستورات داخل DoSomethingWithCustomer به صورت CPU bound باشند، مثل محاسبات یا ساختن Pdf و ... دیگر این روش جواب نمیدهد و اینجاست که باید از Task Parallel Library استفاده کنیم ( البته Task Parallel Libraray یا به اختصار TPL هم برای IO Bound و هم برای CPU Bound مناسب است).
برای استفاده از TPL داریم:
public async Task Sample() { var customers = await GetCustomersFromSomeWhere(); ActionBlock<Customer> action = new ActionBlock<Customer>(c => DoSomethingWithCustomer(c), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 25 }); foreach (var customer in customers) { action.Post(customer); } action.Complete(); await action.Completion; }
همانطور که میبینید، بحث 25 تا 25 تا اجرا کردن در اینجا نیز وجود دارد، با این تفاوت که بسیار هوشمندانهتر کارها را به صورتی پیش میبرد که از منابع سرور به بهینهترین شکل ممکن استفاده شود و همین TPL را هم برای اعمال IO bound و هم اعمال CPU bound مناسب میکند.
گزینههایی از قدیم نیز وجود دارند، مانند استفاده از Thread و Semaphore و ... که ابدا استفاده مستقیم از آنها توصیه نمیشود.
البته با TPL و RX میشود کارهای خیلی بیشتری نیز انجام داد و این دو فقط برای این سناریو ساخته نشدهاند و همه جا جایگزین یکدیگر نیستند و هر دو دنیای وسیعی هستند که توصیه میکنم به هر دو نگاهی بیاندازید. همچنین TPL تا جایی که میدانم جزو مواردی است که در بیرون از دنیای NET. وجود ندارد و یکی از ارزشمندترین ویژگیهای Unique در NET. است که به این سادگی چنین مسئلهای با این کیفیت حل شود.
توجه داشته باشید که اگر فرآیندی که برای تک تک Customerها در مثال فوق قرار است انجام شود، خود یک روال سنگین و زمان بر باشد، بهتر است از روشهای دیگری مبتنی بر Event processing و ابزارهایی چون Azure Service Bus یا Mass Transit استفاده کنیم که کمک میکنند اگر مثلا سه سرور داریم، بار پردازش آن 100 مشتری مثال ما، بین سه سرور هم پخش شود که این مورد پیچیدگیهای خود را دارد و در اینجا که فرض بر این است که سناریو خیلی پیچیده و میزان بار خیلی زیاد نیست و همچنین نیازی هم به استفاده از این موارد و اضافه کردن پیچیدگیهای بیشتر به برنامه نیست. در واقع TPL علیرغم کار بسیار ارزشمندی که میکند، در نهایت یک Nuget Package است که در یک دستگاه موبایل Android و با Xamarin نیز قابل استفاده است.
البته این همه داستان نیست. برای مثال در صورتی که فرآیندی بخواهد به صورت Concurrent / Parallel انجام شود و در انجام آن از Entity Framework Db Context استفاده شده باشد، کد به مشکل بر میخورد و خطا میدهد، چون یک Instance از DbContext مناسب انجام چند کار همزمان نیست. به واقع تمامی Objectهایی که Thread Safe نباشند، در روشهای فوق به مشکل بر میخوردند. همچنین بحث مدیریت کردن Transaction در صورتی که بخواهید با دیتابیس هم کار کنید نیز خود مسئلهای است که باید حل شود.
حل مسائلی که گفته شد و ادغام کردن روشهای فوق با بحث Dependency Injection و ... موضوع بحث قسمت بعدی این مطلب است.
هر بانکی بر اساس مستنداتی که ارائه میکنه، مشخص خواهد کرد که دیتا را باید با چه فرمتی به سمت آن ارسال کنید (و با چه فرمتی اطلاعات نهایی را دریافت خواهید کرد). این قسمت همیشه برای آن بانک ثابت است و در این کتابخانه پیاده سازی شده. بنابراین بهترین امنیتی را که در این بین میتوانید برقرار کنید، پیاده سازی SSL در سایت خودتان است تا رفت و برگشت اطلاعات رمزنگاری شده و در بین راه قابل شنود نباشد.
با تشکر. به علت اینکه در تابع getFile فایلی که برگشت داده میشود ممکن است MediaTypeهای مختلفی داشته باشد، بهتر است از MediaTypeMap استفاده بشود. برای نصب آن میتوانید از دستور Install-Package MediaTypeMap برای نصب از nuget استفاده کنید و پسوند فایل را ارسال کرده و Content Type آن را بدست آورید.
نظرات مطالب
EF Code First #12
- من هر نوع طراحی رو تائید نمیکنم. چرا یک برنامه باید چندین DbContext
داشته باشد؟ نیازی نداره. چرا باید چندین ماژول کنترلر داشته باشه؟
- سؤال شما خارج از موضوع بحث است (در اینجا بحثی در مورد طراحی «افزونه پذیر» مطرح نشده). برای طراحی افزونه پذیر میتونید به مباحث زیر مراجعه کنید:
ابتدا فقط و فقط یک DbContext مرکزی را در کل برنامه تعریف کنید. بعد تنظیمات نگاشتها را به صورت پویا یافته و به آن اضافه کنید. سپس موجودیتهای مهیا را به صورت پویا یافته و به Context مرکزی اضافه نمائید.
+ در EF نمیتونید در عمل چندین DbContext داشته باشید مرتبط با یک دیتابیس. Change tracking در EF بر مبنای یک DbContext کار میکند. اگر قرار باشد چندین وهله از DbContextهای مختلف مثلا در طی یک درخواست وجود داشته باشند، یعنی چندین اتصال باز شده به دیتابیس و چندین تراکنش مجزا در حال انجام است (کل بحث جاری از ابتدا). به علاوه قابلیت کار کردن با چندین موجودیت را به صورت همزمان در طی یک تراکنش از دست میدهید.
- برای اینکه در حین کار با Structure Map خطای Circular dependency را مشاهده نکنید، نیاز است یک کتابخانه یا حتی یک کلاس واسط طراحی کنید تا مشترکات در آن قرار گیرند.
- سؤال شما خارج از موضوع بحث است (در اینجا بحثی در مورد طراحی «افزونه پذیر» مطرح نشده). برای طراحی افزونه پذیر میتونید به مباحث زیر مراجعه کنید:
ابتدا فقط و فقط یک DbContext مرکزی را در کل برنامه تعریف کنید. بعد تنظیمات نگاشتها را به صورت پویا یافته و به آن اضافه کنید. سپس موجودیتهای مهیا را به صورت پویا یافته و به Context مرکزی اضافه نمائید.
+ در EF نمیتونید در عمل چندین DbContext داشته باشید مرتبط با یک دیتابیس. Change tracking در EF بر مبنای یک DbContext کار میکند. اگر قرار باشد چندین وهله از DbContextهای مختلف مثلا در طی یک درخواست وجود داشته باشند، یعنی چندین اتصال باز شده به دیتابیس و چندین تراکنش مجزا در حال انجام است (کل بحث جاری از ابتدا). به علاوه قابلیت کار کردن با چندین موجودیت را به صورت همزمان در طی یک تراکنش از دست میدهید.
- برای اینکه در حین کار با Structure Map خطای Circular dependency را مشاهده نکنید، نیاز است یک کتابخانه یا حتی یک کلاس واسط طراحی کنید تا مشترکات در آن قرار گیرند.
یک نکتهی تکمیلی: امکان بالابردن سرعت بارگذاری برنامههای مبتنی بر EF 6.2 به صورت توکار
با به روز رسانی ارجاعات EF مورد استفاده:
تنها کافی است قطعه کد ذیل را به اسمبلی حاوی Context خود اضافه کنید:
تشخیص و استفادهی از آن توسط EF 6.2 خودکار است. پس از آن یک کش محلی، از مدل سیستم تهیه میشود (Entity Framework Code First Model Cache) و در مسیری که قید شده (پارامتر DefaultDbModelStore)، ذخیره خواهد شد؛ مانند:
بازسازی این کش محلی بجای تولید پویای آن در هربار بارگذاری برنامه (که سرعت آغاز برنامه را کاهش میدهد)، بر اساس مقایسهی تاریخ آخرین تغییر اسمبلی حاوی Context برنامه با تاریخ آخرین تغییر فایل کش محلی است. اگر این دو یکی نبودند، این کش بازتولید خواهد شد.
پس از این تغییر کوچک، اولین بار اجرای برنامه همانند حالت تولید پویای Entity Framework Code First Model Cache که پیشتر در حافظه انجام میشد، اندکی طول کشیده و نتیجهی آن در فایل edmx یاد شده ذخیره میشود. از بار دوم اجرای برنامه، Model Cache از فایل edmx محلی خوانده شده و به این ترتیب سرعت آغاز برنامه به شدت افزایش خواهد یافت.
برای نمونه عنوان شدهاست که با استفاده از این روش، سرعت بارگذاری Context ایی با 600 مدل، از 14 ثانیه به 2 ثانیه کاهش یافتهاست.
یک نکته: برای برنامههای وب بهتر است از مسیر پوشهی محافظت شدهی App_Data به عنوان پارامتر DefaultDbModelStore استفاده کنید:
با به روز رسانی ارجاعات EF مورد استفاده:
To Update PM> Update-Package EntityFramework -Version 6.2.0 To install PM> Install-Package EntityFramework -Version 6.2.0
public class MyDbConfiguration : DbConfiguration { public MyDbConfiguration() : base() { SetModelStore(new DefaultDbModelStore(Directory.GetCurrentDirectory())); } }
/bin/Debug/MyAssembly.MyNamespace.MyDbContext.edmx
پس از این تغییر کوچک، اولین بار اجرای برنامه همانند حالت تولید پویای Entity Framework Code First Model Cache که پیشتر در حافظه انجام میشد، اندکی طول کشیده و نتیجهی آن در فایل edmx یاد شده ذخیره میشود. از بار دوم اجرای برنامه، Model Cache از فایل edmx محلی خوانده شده و به این ترتیب سرعت آغاز برنامه به شدت افزایش خواهد یافت.
برای نمونه عنوان شدهاست که با استفاده از این روش، سرعت بارگذاری Context ایی با 600 مدل، از 14 ثانیه به 2 ثانیه کاهش یافتهاست.
یک نکته: برای برنامههای وب بهتر است از مسیر پوشهی محافظت شدهی App_Data به عنوان پارامتر DefaultDbModelStore استفاده کنید:
var appDataDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data"); //OR var appDataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();