مطالب
آپلود همزمان چندین فایل در Asp.Net Web Forms
تا قبل از آمدن html5 امکان آپلود چندین فایل در Asp.net web forms امکان پذیر نبود و کاربران می‌بایستی فایل‌های مورد نظر خود را یکی یکی انتخاب و آپلود می‌کردند که تا حد زیادی سخت و حوصله زیادی هم می‌خواست. اما با معرفی html5  یک attribute به تگ مربوط به آپلود فایل به اسم AllowMultiple افزودن شد که مقادیر قابل قبول این attribute مقادیر بولی true,false می‌باشند. اگر این attribute به تگ‌های مربوط به آپلود فایل اضافه نشود، به صورت پیش فرض قادر خواهید بود که هر بار فقط یک فایل را برای آپلود انتخاب کنید. با مقدار دهی این Attribute با مقدار true این اجازه به شما داده می‌شود که در هر بار بتوانید چندین فایل را به صورت همزمان آپلود نمایید.
کد زیر نمایشی از چگونگی بکاربردن این attribute در تگ input می‌باشد:
<input type="file" multiple="multiple" name="FileUpload1" id="FileUpload1" />
این Attribute از نسخه ASP.NET 4.5 Framework به Asp.net Web forms اضافه شده و برنامه نویسان می‌توانند در صورت نیاز از این امکان استفاده کنند.
کد زیر، نحوه افزودن این Attribute به تگ FileUpload  در ASP.Net Web Forms می‌باشد:
<asp:FileUpload runat="server" ID="FileUploadMultiple" AllowMultiple="true" />
 و اما به چه روشی می‌توانیم فایل یا فایل‌های انتخاب شده را در این حالت آپلود کرد؟
در صورتیکه از این روش در پروژه‌هایتان استفاده کنید فقط کافیست با یک حلقه تمامی کنترل‌های مورد نظر را پیمایش و هر کدام از فایل‌ها را آپلود و ذخیره نمایید.
برای درک بهتر مطلب، پروژه جدیدی در Asp.net web Forms ایجاد کرده و کنترل‌های زیر را به آن اضافه کنید:
 <asp:FileUpload runat="server" ID="FileUploadMultiple" AllowMultiple="true" />
        <asp:Button runat="server" ID="btnUlpad" Text="Upload" OnClick="btnUlpad_Click" />
       <asp:Label runat="server" ID="lblMessage"></asp:Label>
و در رویداد مربوط به دکمه آپلود، قطعه کد زیر را قرار دهید :
            int Count = 0;
            foreach (var item in FileUploadMultiple.PostedFiles)
            {
                string Extension = Path.GetExtension(item.FileName);
                string FileName = new Random().Next(1, 50).ToString()+Extension;
                item.SaveAs(Server.MapPath("~")+"//File//"+FileName);
                Count++;
            }
            if (Count == FileUploadMultiple.PostedFiles.Count)
                lblMessage.Text = string.Format("فایل‌های انتخابی با موفقیت آپلود شدند");
            else
                lblMessage.Text = string.Format("{0} از {1} فایل با موفقیت آپلود شد", Count, FileUploadMultiple.PostedFiles.Count);
کدهای پروژه ;Upload-multiple-.rar 
مطالب
بررسی الگوی Chain of Responsibility در جاوا اسکریپت
الگوی Chain of Responsibility، یک زنجیر، از اشیاء متصل شده‌ی به هم را فراهم می‌کند که یکی از آنها می‌تواند درخواست رسیده را راضی کند؛ به عبارتی دیگر به محض دریافت درخواست، آن را پردازش می‌کند. این الگو اساسا یک جستجوی خطی ( linear search )، برای یک شیء می‌باشد که می‌تواند یک درخواست ویژه را handle کند. 

الگوی chain-of-responsibility، ارتباط با الگوی Chaining دارد که به دفعات در جاوا اسکریپت استفاده شده‌است (jQuery  استفاده‌ی گسترده‌ای از این الگو کرده‌است).
این الگو اجازه میدهد که یک درخواست ارسال شده‌ی توسط کلاینت، توسط یک یا بیش از یک object، دریافت شود. یک مثال عمومی از این الگو،  event bubbling در DOM  می‌باشد. یک رویداد از طریق element‌‌‌های تودرتوی متفاوت انتشار پیدا می‌کند؛ تا زمانیکه یکی از آن‌ها، آن را handle کند.


مثال زیر را در نظر بگیرید:
class HandlerChain 
{ 
   setNextObj(nextObjInChain){}
   processMultiple(req){
     console.log("No multiple for: " + req.getMultiple());
   }
} 
  
class Multiple
{
  constructor(multiple){
    this.multiple = multiple;
  }
  
  getMultiple(){ 
    return this.multiple; 
  } 
  
} 
  

class MultipleofTwoHandler extends HandlerChain
{
  constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
  } 
  
  setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
  } 
  
  processMultiple(req) { 
    if ((req.getMultiple() % 2) == 0) { 
      console.log("Multiple of 2: " + req.getMultiple()); 
    }else{ 
      this.nextObjInChain.processMultiple(req); 
    } 
  } 
} 
  
class MultipleofThreeHandler extends HandlerChain 
{ 
  constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
  } 
  
   setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
  } 
  
  processMultiple(req) 
  { 
    if ((req.getMultiple() % 3) == 0) { 
      console.log("Multiple of 3: " + req.getMultiple()); 
    }else{ 
          this.nextObjInChain.processMultiple(req); 
        } 
    } 
} 


class MultipleofFiveHandler extends HandlerChain
{ 
  constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
  }
  
  setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
  } 
  
  processMultiple(req) { 
    if ((req.getMultiple() % 5) == 0) { 
      console.log("Multiple of 5: " + req.getMultiple()); 
    }else{ 
      this.nextObjInChain.processMultiple(req); 
      } 
    } 
} 

//configuring the chain of handler objects
var c1 = new MultipleofTwoHandler(); 
var c2 = new MultipleofThreeHandler(); 
var c3 = new MultipleofFiveHandler(); 
c1.setNextObj(c2); 
c2.setNextObj(c3); 
  
//the chain handling different cases
c1.processMultiple(new Multiple(95));  // Multiple of 5: 95  
c1.processMultiple(new Multiple(50));  // Multiple of 2: 50 
c1.processMultiple(new Multiple(9));   // Multiple of 3: 9 
c1.processMultiple(new Multiple(4));   // Multiple of 2: 4 
c1.processMultiple(new Multiple(21));  // Multiple of 3: 21 
c1.processMultiple(new Multiple(23));  // No multiple for: 23  
مثال بالا، الگوی chain of responsibility را پیاده سازی می‌کند و بررسی می‌کند که آیا عدد وارد شده، مضربی از 2، 3 یا 5 است و یا نه.
چگونه می‌توانیم این قابلیت را پیاده سازی کنیم؟ ما می‌خواهیم که یک عدد داشته باشیم و سپس به handler‌ها در زنجیره اجازه بدهیم که تصمیم گیری کنند که آیا آنها می‌خواهند عدد وارد شده را پردازش کنند، یا به handler بعدی پاس بدهند.

ما 3 نوع handler در این زنجیره داریم:

  • MultipleofTwoHandler: بررسی می‌کند که آیا عدد وارد شده، مضربی از 2 است یا نه. 
  • MultipleofThreeHandler: بررسی می‌کند که آیا عدد وارد شده، مضربی از 3 است یا نه 
  • MultipleofFiveHandler: بررسی می‌کند که آیا عدد وارد شده، مضربی از 5 است یا نه. 

اولین قدم، ایجاد یک زنجیره از handler‌های بالا می‌باشد. در اینجا یک کلاس را به نام HandlerChain، برای همین منظور داریم و این کلاس شامل دو تابع، به نام‌های setNextObj و processMultiple می‌باشد.
class HandlerChain 
{ 
   setNextObj(nextObjInChain){}
   processMultiple(req){
     console.log("No multiple for: " + req.getMultiple());
   }
}

پیاده سازی پیش فرض processMultiple، زمانی اجرا می‌شود که هیچ مضربی برای یک عدد، وجود نداشته باشد. به عبارت دیگر، عدد مورد نظر، مضربی از 2،3 و 5 نباشد ( از بین مضرب‌های تعین شده). 
تمام handler‌ها در این زنجیره، از این کلاس ارث بری می‌کنند. هر handler می‌تواند دو عملیات را انجام دهد:

  1. تنظیم کردن handler بعدی در زنجیره 
  2. پردازش عدد وارد شده به این منظور که آیا در مضرب مورد نظر قرار دارد یا نه. 

class MultipleofTwoHandler extends HandlerChain
{ 
  constructor(){/*code*/}
  setNextObj(nextObj){/*code*/}  
  processMultiple(req){/*code*/}
}
  
class MultipleofThreeHandler extends HandlerChain
{
  constructor(){/*code*/}
  setNextObj(nextObj){/*code*/}  
  processMultiple(req){/*code*/}
}


class MultipleofFiveHandler extends HandlerChain
{
  constructor(){/*code*/}
  setNextObj(nextObj){/*code*/}  
  processMultiple(req){/*code*/}
}

constructor برای هر handler، همانند زیر تعریف شده‌است:
constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
}

در ابتدا توسط  super ،  مقدار دهی اولیه‌ای برای متد‌های setNextObj و processMultiple از کلاس پدر انجام میشود. همچنین در constructor، متغیر nextObjInChain
مقدار دهی اولیه می‌شود که تعیین کننده‌ی شیء بعدی در زنجیره می‌باشد. 
 
 چگونه شیء جاری، شیء بعد را در زنجیره تعیین می‌کند؟ اجازه بدهید برای این منظور نگاهی به تابع setNextObj داشته باشیم. setNextObj در هر handler، همانند زیر تعیین شده‌است: 
setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
}

اکنون می‌توانیم زنجیره‌ای از handler‌ها را همانند زیر ایجاد کنیم: 
var c1 = new MultipleofTwoHandler(); 
var c2 = new MultipleofThreeHandler(); 
var c3 = new MultipleofFiveHandler(); 
c1.setNextObj(c2); 
c2.setNextObj(c3);

در اینجا ما handler‌‌‌های c2, c1  و c3 را به منظور پردازش کردن مضرب‌های 2، 3 و 5 ایجاد کرده‌ایم. آن‌ها به یکدیگر به حالت زیر متصل شده‌اند:
 c2 به عنوان handler بعدی برای c1 و c3 به عنوان handler بعدی برای c2. 


اکنون که زنجیره ایجاد شده‌است، زمان آن است که عدد وارد شده را پردازش کنیم. اجازه بدهید که کار را با ایجاد کردن شیء “multiple”  با استفاده از کلاس Multiple، شروع کنیم.

class Multiple
{
  constructor(multiple){
    this.multiple = multiple
  }
  
  getMultiple(){ 
    return this.multiple; 
  }  
}

یک شیء Multiple، شامل یک خصوصیت به نام multiple و یک متد است به نام getMultiple که به منظور برگشت دادن multiple می‌باشد.
 
MultipleofTwoHandler به صورت زیر تعریف شده‌است:
processMultiple(req) { 
    if ((req.getMultiple() % 2) == 0) { 
      console.log("Multiple of 2: " + req.getMultiple()); 
    }else{ 
      this.nextObjInChain.processMultiple(req); 
    } 
}

در اینجا، یک multiple دریافت شده و چک می‌شود که آیا مضربی از 2 می‌باشد یا نه؛ اگر چنین است، سپس عدد دریافت شده را به عنوان مضربی از 2 نمایش می‌دهد. در صورتیکه مضربی از 2 نباشد، handler آن را به شیء بعدی در زنجیره، پاس میدهد و سپس همین روال دوباره تکرار می‌شود و تا زمانیکه یکی از handler ‌ها، جواب را برگشت دهد، این روال ادامه پیدا می‌کند. 
 
تعریف تابع processMultiple  برای هر 3 handler یکسان میباشد؛ با این تفاوت که MultipleofThreeHandler، برای بررسی مضربی از 3 بودن و MultipleofFiveHandler  برای بررسی مضربی از 5 بودن است.

اجازه بدهید یک مثال بزنیم و ببینیم که چه اتفاقی می‌افتد:
c1.processMultiple(new Multiple(95)) // Multiple of 5: 95  

اولین handler در زنجیره که c1 است، یک multiple (95) را دریافت می‌کند و چک می‌کند که آیا مضربی از 2 است یا نه. جواب خیر است؛ بنابراین multiple به دومین handler  در زنجیره پاس داده میشود؛ یعنی به handler بررسی کننده‌ی مضربی از 3 . در اینجا دوباره بررسی میشود که آیا عدد مورد نظر، مضربی از 3 است یا نه؟ که جواب خیر است. در ادامه multiple به handler سوم در زنجیره که بررسی کننده‌ی مضربی از 5 است، پاس داده میشود. در اینجا، شرط درست است و عدد 95 به عنوان مضربی از 5 چاپ میشود. 


چه زمانی از این الگو استفاده کنیم؟
به منظور مدیریت کردن انواع درخواست‌ها، به روش‌های متفاوت، بدون دانستن ترتیب و نوع درخواست از قبل، از این الگو استفاده می‌کنیم. این الگو به ما اجازه می‌دهد که زنجیره‌ای از چند handler را داشته باشیم. تمامی handler‌ها، یک شانس را جهت پردازش درخواست دارند.  
مطالب دوره‌ها
استفاده از Async و Await در برنامه‌های ASP.NET MVC
از ASP.NET MVC 4 به بعد، امکان استفاده از اکشن متدهای async در ASP.NET MVC میسر شده‌است. البته همانطور که پیشتر نیز ذکر شد، شرط استفاده از امکانات async در نگارش‌های پیش از دات نت 4.5، استفاده از کامپایلری است که بتواند کدهای async را تولید کند و این مورد تنها از VS 2012 به بعد ممکن شده‌است.

علت استفاده از اکشن متدهای async در ASP.NET MVC

اگر نیاز دارید که برنامه‌ی وبی، به شدت مقیاس پذیر را تولید کنید، باید بتوانید مجموعه تردهای سیستم را تا حد ممکن مشغول به کار و سرویس دهی نگه دارید. در برنامه‌های وب ASP.NET تنها تعداد مشخصی ترد، برای پاسخ دهی به درخواست‌های رسیده، همواره مشغول به کار می‌باشند. در اینجا اگر این تردها را برای مدت زمان زیادی جهت اعمال IO مشغول نگه داریم، دست آخر به سیستمی خواهیم رسید که تردهای مفید آن، جهت پایان عملیات مرتبط بیکار شده‌اند و دیگر ASP.NET قادر نیست از آن‌ها جهت پاسخ‌دهی به سایر درخواست‌های رسیده استفاده کند.
برای مثال یک اکشن متد را درنظر بگیرید که نیاز است با یک وب سرویس، برای دریافت نتیجه کار کند. اگر این عملیات اندکی طول بکشد، به همین میزان ترد جاری درحال پردازش این درخواست، بیکار شده و منتظر دریافت پاسخ خواهد ایستاد و اگر به همین ترتیب تعداد تردهای بیکار، بیشتر و بیشتر شوند، دیگر سیستم قادر نخواهد بود به درخواست‌های جدید رسیده پاسخ دهد و ASP.NET مجبور خواهد شد این درخواست‌ها را در صف قرار دهد تا بالاخره زمانی این تردها آزاد شده و قابل استفاده‌ی مجدد گردند. برای رفع این مشکل، استفاده از اعمال غیرهمزمان ابداع گردیدند تا در آن‌ها ترد مورد استفاده جهت پردازش درخواست رسیده را آزاد کرده و به این ترتیب دیگر نیازی نباشد تا تردجاری، تا پایان عملیات IO بلاک شده و بدون استفاده باقی بماند.
در ASP.NET MVC 3 برای نوشتن اکشن متدهای async می‌بایستی از روش قدیمی مدل‌های Async در دات نت مانند APM استفاده می‌شد و همچنین کنترلر جاری بجای ارث بری از کلاس Controller می‌بایستی از کلاس AsyncController مشتق می‌شد. به علت سخت بودن استفاده از آن، این روش و کنترلرهای async در نگاش 3 آن آنچنان مقبولیت و استفاده‌ی گسترده‌ای نیافتند. چون هر اکشن متد تبدیل می‌شد به دو قسمت Begin و End متداول روش‌های APM. سپس در کشن متد دومی، نتیجه‌ی این عملیات به View بازگشت داده می‌شد.
از ASP.NET MVC 4 به بعد، خالی کردن تردهای سیستم و استفاده‌ی مجدد و مشغول به کار نگه داشتن مداوم آن‌ها با استفاده از امکانات توکار زبان‌هایی مانند سی‌شارپ 5، ساده‌تر و خواناتر شده‌است.
البته باید دقت داشت که این بحث صرفا سمت سرور بوده و ارتباطی به مباحث غیرهمزمان سمت کلاینت، مانند Ajax ندارد (A در Ajax نیز به معنای Async است) و از دید مصرف کننده‌ی نهایی، نامرئی می‌باشد. کار Aajx در سمت کلاینت نیز خالی کردن ترد UI مرورگر است (و نه سرور) و در نهایت تهیه‌ی برنامه‌هایی با قابلیت پاسخ‌دهی بهتر.


نوشتن اکشن متدهای Async در ASP.NET MVC

اولین کاری که باید صورت گیرد، اندکی تغییر امضای اکشن متدهای متداول است:
 public ActionResult Index()
این اکشن متد متداول، در یک ترد اجرا شده و این ترد تا پایان کار آن بلاک خواهد شد. برای مثال اگر قرار است مانند قسمت قبل، متد GetStringAsync در آن پردازش شود، تا پایان مدت زمان پردازش این متد، ترد جاری بلاک شده و سیستم قادر به استفاده‌ی مجدد از آن جهت پاسخ‌دهی به سایر درخواست‌های رسیده نخواهد بود. برای تبدیل آن به یک اکشن متد async باید به نحو ذیل عمل کرد:
 public async Task<ActionResult> Index()
ابتدا واژه‌ی کلیدی async به ابتدای امضای متد اضافه می‌شود. سپس خروجی آن اینبار بجای ActionResult، نسخه‌ی جنریک Task of T خواهد بود. همچنین دیگر نیازی نیست مانند MVC 3، کنترلر جاری از کلاس AsyncController مشتق شود.
زمانیکه به امضای متدی، async اضافه می‌شود، یعنی جایی در داخل بدنه‌ی آن باید await بکار رود:
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Mvc;

namespace Async11.Controllers
{
    public class HomeController : Controller
    {
        public async Task<ActionResult> Index()
        {
            var url = "https://www.dntips.ir";
            var client = new HttpClient(); // make sure you have an assembly reference to System.Net.Http.dll
            client.DefaultRequestHeaders.UserAgent.ParseAdd("Test Async");
            var result = await client.GetStringAsync(url);
            return View(result);
        }
    }
}
بنابراین اگر داخل اکشن متد جاری، جایی از await استفاده نمی‌شود، async کردن آن بی‌معنا است. این await است که سبب آزاد شدن ترد جاری جهت استفاده‌ی مجدد از آن برای پاسخ‌دهی به سایر درخواست‌های رسیده می‌شود.


یک نکته در مورد WCF 4.5

از WCF 4.5 به بعد، در صفحه‌ی معروف Add service references آن، با کلیک بر روی گزینه‌ی advanced و تنظیمات سرویس، امکان انتخاب گزینه‌ی Create task based operations نیز وجود دارد. این مورد دقیقا برای سهولت استفاده از آن با async و await سی‌شارپ 5 و مدل TAP آن طراحی شده‌است.


تعیین timeout در اکشن متدهای async

برای مشخص سازی صریح timeout در اکشن متدهای غیرهمزمان، می‌توان از ویژگی خاصی به نام AsyncTimeout به نحو ذیل استفاده کرد:
   [AsyncTimeout(duration: 1200)]
  public async Task<ActionResult> Index(CancellationToken ct)
در مورد لغو اعمال غیرهمزمان پیشتر صحبت شد. در اینجا پارامتر CancellationToken توسط فریم ورک جاری تنظیم شده و می‌توان آن‌را به متدهایی که قادرند اعمال غیر همزمان خود را بر اساس درخواست رسیده CancellationToken لغو کنند، ارسال کرد.


استفاده از قابلیت‌های غیرهمزمان EF 6 به همراه ASP.NET MVC 5

EF 6 به همراه یک سری متد و همچنین متد الحاقی جدید است که اعمال Async را پشتیبانی می‌کنند. اگر در حین انتخاب گزینه‌ی ایجاد کنترلر جدید، گزینه‌ی MVC 5 Controller with views, using EF را انتخاب کنید، امکان تولید اکشن متدهای async نیز به صورت پیش فرض پیش بینی شده‌است:


   public async Task<ActionResult> Index()
  {
     var model = await db.Books.ToListAsync();
     return View(model);
  }
در اینجا نیز امضای اکشن متد، همانند توضیحاتی است که در ابتدای بحث ارائه شد. فقط بجای متد ToList معمولی EF، از نگارش Async آن استفاده شده‌است و همچنین برای دریافت نتیجه‌ی آن از کلمه‌ی کلیدی await استفاده گردیده است.
به علاوه متد Find اکنون معادل FindAsync نیز دارد و همچنین SaveChanges دارای معادل غیرهمزمانی شده‌است به نام SaveChangesAsync .
البته باید دقت داشت که برای Where معادل Async ایی طراحی نشده‌است؛ زیرا نوع IQueryable صرفا یک عبارت است و اجرای آن تا زمانیکه ToList، First و امثال آن فراخوانی نشوند، به تعویق خواهد افتاد.
مطالب
ساخت دیتابیس sqlite با EF6 Code First
تا نسخه EF6 و minor‌های آن به دلیل عدم پشتیبانی داریور sqlite از migration، ساخت دیتابیس با code first ممکن نیست برای همین مجبور هستند از پیاده سازی‌های خودشان و موجود بودن دیتابیس از قبل با استفاده از EF با آن کار کنند که یکی از مثال‌های آن در این آدرس قرار دارد و سعی دارد کلاسی مشابه sqlitehelper در اندروید که کار ساخت دیتابیس و مدیریت نسخه را دارد بسازد و از آن استفاده کند. البته در EF7 این مشکل حل شده است و تیم دات نت تمهیداتی را برای آن اندیشیده‌اند. در این نوشتار قصد داریم با استفاده از یک کتابخانه که توسط آقای مارک سالین نوشته شده است کار ساخت دیتابیس را آسانتر کنیم. این کتابخانه که با دات نت 4 به بعد کار میکند خیلی راحت می‌تواند دیتابیس شما را به روش Code First ایجاد کند.

در حال حاضر این کتابخانه از مفاهیم زیر پشتیبانی می‌کند:

  • تبدیل کلاس به جدول با پشتیبانی از خصوصیت Table
  • تبدیل پراپرتی‌ها به ستون با پشتیبانی از خصوصیت هایی چون Column,Key,MaxLength,Required,Notmapped,DatabaseGenerated,Index
  • پشتیبانی از primarykey و کلید‌های ترکیبی
  • کلید خارجی و روابط یک  به چند و پشتیبانی از cascade on delete
  • فیلد غیر نال


برای شروع ابتدا کتابخانه مورد نظر را از Nuget با دستور زیر دریافت کنید:
Install-Package SQLite.CodeFirst
خود این دستور باعث می‌شود که وابستگی‌هایش از قبیل sqlite provider‌ها نیز دریافت گردند.
solution من شامل سه پروژه است یکی برای مدل‌ها که شامل کلاس‌های زیر برای تهیه یک دفترچه تلفن ساده است:

Person
 public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public virtual ICollection<PhoneBook> Numbers { get; set; }

    }

PhoneBook
  public class PhoneBook
    {
        public int Id { get; set; }

        public string Field{ get; set; }

        public string Number { get; set; }

        public virtual Person Person { get; set; }
    }

پروژه بعدی به نام سرویس که جهت پیاده سازی کلاس‌های EF است و دیگری هم یک پروژه‌ی WPF جهت تست برنامه.
در پروژه‌ی سرویس ما یک کلاس به نام Context داریم که مفاهیم مربوط به پیاده سازی Context در آن انجام شده است:
public class Context:DbContext
    {
        public Context():base("constr")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            var initializer = new InitialDb(modelBuilder);
            Database.SetInitializer(initializer);        
        }

        public DbSet<PhoneBook> PhoneBook { get; set; }
        public DbSet<Person> Persons { get; set; }
    }
تا به الان چیز جدیدی نداشتیم و همه چیز طبق روال صورت گرفته است؛ ولی دو نکته‌ی مهم در این کد نهفته است:

 اول اینکه در سطر اول متد بازنویسی شده onModelCreating، قرارداد مربوط به نامگذاری جداول را حذف می‌کنیم چرا که در صورت نبودن این خط، اسامی که کلاس sqllite برای آن در نظر خواهد گرفت با اسامی که برای انجام عملیات CURD استفاده می‌شوند متفاوت خواهد بود. برای مثال برای Person جدولی به اسم People خواهد ساخت ولی برای درج، آن را در جدول Person انجام می‌دهد که به خاطر نبودن جدول با خطای چنین جدولی موجود نیست روبرو می‌شویم.

نکته‌ی دوم اینکه در همین کلاس Context ما یک پیاده سازی جدید بر روی کلاس InitialDb داشته ایم که در زیر نمونه کد آن را می‌بینید:
 public class InitialDb:SQLite.CodeFirst.SqliteCreateDatabaseIfNotExists<Context>
    {
        public InitialDb(DbModelBuilder modelBuilder) : base(modelBuilder)
        {
        }

        protected override void Seed(Context context)
        {
            var person = new Person()
            {
                FirstName = "ali",
                LastName = "yeganeh",
                Numbers = new List<PhoneBook>()
                {
                    new PhoneBook()
                    {
                        Field = "Work",
                        Number = "031551234"
                    },
                    new PhoneBook()
                    {
                        Field = "Mobile",
                        Number = "09123456789"
                    },
                    new PhoneBook()
                    {
                        Field = "Home",
                        Number = "031554321"
                    }
                }
            };

            context.Persons.Add(person);
            base.Seed(context);
        }
    }
در این کد کلاس InitialDb از کلاس SqliteCreateDatabaseIfNotExists ارث بری کرده‌است و متد seed آن را هم بازنویسی کرده‌ایم. کلاس SqliteCreateDatabaseIfNotExists برای زمانی کاربر دارد که اگر دیتابیس موجود نیست آن را ایجاد کند، در غیر اینصورت خیر. به غیر از آن، کلاس دیگری به نام SqliteDropCreateDatabaseAlways هم وجود دارد که با هر بار اجرا، جداول قبلی را حذف و مجددا آن‌ها را ایجاد میکند.
سپس در پروژه‌ی اصلی WPF در فایل AppConfig رشته اتصالی مورد نظر را وارد نمایید:
  <connectionStrings>
    <add name="constr" connectionString="data source=.\phonebook.sqlite;foreign keys=true" providerName="System.Data.SQLite" />
  </connectionStrings>
نکته‌ی مهم اینکه با افزودن کتابخانه از طریق nuget فایل app.config به روز می‌شود؛ ولی به نظر می‌رسد که تنظیمات به درستی انجام نمی‌شوند. در صورتیکه به مشکل زیر برخوردید و نتوانستید برنامه را اجرا کنید، کد زیر را که قسمتی از فایل app.config است، مطالعه فرمایید و موارد مربوط به آن را اصلاح کنید:

خطا:
The ADO.NET provider with invariant name 'System.Data.SQLite' is either not registered in the machine or application config file, or could not be loaded

قسمتی از فایل app.config:
<entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
          <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />

    </providers>
  </entityFramework>
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SQLite.EF6" />
      <remove invariant="System.Data.SQLite" />
       <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".Net Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
    </DbProviderFactories>
  </system.data>

کد Load پروژه WPF:
 public MainWindow()
        {
            InitializeComponent();
           var context=new Context();

            var list= context.Persons.ToList();

            var s = "";

            foreach (var person in list)
            {
                s += person.FirstName + " " + person.LastName +
            " has these numbers:" + Environment.NewLine;

                foreach (var number in person.Numbers)
                {
                    s += number.Field + " : " + number.Number + Environment.NewLine;
                }

                s += Environment.NewLine;
            }
           MessageBox.Show(s);


        }



دانلود مثال
 
مطالب
روش های ارث بری در Entity Framework - قسمت اول
بخش هایی از کتاب "مرجع کامل Entity Framework 6.0"
ترجمه و تالیف: بهروز راد
وضعیت: در حال نگارش


پیشتر، آقای نصیری در بخشی از مباحث مربوط به Code First در مورد روش‌های مختلف ارث بری در EF و در روش Code First صحبت کرده اند. در این مقاله‌ی دو قسمتی، در مورد دو تا از این روش‌ها در حالت Database First می‌خوانید.

چرا باید از ارث بری استفاده کنیم؟

یکی از اهداف اصلی ORMها این است که با ایجاد یک مدل مفهومی از پایگاه داده، آن را هر چه بیشتر به طرز تفکر ما از مدل شی گرای برنامه مان نزدیکتر کنند. از آنجا که ما توسعه گران از مفاهیم شی گرایی مانند "ارث بری" در کدهای خود استفاده می‌کنیم، نیاز داریم تا این مفهوم را در سطح پایگاه داده نیز داشته باشیم. آیا این کار امکان پذیر است؟ EF چه امکاناتی برای رسیدن به این هدف برای ما فراهم کرده است؟ در این قسمت به این سوال پاسخ خواهیم داد.

ارث­ بری جداول مفهومی است که در EF به راحتی قابل پیاده­ سازی است. سه روش برای پیاده­ سازی این مفهوم در مدل وجود دارد.
  1. Table Per Type یا TPT: خصیصه‌های مشترک در جدول پایه قرار دارند و به ازای هر زیر مجموعه نیز یک جدول جدا ایجاد می‌شود.
  2. Table Per Hierarchy یا TPH: تمامی خصیصه‌ها در یک جدول وجود دارند.
  3. Table Per Concrete Type یا TPC: جدول پایه ای وجود ندارد و به ازای هر موجودیت دقیقاً یک جدول همراه با خصیصه‌های موجودیت در آن ایجاد می‌شود.
 
روش TPT

در این روش، خصیصه‌های مشترک در یک جدول پایه قرار دارند و به ازای هر زیر مجموعه از جدول پایه، یک جدول با خصیصه‌های منحصر به آن نوع ایجاد می‌شود. ابتدا جداول و ارتباطات بین آنها که در توضیح مثال برای این روش با آنها کار می‌کنیم را ببینیم.



فرض کنید قصد داریم تا در هنگام ثبت مشخصات یک دانش آموز، مقطع تحصیلی او نیز حتماً ذخیره شود. در این حالت، فیلدی با نام Degree ایجاد و تیک گزینه‌ی Allow Nulls را از روبروی آن بر میداریم. با این حال اگر مشخصات دانش آموزان را در جدولی عمومی مثلاً با نام People ذخیره کنیم و این جدول را مکانی برای ذخیره‌ی مشخصات افراد دیگری مانند مدیران و معلمان نیز در نظر بگیریم، از آنجا که قصد ثبت مقطع تحصیلی برای مدیران و معلمان را نداریم، وجود فیلد Degree در کار ما اختلال ایجاد می‌کند. اما با ذخیره‌ی اطلاعات مدیران و معلمان در جداول مختص به خود، می‌توان قانون غیر قابل Null بودن فیلد Degree برای دانش آموزان را به راحتی پیاده سازی کرد.
همان طور که در شکل قبل نیز مشخص است، ما یک جدول پایه با نام Persons ایجاد کرده ایم و خصیصه‌های مشترک بین زیر مجموعه‌ها (FirstName و LastName) را در آن قرار داده ایم. سه موجودیت (Student، Admin و Instructor) از Persons ارث می‌برند و موجودیت BusinessStudent نیز از Student ارث می‌بَرَد.
جداول ایجاد شده، پس از ایجاد مدل به روش Database First، به شکل زیر تبدیل می‌شوند.


از آنجا که قصد داریم ارتباطات ارث بری شده ایجاد کنیم، باید ارتباطات پیش فرض شکل گرفته بین موجودیت‌ها را حذف کنیم. بدین منظور، بر روی هر خط ارتباطی در EDM Designer کلیک راست و گزینه‌ی Delete from Model را انتخاب کنید. سپس بر روی موجودیت Person، کلیک راست کرده و از منوی Add New، گزینه‌ی Inheritance را انتخاب کنید (شکل زیر).


شکل زیر ظاهر می‌شود.


قسمت بالا، موجودیت پایه، و قسمت پایین، موجودیت مشتق شده را مشخص می‌کند. این کار را سه مرتبه برای ایجاد ارتباط ارث بری شده بین موجودیت Person به عنوان موجودیت پایه و موجودیت‌های Student، Instructor و Admin به عنوان موجودیت‌های مشتق شده ایجاد کنید. همچنین یک ارتباط نیز بین موجودیت Student به عنوان موجودیت پایه و موجودیت BusinessStudent به عنوان موجودیت مشتق شده ایجاد کنید. نتیجه‌ی کار را در شکل زیر ملاحظه می‌کنید.

اگر بر روی دکمه‌ی Save در نوار ابزار Visual Studio کلیک کنید، چهار خطا در پنجره‌ی Error List نمایش داده می‌شود


این خطاها بیانگر این هستند که خصیصه‌ی PersonId به دلیل اینکه در موجودیت پایه‌ی Person تعریف شده است، نباید در موجودیت‌های مشتق شده از آن نیز وجود داشته باشد چون موجودیت‌های مشتق شده، خصیصه‌ی PersonId را به ارث برده اند. وجود این خصیصه در زمان طراحی جدول در مدل فیزیکی الزامی بوده است اما اکنون ما با مدل مفهومی و قوانین شی گرایی سر و کار داریم. بنابراین خصیصه‌ی PersonId را از موجودیت‌های Student، Instructor، Admin و BusinessStudent حذف کنید. شکل زیر، نتیجه‌ی کار را نشان می‌دهد.


اکنون اگر بر روی دکمه‌ی Save کلیک کنید، خطاها از بین می‌روند.
ما خصیصه‌ی PersonId را از موجودیت‌های مشتق شده به این دلیل که آن را از موجودیت پایه ارث می‌برند حذف کردیم. حال این خصیصه برای موجودیت‌های مشتق شده وجود دارد اما باید مشخص کنیم که به کدام خصیصه از کلاس پایه تناظر دارد. شاید انتظار این باشد که EF، خود تشخیص بدهد که PersonId در موجودیت‌های مشتق شده باید به PersonId کلاس پایه‌ی خود تناظر داشته باشد اما در حال حاضر این کاری است که خود باید انجام دهیم. بدین منظور، بر روی هر یک از موجودیت‌های مشتق شده کلیک راست کرده و گزینه‌ی Table Mapping را انتخاب کنید. سپس همان طور که در شکل زیر مشاهده می‌کنید، تناظر را ایجاد کنید.


مدل ما آماده است. آن را امتحان می‌کنیم. در زیر، یک کوئری LINQ ساده بر روی مدل ایجاد شده را ملاحظه می‌کنید.
using (PersonDbEntities context = new PersonDbEntities())
{

    var people = from p in context.Persons
                 select p;

    foreach (Person person in people)
    {
        Console.WriteLine("{0}, {1}",
            person.LastName,
            person.FirstName);
    }

    Console.ReadLine();
}

قضیه به همین جا ختم نمی‌شود! ما الان یک مدل ارث بری شده داریم. بهتر است مزایای آن را در عمل ببینیم. شاید دوست داشته باشیم تا فقط اطلاعات زیر مجموعه‌ی BusinessStudent را بازیابی کنیم.
using (PersonDbEntities context = new PersonDbEntities())
{

    var students = from p in context.Persons.OfType<BusinessStudent>()
                 select p;

    foreach (BusinessStudent student in students)
    {
        Console.WriteLine("{0}, {1}: Degree {2}, Discipline {3}",
            student.LastName,
            student.FirstName,
            student.Degree,
            student.Discipline);
    }

    Console.ReadLine();
}

همان طور که در کدهای قبل نیز مشخص است، خصیصه‌های LastName و FirstName از موجودیت پایه یعنی Person، خصیصه‌ی Degree از موجودیت مشتق شده‌ی Student (که البته در نقش موجودیت پایه برای BusinessStudent است) و Discipline از موجودیت مشتق شده یعنی BusinessStudent خوانده می‌شوند.
یک روش دیگر نیز برای کار با این سلسه مراتب ارث بری وجود دارد. کوئری اول را دست نزنیم (اطلاعات موجودیت پایه را بازیابی کنیم) و پیش از انجام عملیاتی خاص، نوع موجودیت مشتق شده را بررسی کنیم. مثالی در این زمینه:
using (PersonDbEntities context = new PersonDbEntities())
{

    var people = from p in context.Persons
                 select p;

    foreach (Person person in people)
    {
        Console.WriteLine("{0}, {1}",
            person.LastName,
            person.FirstName);

        if (person is Student)
            Console.WriteLine("    Degree: {0}",
                ((Student)person).Degree);
        
        if (person is BusinessStudent)
            Console.WriteLine("    Discipline: {0}",
                ((BusinessStudent)person).Discipline);
    }

    Console.ReadLine();
}

مزایای روش TPT
  • امکان نرمال سازی سطح 3 در این روش به خوبی وجود دارد
  • افزونگی در جداول وجود ندارد.
  • اصلاح مدل آسان است (برای اضافه یا حذف کردن یک موجودیت به/از مدل فقط کافی است تا جدول متناظر با آن را از پایگاه داده حذف کنید)
معایب روش TPT
  • سرعت عملیات CRUD (ایجاد، بازیابی، آپدیت، حذف) داده‌ها با افزایش تعداد موجودیت‌های شرکت کننده در سلسله مراتب ارث بری کاهش می‌یابد. به عنوان مثال، کوئری‌های SELECT، حاوی عبارت‌های JOIN خواهند بود و عدم توجه صحیح به کوئری نوشته شده می‌تواند منجر به حضور چندین عبارت JOIN که برای ارتباط بین جداول به کار می‌رود در اسکریپت تولیدی و کاهش زمان اجرای بازیابی داده‌ها شود.
  • تعداد جداول در پایگاه داده زیاد می‌شود

در قسمت بعد با روش TPH آشنا می‌شوید.
مطالب
روش‌های مختلف انجام چند کار به صورت همزمان در C# .NET - قسمت اول
آیا تا به حال لیستی از دیتا داشته‌اید که بخواهید بر روی آنها کاری را انجام دهید؟ مثلا لیستی از مشتریان که باید برای تک تک آنها 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)));
}
اگر چه می‌توانیم خودمان آیتم‌ها را دسته بندی کنیم و مثلا هر 25 تا 25 آنها را با هم پردازش کنیم، ولی این دسته بندی خیلی معقول نیست، چون RX اینجاست!
public async Task Sample()
{
    var customers = await GetCustomersFromSomeWhere();

    await customers.Select(c => Observable.FromAsync(() => DoSomethingWithCustomer(c))).Merge(maxConcurrent: 25);
}
به خاطر وجود maxConcurrent: 25 در دستور فوق، مشتری‌ها بسته به وضعیت کلی سرور، حداکثر 25 تا 25 تا پردازش می‌شوند، ولی ممکن است مثلا 10 تا 10 پردازش شوند. البته انتظار هوشمندی خیلی زیادی از آن نداشته باشید.

استفاده از 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 مناسب می‌کند.

سایر گزینه هایی که داریم شامل Parallel Linq و Parallel.ForEach است که عموما برای کارهای 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 و ... موضوع بحث قسمت بعدی این مطلب است.
مطالب
Blazor 5x - قسمت 19 - کار با فرم‌ها - بخش 7 - نکات ویژه‌ی کار با EF-Core در برنامه‌های Blazor Server
تا قسمت قبل، روشی را که برای کار با EF-Core درنظر گرفتیم، روش متداول کار با آن، در برنامه‌های ASP.NET Core Web API بود؛ یعنی این روش با برنامه‌های مبتنی بر Blazor WASM که از دو قسمت مجزای Web API سمت سرور و Web Assembly سمت کلاینت تشکیل شده‌اند، به خوبی جواب می‌دهد؛ اما ... با Blazor Server یکپارچه که تمام قسمت‌های مدیریتی آن سمت سرور رخ می‌دهند، خیر! در این مطلب، دلایل این موضوع را به همراه ارائه راه‌حلی، بررسی خواهیم کرد.


طول عمر سرویس‌ها، در برنامه‌های Blazor Server متفاوت هستند

هنگامیکه با یک ASP.NET Core Web API متداول کار می‌کنیم، درخواست‌های HTTP رسیده، از میان‌افزارهای موجود رد شده و پردازش می‌شوند. اما هنگامیکه با Blazor Server کار می‌کنیم، به علت وجود یک اتصال دائم SignalR که عموما از نوع Web socket است، دیگر درخواست HTTP وجود ندارد. تمام رفت و برگشت‌های برنامه به سرور و پاسخ‌های دریافتی، از طریق Web socket منتقل می‌شوند و نه درخواست‌ها و پاسخ‌های متداول HTTP.
این روش پردازشی، اولین تاثیری را که بر روی رفتار یک برنامه می‌گذارد، تغییر طول عمر سرویس‌های آن است. برای مثال در برنامه‌های Web API، طول عمر درخواست‌ها، از نوع Scoped هستند و با شروع پردازش یک درخواست، سرویس‌های مورد نیاز وهله سازی شده و در پایان درخواست، رها می‌شوند.
این مساله در حین کار با EF-Core نیز بسیار مهم است؛ از این جهت که در برنامه‌های Web API نیز EF-Core و DbContext آن، به صورت سرویس‌هایی با طول عمر Scoped تعریف می‌شوند. برای مثال زمانیکه یک چنین تعریفی را در برنامه داریم:
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
امضای واقعی متد AddDbContext مورد استفاده‌ی فوق، به صورت زیر است:
public static IServiceCollection AddDbContext<TContext>(
    [NotNullAttribute] this IServiceCollection serviceCollection, 
    [CanBeNullAttribute] Action<DbContextOptionsBuilder> optionsAction = null, 
    ServiceLifetime contextLifetime = ServiceLifetime.Scoped, 
    ServiceLifetime optionsLifetime = ServiceLifetime.Scoped) where TContext : DbContext;
همانطور که مشاهده می‌کنید، طول عمرهای پیش‌فرض تعریف شده‌ی در اینجا، از نوع Scoped هستند. یعنی زمانیکه سرویس‌های ApplicationDbContext را از طریق سیستم تزریق وابستگی‌های برنامه دریافت می‌کنیم، در ابتدای یک درخواست Web API، به صورت خودکار وهله سازی شده و در پایان درخواست رها می‌شوند. به این ترتیب به ازای هر درخواست رسیده، وهله‌ی متفاوتی از DbContex را دریافت می‌کنیم که با وهله‌ی استفاده شده‌ی در درخواست قبلی، یکی نیست.
اما زمانیکه مانند یک برنامه‌ی مبتنی بر Blazor Server، دیگر HTTP Requests متداولی را نداریم، چطور؟ در این حالت زمانیکه یک اتصال SignalR برقرار شد، وهله‌ای از DbContext که در اختیار برنامه‌ی Blazor Server قرار می‌گیرد، تا زمانیکه کاربر این اتصال را به نحوی قطع نکرده (مانند بستن کامل مرورگر و یا ریفرش صفحه)، ثابت باقی خواهد ماند. یعنی به ازای هر اتصال SignalR، طول عمر ServiceLifetime.Scoped پیش‌فرض تعریف شده، همانند یک وهله‌ی با طول عمر Singleton عمل می‌کند. در این حالت تمام صفحات و کامپوننت‌های یک برنامه‌ی Blazor Server، از یک تک وهله‌ی مشخص DbContext که در ابتدای کار دریافت کرده‌اند، کار می‌کنند و از آنجائیکه DbContext به صورت thread-safe کار نمی‌کند، این تک وهله مشکلات زیادی را ایجاد خواهد کرد که یک نمونه از آن‌را در عمل، در پایان قسمت قبل مشاهده کردید:
«اگر برنامه را اجرا کرده و سعی در حذف یک ردیف کنیم، به خطای زیر می‌رسیم و یا حتی اگر کاربر شروع کند به کلیک کردن سریع در قسمت‌های مختلف برنامه، باز هم این خطا مشاهده می‌شود:
 An exception occurred while iterating over the results of a query for context type 'BlazorServer.DataAccess.ApplicationDbContext'.
System.InvalidOperationException: A second operation was started on this context before a previous operation completed.
This is usually caused by different threads concurrently using the same instance of DbContext.
For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
عنوان می‌کند که متد OnConfirmDeleteRoomClicked، بر روی ترد دیگری نسبت به ترد اولیه‌ای که DbContext بر روی آن ایجاد شده، در حال اجرا است و چون DbContext برای یک چنین سناریوهایی، thread-safe نیست، اجازه‌ی استفاده‌ی از آن‌را نمی‌دهد.»
هر درخواست Web API نیز بر روی یک ترد جداگانه اجرا می‌شود؛ اما چون ابتدا و انتهای درخواست‌ها مشخص است، طول عمر Scoped، در ابتدای درخواست شروع شده و در پایان آن رها سازی می‌شود. به همین جهت استثنائی را که در اینجا مشاهده می‌کنید، در برنامه‌های Web API شاید هیچگاه مشاهده نشود.


معرفی DbContextFactory در EF Core 5x

همواره باید طول عمر DbContext را تا جای ممکن، کوتاه نگه داشت. مشکل فعلی ما، Singleton رفتار کردن DbContext‌ها (داشتن طول عمر طولانی) در برنامه‌های Blazor Server هستند. یک چنین رفتاری را شاید در برنامه‌های دسکتاپ هم پیشتر مشاهده کرده باشید. برای مثال در برنامه‌های دسکتاپ WPF، تا زمانیکه یک فرم باز است، Context ایجاد شده‌ی در آن هم برقرار است و Dispose نمی‌شود. در یک چنین حالت‌هایی، عموما Context را در زمان نیاز، ایجاد کرده و پس از پایان آن کار کوتاه، Context را رها می‌کنند. به همین جهت نیاز به DbContext Factory ای وجود دارد که بتواند یک چنین پیاده سازی‌هایی را میسر کند و خوشبختانه از زمان EF Core 5x، یک چنین امکانی خصوصا برای برنامه‌های Blazor Server تحت عنوان DbContextFactory ارائه شده‌است که به عنوان راه حل استاندارد دسترسی به DbContext در اینگونه برنامه‌ها مورد استفاده قرار می‌گیرد.
برای کار با DbContextFactory، اینبار در فایل BlazorServer.App\Startup.cs، بجای استفاده از services.AddDbContext، از متد AddDbContextFactory استفاده می‌شود:
public void ConfigureServices(IServiceCollection services)
{
    var connectionString = Configuration.GetConnectionString("DefaultConnection");
    //services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
    services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
سپس باید دقت داشت که روش استفاده‌ی از آن، نسبت به کار مستقیم با ApplicationDbContext، کاملا متفاوت است. هدف از DbContextFactory، ساخت دستی Context در زمان نیاز و سپس Dispose صریح آن است. بنابراین طول عمر Context دریافت شده‌ی توسط آن باید توسط برنامه نویس مدیریت شود و به صورت خودکار توسط IoC Container برنامه مدیریت نخواهد شد. در این حالت دو روش استفاده‌ی از آن در کامپوننت‌های برنامه‌های Blazor Server، پیشنهاد می‌شود.


روش اول کار با DbContextFactory در کامپوننت‌های Blazor Server : وهله سازی از نو، به ازای هر متد

در این روش پس از ثبت AddDbContextFactory در فایل Startup برنامه مانند مثال فوق، ابتدا سرویس IDbContextFactory که به ApplicationDbContext اشاره می‌کند به ابتدای کامپوننت تزریق می‌شود:
@inject IDbContextFactory<ApplicationDbContext> DbFactory
سپس در هر جائی که نیاز به وهله‌ای از ApplicationDbContext است، آن‌را به صورت دستی وهله سازی کرده و همانجا هم Dispose می‌کنیم:
private async Task DeleteImageAsync()
{
    using var context = DbFactory.CreateDbContext();

    var image = await context.HotelRoomImages.FindAsync(1);

   // ...
}
در اینجا یکی متدهای یک کامپوننت فرضی را مشاهده می‌کند که از DbFactory تزریق شده استفاده کرد و سپس با استفاده از متد ()CreateDbContext، وهله‌ی جدیدی از ApplicationDbContext را ایجاد می‌کند. همچنین در همان سطر، وجود عبارت using نیز مشاهده می‌شود. یعنی در پایان کار این متد، context ایجاد شده حتما Dispose شده و طول عمر کوتاهی خواهد داشت.


روش دوم کار با DbContextFactory در کامپوننت‌های Blazor Server : یکبار وهله سازی Context به ازای هر کامپوننت

در این روش می‌توان طول عمر Context را معادل طول عمر کامپوننت تعریف کرد که مزیت استفاده‌ی از Change tracking موجود در EF-Core را به همراه خواهد داشت. در این حالت کامپوننت‌های Blazor Server، شبیه به فرم‌های برنامه‌های دسکتاپ عمل می‌کنند:
@implements IDisposable
@inject IDbContextFactory<ApplicationDbContext> DbFactory


@code
{
   private ApplicationDbContext Context;

   protected override async Task OnInitializedAsync()
   {
       Context = DbFactory.CreateDbContext();
       await base.OnInitializedAsync();
   }

   private async Task DeleteImageAsync()
   {
       var image = await Context.HotelRoomImages.FindAsync(1);
       // ...
   }

   public void Dispose()
   {
     Context.Dispose();
   }
}
- در اینجا همانند روش اول، کار با تزریق IDbContextFactory شروع می‌شود
-  اما بجای اینکه به ازای هر متد، کار فراخوانی DbFactory.CreateDbContext صورت گیرد، یکبار در آغاز کار کامپوننت و در روال رویدادگردان OnInitializedAsync، کار وهله سازی Context کامپوننت انجام شده و از این تک Context در تمام متدهای کامپوننت استفاده خواهد شد.
- در این حالت کار Dispose خودکار این Context به متد Dispose نهایی کل کامپوننت واگذار شده‌است. برای اینکه این متد فراخوانی شود، نیاز است در ابتدای تعاریف کامپوننت، از دایرکتیو implements IDisposable@ استفاده کرد.


سؤال: اگر سرویسی از ApplicationDbContext تزریق شده‌ی در سازنده‌ی خود استفاده می‌کند، چکار باید کرد؟

برای نمونه سرویس‌های از پیش تعریف شده‌ی ASP.NET Core Identity، در سازنده‌ی خود از ApplicationDbContext استفاده می‌کنند و نه از IDbContextFactory. در این حالت برای تامین ApplicationDbContext‌های تزریق شده، فقط کافی است از روش زیر استفاده کنیم:
services.AddScoped<ApplicationDbContext>(serviceProvider =>
     serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
در این حالت به ازای هر Scope تعریف شده‌ی در برنامه، جهت دسترسی به ApplicationDbContext از طریق سیستم تزریق وابستگی‌ها، کار فراخوانی DbFactory.CreateDbContext به صورت خودکار انجام خواهد شد.


سؤال: روش پیاده سازی سرویس‌های یک برنامه Blazor Server به چه صورتی باید تغییر کند؟

تا اینجا روش‌هایی که برای استفاده از IDbContextFactory معرفی شدند (که روش‌های رسمی و توصیه شده‌ی اینکار نیز هستند)، فرض را بر این گذاشته‌اند که ما قرار است تمام منطق تجاری کار با بانک اطلاعاتی را داخل همان متدهای کامپوننت‌ها انجام دهیم (این روش برنامه نویسی، بسیار مورد علاقه‌ی مایکروسافت است و در تمام مثال‌های رسمی آن به صورت ضمنی توصیه می‌شود!). اما اگر همانند مثالی که تاکنون در این سری بررسی کردیم، نخواهیم اینکار را انجام دهیم و علاقمند باشیم تا این منطق تجاری را به سرویس‌های مجزایی، با مسئولیت‌های مشخصی انتقال دهیم، روش استفاده‌ی از IDbContextFactory چگونه خواهد بود؟
در این حالت از ترکیب روش دوم مطرح شده‌ی استفاده از IDbContextFactory که به همراه مزیت دسترسی کامل به Change Tracking توکار EF-Core و پیاده سازی الگوی واحد کار است و وهله سازی خودکار ApplicationDbContext که معرفی شد، استفاده خواهیم کرد؛ به این صورت:
الف) تمام سرویس‌های EF-Core یک برنامه‌ی Blazor Server باید اینترفیس IDisposable را پیاده سازی کنند.
این مورد برای سرویس‌های پروژه‌های Web API، ضروری نیست؛ چون طول عمر Context آن‌ها توسط خود IoC Container مدیریت می‌شود؛ اما در برنامه‌های Blazor Server، مطابق توضیحاتی که ارائه شد، خودمان باید این طول عمر را مدیریت کنیم.
بنابراین به پروژه‌ی سرویس‌های برنامه مراجعه کرده و هر سرویسی که ApplicationDbContext تزریق شده‌ای را در سازنده‌ی خود می‌پذیرد، یافته و تعریف اینترفیس آن‌را به صورت زیر تغییر می‌دهیم:
public interface IHotelRoomService : IDisposable
{
   // ...
}

public interface IHotelRoomImageService : IDisposable
{
   // ...
}
سپس باید اینترفیس‌های IDisposable را پیاده سازی کرد که روش مورد پذیرش code analyzer‌ها در این زمینه، رعایت الگوی زیر، دقیقا به همین شکل است و باید از دو متد تشکیل شود:
    public class HotelRoomService : IHotelRoomService
    {
        private bool _isDisposed;

        // ...

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                try
                {
                    if (disposing)
                    {
                        _dbContext.Dispose();
                    }
                }
                finally
                {
                    _isDisposed = true;
                }
            }
        }
    }
این الگو را به همین شکل برای سرویس HotelRoomImageService نیز پیاده سازی می‌کنیم.


ب) Dispose دستی تمام سرویس‌ها، در کامپوننت‌های مرتبط
در ادامه تمام کامپوننت‌هایی را که از سرویس‌های فوق استفاده می‌کنند یافته و ابتدا دایرکتیو implements IDisposable@ را به ابتدای آن‌ها اضافه می‌کنیم. سپس متد Dispose آن‌ها را جهت فراخوانی متد Dispose سرویس‌های فوق، تکمیل خواهیم کرد:
بنابراین ابتدا به فایل BlazorServer\BlazorServer.App\Pages\HotelRoom\HotelRoomUpsert.razor مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
@page "/hotel-room/create"
@page "/hotel-room/edit/{Id:int}"

@implements IDisposable
// ...


@code
{
    // ...

    public void Dispose()
    {
        HotelRoomImageService.Dispose();
        HotelRoomService.Dispose();
    }
}
و همچنین به کامپوننت BlazorServer\BlazorServer.App\Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و آن‌را به صورت زیر جهت Dispose دستی سرویس‌ها، تکمیل می‌کنیم:
@page "/hotel-room"

@implements IDisposable
// ...


@code
{
    // ...

    public void Dispose()
    {
        HotelRoomService.Dispose();
    }
}


مشکل! اینبار خطای dispose شدن context را دریافت می‌کنیم!

System.ObjectDisposedException: Cannot access a disposed context instance.
A common cause of this error is disposing a context instance that was resolved from dependency injection and then
later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose'
on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context instances.
Object name: 'ApplicationDbContext'.
هم برنامه‌های Blazor WASM و هم برنامه‌های Blazor Server از مفهوم طول عمرهای تنظیم شده‌ی سرویس‌ها پشتیبانی نمی‌کنند! در هر دوی این‌ها اگر سرویسی را با طول عمر Scoped تنظیم کردیم، رفتار آن همانند سرویس‌های Singleton خواهد بود. تنها زمانی رفتارهای Scoped و یا Transient پشتیبانی می‌شوند که درخواست HTTP ای رخ داده باشد که این مورد خارج است از طول عمر یک برنامه‌ی Blazor WASM و همچنین اتصال SignalR برنامه‌های Blazor Server. فقط قسمت‌هایی از برنامه‌ی Blazor Server که با مدل قبلی Razor pages طراحی شده‌اند، چون سبب شروع یک درخواست HTTP معمولی می‌شوند، همانند برنامه‌های متداول ASP.NET Core رفتار می‌کنند و در این حالت طول عمرهای غیر Singleton مفهوم پیدا می‌کنند.

مشکلی که در اینجا رخ داده این است که سرویس‌هایی را داریم با طول عمر به ظاهر Scoped که یکی از وابستگی‌های آن‌ها را به صورت دستی Dispose کرده‌ایم. چون طول عمر Scoped در اینجا وجود ندارد و طول عمرها در اصل Singleton هستند، هربار که سرویس مدنظر مجددا درخواست شود، همان وهله‌ی ابتدایی که اکنون یکی از وابستگی‌های آن Dispose شده، در اختیار برنامه قرار می‌گیرد.
پس از این تغییرات، اولین باری که برنامه را اجرا می‌کنیم، لیست اتاق‌ها به خوبی نمایش داده می‌شوند و مشکلی نیست. بعد در همین حال و در همین صفحه، اگر بر روی دکمه‌ی افزودن یک اتاق جدید کلیک کنیم، اتفاقی که رخ می‌دهد، فراخوانی متد Dispose کامپوننت لیست اتاق‌ها است (بر روی آن یک break-point قرار دهید). بنابراین متد Dispose یک کامپوننت، با هدایت به یک مسیر دیگر، به صورت خودکار فراخوانی می‌شود. در این حالت Context برنامه Dispose شده و در کامپوننت ثبت یک اتاق جدید دیگر، در دسترس نخواهد بود؛ چون IHotelRoomService مورد استفاده مجددا وهله سازی نمی‌شود و از همان وهله‌ای که بار اول ایجاد شده، استفاده خواهد شد.
 
بنابراین سؤال اینجا است که چگونه می‌توان سیستم تزریق وابستگی‌ها را وادار کرد تا تمام سرویس‌های تزریق شده‌ی به سازنده‌ها‌ی سرویس‌های HotelRoomService و  HotelRoomImageService را مجددا وهله سازی کند و سعی نکند از همان وهله‌های قبلی استفاده کند؟

پاسخ: یک روش این است که IHotelRoomImageService را خودمان به ازای هر کامپوننت به صورت دستی در روال رویدادگردان OnInitializedAsync وهله سازی کرده و DbFactory.CreateDbContext جدیدی را مستقیما به سازنده‌ی آن ارسال کنیم. در این حالت مطمئن خواهیم شد که این وهله، جای دیگری به اشتراک گذاشته نمی‌شود:
@code
{
   private IHotelRoomImageService HotelRoomImageService;

   protected override async Task OnInitializedAsync()
   {
       HotelRoomImageService =  new HotelRoomImageService(DbFactory.CreateDbContext(), mapper);
       await base.OnInitializedAsync();
   }

   private async Task DeleteImageAsync()
   {
       await HotelRoomImageService.DeleteAsync(1);
       // ...
   }

   public void Dispose()
   {
     HotelRoomImageService.Dispose();
   }
}
هرچند این روش کار می‌کند، اما در زمان استفاده از IoC Container‌ها قرار نیست کار انجام new‌ها را خودمان به صورت دستی انجام دهیم و بهتر است مدیریت این مساله به آن‌ها واگذار شود.


وادار کردن Blazor Server به وهله سازی مجدد سرویس‌های کامپوننت‌ها

بنابراین مشکل ما Singleton رفتار کردن سرویس‌ها، در برنامه‌های Blazor است. برای مثال در برنامه‌های Blazor Server، تا زمانیکه اتصال SignalR برنامه برقرار است (مرورگر بسته نشده، برگه‌ی جاری بسته نشده و یا کاربر صفحه را ریفرش نکرده)، هیچ سرویسی دوباره وهله سازی نمی‌شود.
برای رفع این مشکل، امکان Scoped رفتار کردن سرویس‌های یک کامپوننت نیز در نظر گرفته شده‌اند. برای نمونه کدهای کامپوننت HotelRoomList.razor را به صورت زیر تغییر می‌دهیم:
@page "/hotel-room"

@*@implements IDisposable*@
@*@inject IHotelRoomService HotelRoomService*@
@inherits OwningComponentBase<IHotelRoomService>
با استفاده از دایرکتیو جدید inherits OwningComponentBase@ می‌توان میدان دید یک سرویس را به طول عمر کامپوننت جاری محدود کرد. هربار که این کامپوننت نمایش داده می‌شود، وهله سازی شده و هربار که به کامپوننت دیگری هدایت می‌شویم، به صورت خودکار سرویس مورد استفاده را Dispose می‌کند. بنابراین در اینجا دیگر نیازی به ذکر دایرکتیو implements IDisposable@ نیست.

چند نکته:
- فقط یکبار به ازای هر کامپوننت می‌توان از دایرکتیو inherits استفاده کرد.
- زمانیکه طول عمر سرویسی را توسط OwningComponentBase مدیریت می‌کنیم، در حقیقت یک کلاس پایه را برای آن کامپوننت درنظر گرفته‌ایم که به همراه یک خاصیت عمومی ویژه، به نام Service و از نوع سرویس مدنظر ما است. در این حالت یا می‌توان از خاصیت Service به صورت مستقیم استفاده کرد و یا می‌توان به صورت زیر، همان کدهای قبلی را داشت و هربار که نیازی به HotelRoomService بود، آن‌را به خاصیت عمومی Service هدایت کرد:
@code
{
   private IHotelRoomService HotelRoomService => Service;
- اگر نیاز به بیش از یک سرویس وجود داشت، چون نمی‌توان بیش از یک inherits را تعریف کرد، می‌توان از نمونه‌ی غیرجنریک OwningComponentBase استفاده کرد:
@page "/preferences"
@using Microsoft.Extensions.DependencyInjection
@inherits OwningComponentBase


@code {
    private IHotelRoomService HotelRoomService { get; set; }
    private IHotelRoomImageService HotelRoomImageService { get; set; }

    protected override void OnInitialized()
    {
        HotelRoomService = ScopedServices.GetRequiredService<IHotelRoomService>();
        HotelRoomImageService = ScopedServices.GetRequiredService<IHotelRoomImageService>();
    }
}
در این حالت کلاس پایه‌ی OwningComponentBase، به همراه خاصیت جدید ScopedServices است که با فراخوانی متد GetRequiredService در روال رویدادگردان OnInitialized بر روی آن، سبب وهله سازی Scoped سرویس مدنظر خواهد شد. نمونه‌ی جنریک آن، تمام این موارد را در پشت صحنه انجام می‌دهد و کار کردن با آن ساده‌تر و خلاصه‌تر است.


خلاصه‌ی بحث جاری در مورد روش مدیریت DbContext برنامه‌های Blazor Server:

- بجای services.AddDbContext متداول، باید از AddDbContextFactory استفاده کرد:
services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
services.AddScoped<ApplicationDbContext>(serviceProvider =>
        serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
- تمام سرویس‌هایی که از ApplicationDbContext استفاده می‌کنند، باید به همراه پیاده سازی Dispose آن نیز باشند؛ چون Scope یک سرویس، معادل طول عمر اتصال SignalR برنامه است و مدام وهله سازی نمی‌شود. در این حالت باید وهله سازی و Dispose آن‌را دستی مدیریت کرد.
- کامپوننت‌های برنامه، سرویس‌هایی را که باید Scoped عمل کنند، دیگر نباید از طریق تزریق مستقیم آن‌ها دریافت کنند؛ چون در این حالت همواره به همان وهله‌ای که در ابتدای کار ایجاد شده، می‌رسیم:
@inject IHotelRoomService HotelRoomService
این دریافت باید با استفاده از کلاس پایه OwningComponentBase صورت گیرد:
@inherits OwningComponentBase<IHotelRoomService>
تا عملیات فراخوانی خودکار ScopedServices.GetRequiredService (دریافت وهله‌ی جدید Scoped) و همچنین Dispose خودکار آن‌ها را به ازای هر کامپوننت مجزا، مدیریت کند.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-19.zip
نظرات مطالب
Build Events
با سلام 
در پاسخ به مشکل شما چند نکته باید اشاره بشه. 

نکته اول: ماکروی TargetFileName فقط اسم فایل خروجی پروژه رو برمی‌گردونه، درصورتیکه برای کارکردن دستور فوق مسیر کامل فایل نیازه. چون برنامه editbin.exe درون مسیر خروجی پروژه شما اجرا نمی‌شه. شما می‌تونین از ماکروی TargetPath استفاده کنید که مسیر کامل فایل خروجی پروژه رو برمی‌گردونه.

نکته دوم: کد خطای 9009 مربوط به پیدا نکردن فایل هست. البته فایلی که در اینجا پیدا نشده خروجی پروژه شما نیست بلکه خود ابزار editbin هستش. مسیر درستش در سیستم 32 بیتی برای ویژوال استودیو 2010 اینه:
C:\Program Files\Microsoft Visual Studio 10.0\VC\bin\editbin.exe
اما چون این مسیرها معمولا حاوی فاصله هستند نیاز به استفاده از دابل کوتیشن در ابتدا و انتها وجود داره. بنابراین دستور کامل باید به صورت زیر باشه:
"C:\Program Files\Microsoft Visual Studio 10.0\VC\bin\editbin.exe" /STACK:1000000 "$(TargetPath)"
اما با اجرای دستور فوق باز هم خطایی صادر میشه که کمی خطرناکتر از قبلیه. و اما دلیلش:

نکته سوم: متن زیر از msdn گرفته شده:
You can start this tool only from the Visual Studio command prompt. You cannot start it from a system command prompt or from Windows Explorer.
البته منظور دقیق‌تر این جمله اینه که ابزار editbin نیاز به یکسری تنظیمات و متغیرهای ازپیش تعیین شده داره که در Visual Studio command prompt انجام شده. اما نگران نباشید برای تنظیم این تنظیمات و تبدیل خط فرمان Build Events در ویژوال استودیو به یک Visual Studio command prompt کافیه که خط زیر رو در ابتدای مجموعه دستورات build events خودتون قرار بدین:
call "$(DevEnvDir)..\Tools\vsvars32.bat"
این بچ فایل حاوی دستوراتی نسبتا مفصل برای تنظیم تنظیمات موردنیاز است. درواقع با اجرای این بچ فایل هر خط فرمانی تقریبا تبدیل به Visual Studio command prompt خواهد شد. با توجه به ماکروی (DevEnvDir)$ مسیر کامل این فایل در سیستم 32 بیتی و برای ویژوال استودیوی 2010 به صورت زیر است:
C:\Program Files\Microsoft Visual Studio 10.0\Common7\Tools\vsvars32.bat
بنابراین برای کار کردن دستور موردنظر شما کافیه که این دو دستور به صورت زیر در Post Build Event اضافه بشه:
call "$(DevEnvDir)..\Tools\vsvars32.bat"
"C:\Program Files\Microsoft Visual Studio 10.0\VC\bin\editbin.exe" /STACK:1000000 "$(TargetPath)"

نکته چهارم: با توجه به اشاره‌ای که در نکته قبلی شد ("با اجرای این فایل هر خط فرمانی تقریبا تبدیل به Visual Studio command prompt خواهد شد.") بنابراین دستور فوق را میتوان به صورت زیر خلاصه کرد:
call "$(DevEnvDir)..\Tools\vsvars32.bat"
"editbin.exe" /STACK:1000000 "$(TargetPath)"

موفق باشید.
نظرات مطالب
Functional Programming یا برنامه نویسی تابعی - قسمت اول
خیلی ممنونم که این بحث رو مطرح کردید. ولی چیزی که واقعا متوجه نمیشم اینه که چرا باید برنامه نویسی تابعی رو رقیب یا جایگزین برنامه نویسی شی گرا دونست. به نظر من مفاهیمی که این دو روش ارائه میکنن مکمل هم هستن. به عبارتی مطالب مطرح شده میتونه به عنوان رهنمون هایی در برنامه نویسی شی گرا تلقی بشه.
البته در منابع خارجی که مطالعه میکردم تفاوت اصلی رو در این دونسته بودن که در برنامه نویسی تابعی داده‌ها در شی نگهداری نمیشه و باید در تابع نگه داری و جا بجا بشه! البته نمیدونمن چطور این کار به صورت مطلق میسر هست!
در کل من فکر میکنم اگه قرار باشه این کار به صورت مطلق در تمام حالات انجام بشه خودش به ضد مفاهیمی که در این رهنمون‌ها تاکید شده تبدیل خواهد شد.
فکر نمیکنم بدون استفاده از مفاهیم شی گرایی بشه یه پروژه واقعی و بزرگ رو با استفاده از صرفا مفاهیم برنامه نویسی تابعی به صورت بهتری پیاده کرد!
خیلی ممنون میشم اگه اساتید این بحث رو ادامه بدن و توضیحاتی بدن که آیا من دچار بدفهمی شدم یا خیر.
نظرات مطالب
Blazor 5x - قسمت 34 - توزیع برنامه‌های Blazor بر روی IIS
روش درست کردن دمو برای پروژه‌های blazor در Github (یا روش توزیع پروژه‌های Blazor WASM در Github-Pages)

ابتدا فایل yml زیر را در پوشه‌ی github\workflows\deploy.yml. قرار دهید (پوشه‌ای را به این نام، در ریشه‌ی پروژه‌ی خود ایجاد کنید):
name: Deploy to GitHub Pages

# Run workflow on every push to the main branch
on:
  push:
    branches: [ main ]

jobs:
  deploy-to-github-pages:
    # use ubuntu-latest image to run steps on
    runs-on: ubuntu-latest
    steps:
    # uses GitHub's checkout action to checkout code form the main branch
    - uses: actions/checkout@v2
    
    # sets up .NET Core SDK
    - name: Setup .NET Core SDK
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 5.0.302

    # publishes Blazor project to the release-folder
    - name: Publish .NET Core Project
      run: dotnet publish ./src/DNTPersianComponents.Blazor.WasmSample/Server/DNTPersianComponents.Blazor.WasmSample.Server.csproj -c Release -o release --nologo
    
    # changes the base-tag in index.html from '/' to 'DNTPersianComponents.Blazor' to match GitHub Pages repository subdirectory
    - name: Change base-tag in index.html from / to DNTPersianComponents.Blazor
      run: sed -i 's/<base href="\/" \/>/<base href="\/DNTPersianComponents.Blazor\/" \/>/g' release/wwwroot/index.html
    
    # copy index.html to 404.html to serve the same file when a file is not found
    - name: copy index.html to 404.html
      run: cp release/wwwroot/index.html release/wwwroot/404.html

    # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore)
    - name: Add .nojekyll file
      run: touch release/wwwroot/.nojekyll
      
    - name: Commit wwwroot to GitHub Pages
      uses: JamesIves/github-pages-deploy-action@3.7.1
      with:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        BRANCH: github-pages
        FOLDER: release/wwwroot
در این قالب، چهار مورد را باید ویرایش کنید:
- نام شاخه‌ی اصلی پروژه؛ که یا main است و یا master.
- شماره نگارش دات نت مورد استفاده.
- مسیر فایل csproj پروژه‌ی wasm.
- نام اصلی مخزن کد.


سپس آن‌را به مخزن کد خود commit کنید. بعد به قسمت settings->pages در github مراجعه کرده و source را بر روی نام شاخه‌ی جدید github-pages (فوق در قسمت آخر کار) قرار داده و آن‌را ذخیره کنید. الان سایت دموی شما در مسیری که در همین قسمت pages پس از ذخیره سازی، نمایش می‌دهد، آماده‌است.


یک نکته‌ی مهم

چون base href، توسط action فوق اصلاح می‌شود تا به پوشه‌ی نسبی محل قرارگیری برنامه اشاره کند، نیاز است navlink‌ها با href شروع شده‌ی با / نباشند؛ چون به ریشه‌ی سایت اشاره می‌کنند و نه مسیر نسبی محل قرارگیری برنامه. کلا در هر قسمتی از برنامه، این نکته باید رعایت شود. مثلا اگر فونت وبی را در فایل app.css تعریف کرده‌اید، مسیر آن نباید با / شروع شود؛ وگرنه یافت نخواهد شد. یک مثال:
فایل app.css برنامه در مسیر wwwroot\css\app.css قرار دارد و داخل آن فایل، فونت‌های پوشه‌ی دیگر wwwroot\lib\samim-font را به صورت زیر تعریف کرده‌ایم؛ که یعنی مسیر فونت را از ریشه‌ی سایت پیدا کن:
src: url('/lib/samim-font/Samim-Bold.eot?v=4.0.5');
این مسیر، باید به مسیر نسبی زیر که به یک پوشه‌ی بالاتر (از محل قرار گیری app.css) اشاره می‌کند، اصلاح شود:
src: url('../lib/samim-font/Samim-Bold.eot?v=4.0.5');