مطالب
مروری سریع بر اصول مقدماتی MVVM

در قسمت قبل، فلسفه وجودی MVVM و MVC و امثال آن‌را به بیانی ساده مطالعه کردید. همچنین به اینجا رسیدیم که بجای نوشتن روال رخدادگردان، از Commands استفاده کنید.
در این قسمت «تفکر MVVM ایی» بررسی خواهد شد! بنابراین سطح این قسمت را هم مقدماتی درنظر بگیرید.

در سیستم متداول مایکروسافتی ما همیشه یک فرم داریم به همراه یک سری کنترل. برای استفاده از این‌ها هم در فایل code behind فرم مرتبط، امکان دسترسی به این کنترل‌ها وجود دارد. مثلا textBox1.Text یعنی ارجاعی مستقیم به شیء textBox1 و سپس دسترسی به خاصیت متنی آن.
«تفکر MVVM ایی» می‌گه که: خیر! اینکار رو نکنید؛ ارجاع مستقیم به یک کنترل روش کار من نیست! فرم رو طراحی کنید؛ برای هیچکدام از کنترل‌ها هم نامی را مشخص نکنید (برخلاف رویه متداول). یک فایل درست کنید به نام Model ، داخل آن معادل textBox1.Text را که می‌خواهید استفاده کنید، پیش بینی و تعریف کنید؛ مثلا Public string Name . همین!
ما نمی‌خواهیم بدانیم که اصلا textBox1 وجود خارجی دارد یا نه. ما فقط با خاصیت متنی آن که در ادامه نحوه‌ی سیم کشی آن‌را هم بررسی خواهیم کرد، کار داریم.
بنابراین بجای اینکه بنویسید:

<TextBox Name="txtName" />

که ممکن است بعدا وسوسه شوید تا از txtName.Text آن استفاده کنید، بنویسید:

<TextBox Text="{Binding Name}" />

این مهم‌ترین قسمت «تفکر MVVM ایی» به زبان ساده است. یعنی قرار است تا حد ممکن از Binding استفاده کنیم. مثلا در قسمت قبل هم دیدید که بجای نوشتن روال رخدادگردان، فرمان مرتبط با آن‌را به جای دیگری Bind کردیم.

بنابراین تا اینجا Model ما به این شکل خواهد بود:

using System.ComponentModel;

namespace SL5Tests
{
public class MainPageModel : INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
raisePropertyChanged("Name");
}
}

public event PropertyChangedEventHandler PropertyChanged;
void raisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}


سؤال مهم:
تا اینجا یک فایل Model داریم که خاصیت Name در آن تعریف شده؛ یک فرم (View) هم داریم که فقط در آن نوشته شده Binding Name. الان این‌ها چطور به هم متصل خواهند شد؟
پاسخ: اینجا است که کلاس دیگری به نام ViewModel (همان فایل Code behind قدیمی است با این تفاوت که به هیچ فرم خاصی گره نخورده است و اصلا نمی‌داند که در برنامه فرمی وجود دارد یا نه)، کار خودش را شروع خواهد کرد:

namespace SL5Tests
{
public class MainPageViewModel
{
public MainPageModel MainPageModelData { set; get; }
public MainPageViewModel()
{
MainPageModelData = new MainPageModel();
MainPageModelData.Name = "Test1";
}
}
}

ما در این کلاس یک وهله از MainPageModel را ایجاد خواهیم کرد. اگر فرمی (که ما دقیقا نمی‌دانیم کدام فرم) در برنامه نیاز به یک ViewModel بر اساس مدل یاد شده داشت، می‌تواند آن‌را مورد استفاده قرار دهد. مقدار دهی آن در ViewModel موجب مقدار دهی خاصیت Text در فرم مرتبط خواهد شد و برعکس (البته به شرطی که مدل ما INotifyPropertyChanged را پیاده سازی کرده باشد و در فرم برنامه Binding Mode دو طرفه تعریف شود).

در قسمت بعد هم کار اتصال نهایی صورت می‌گیرد:
ابتدا xmlns:VM تعریف می‌شود تا بتوان به ViewModelها در طرف XAML دسترسی پیدا کرد. سپس در قسمت مثلا UserControl.Resources، این ViewModel را تعریف کرده و به عنوان DataContext بالاترین شیء فرم مقدار دهی خواهیم کرد:

<UserControl x:Class="SL5Tests.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:VM="clr-namespace:SL5Tests"
mc:Ignorable="d" Language="fa"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<VM:MainPageViewModel x:Name="vmMainPageViewModel" />
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource vmMainPageViewModel}}"
x:Name="LayoutRoot"
Background="White">
<TextBox Text="{Binding
MainPageModelData.Name,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>

اکنون اگر یک breakpoint روی این سطر Binding قرار دهیم و برنامه را اجرا کنیم، جزئیات این سیم کشی را در عمل بهتر می‌توان مشاهده کرد:


البته این قابلیت قرار دادن breakpoint روی Bindingهای تعریف شده در View فعلا به سیلورلایت 5 اضافه شده و هنوز در WPF موجود نیست.

حداقل مزیتی را که اینجا می‌توان مشاهده کرد این است که فایل MainPageViewModel چون نمی‌داند که قرار است در کدام View وهله سازی شود، به سادگی در Viewهای دیگر نیز قابل استفاده خواهد بود یا تغییر و تعویض کلی View آن کار ساده‌ای است.
Commanding قسمت قبل را هم اینجا می‌شود اضافه کرد. تعاریف DelegateCommandهای مورد نیاز در ViewModel قرار می‌گیرند. مابقی عملیات تفاوتی نمی‌کند و یکسان است.

مسیرراه‌ها
Entity framework code-first
شروع به کار با EF Code first

برای تکمیل بحث نیاز است تغییرات انجام شده از نگارش 4 به 6 را نیز مد نظر داشته باشید:


آشنایی با مباحث Migrations



آشنایی با تنظیمات نگاشت‌ها به دو روش استفاده از ویژگی‌ها و Fluent API



اعتبارسنجی و بررسی استثناءها



ردیابی تغییرات



استفاده از SQL خام و بانک‌های اطلاعاتی متفاوت

      نکات مهم کوئری نویسی در EF



      استفاده از EF در WPF


      لایه بندی پروژه‌های EF Code first



      پروژ‌ه‌های انجام شده با EF Code first

       
      مطالب
      آموزش Prism #2
      در پست قبلی توضیح کلی درباره فریم ورک Prism داده شد. در این بخش قصد داریم آموزش‌های داده شده در پست قبلی را با هم در یک مثال مشاهده کنیم. در پروژه‌های ماژولار طراحی و ایجاد زیر ساخت قوی برای مدیریت ماژول‌ها بسیار مهم است. Prism فریم ورکی است که فقط چارچوب و قواعد اصول طراحی این گونه پروژه‌ها را در اختیار ما قرار می‌دهد. در پروژه‌های ماژولار هر ماژول باید در یک اسمبلی جدا قرار داشته باشد که ساختار پیاده سازی آن می‌تواند کاملا متفاوت با پیاده سازی سایر ماژول‌ها باشد.
       برای شروع  باید فایل‌های اسمبلی Prism رو دانلود کنید(لینک دانلود).
      تشریح پروژه:
      می‌خواهیم برنامه ای بنویسیم که دارای سه ماژول زیر است.:
      1. ماژول Navigator : برای انتخاب و Switch کردن بین ماژول‌ها استفاده می‌شود؛
      2. ماژول طبقه بندی کتاب‌ها : لیست طبقه بندی کتاب‌ها را به ما نمایش می‌دهد؛
      3. ماژول لیست کتاب‌ها : عناوین کتاب‌ها به همراه نویسنده و کد کتاب را به ما نمایش می‌دهد.

      *در این پروژه از UnityContainer برای مباحث Dependency Injection استفاده شده است.
      ابتدا یک پروژه WPF در Vs.Net ایجاد کنید(در اینجا من نام آن را  FirstPrismSample گذاشتم). قصد داریم یک صفحه طراحی کنیم که دو ماژول مختلف در آن لود شود. ابتدا باید Shell پروژه رو طراحی کنیم. یک Window جدید به نام Shell بسازید و کد زیر را در آن کپی کنید.
      <Window x:Class="FirstPrismSample.Shell"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:com="http://www.codeplex.com/CompositeWPF"
          Title="Prism Sample By Masoud Pakdel" Height="400" Width="600" WindowStartupLocation="CenterScreen">
          <DockPanel>
            <ContentControl com:RegionManager.RegionName="WorkspaceRegion" Width="400"/>
            <ContentControl com:RegionManager.RegionName="NavigatorRegion"  DockPanel.Dock="Left" Width="200" />     
          </DockPanel>
      </Window>
      در این صفحه دو ContentControl تعریف کردم یکی به نام Navigator و دیگری به نام Workspace. به وسیله RegionName که یک AttachedProperty است هر کدوم از این نواحی را برای Prism تعریف کردیم. حال باید یک ماژول برای Navigator و دو ماژول دیگر یکی برای طبقه بندی کتاب‌ها و دیگری برای لیست کتاب‌ها بسازیم.

      #پروژه Common
      قبل از هر چیز یک پروژه Common می‌سازیم و مشترکات بین ماژول‌ها رو در آن قرار می‌دهیم(این پروژه باید به تمام ماژول‌ها رفرنس داده شود).  این مشترکات شامل :
      • کلاس پایه ViewModel
      • کلاس ViewRequestEvent
      • کلاس ModuleService

      کد کلاس ViewModelBase که فقط اینترفیس INotifyPropertyChanged رو پیاده سازی کرده است:

      using System.ComponentModel;
      
      namespace FirstPrismSample.Common
      {
          public abstract class ViewModelBase : INotifyPropertyChanged
          {
              public event PropertyChangedEventHandler PropertyChanged;
              protected void RaisePropertyChangedEvent( string propertyName )
              {
                  if ( PropertyChanged != null )
                  {
                      PropertyChangedEventArgs e = new PropertyChangedEventArgs( propertyName );
                      PropertyChanged( this, e );
                  }
              }
          }
      }
      کلاس ViewRequestEvent که به صورت زیر است:
      using Microsoft.Practices.Composite.Presentation.Events;
      
      namespace FirstPrismSample.Common.Events
      {
          public class ViewRequestedEvent : CompositePresentationEvent<string>
          {
          }
      }
      توضیح درباره CompositePresentationEvent :
      در طراحی و توسعه پروژه‌های ماژولار نکته ای که باید به آن دقت کنید این است که ماژول‌های پروژه نباید به هم وابستگی مستقیم داشته باشند در عین حال ماژول‌ها باید بتوانند با هم در ارتباط باشند. CPE یا CompositePresentationEventدقیقا برای این منظور به وجود آمده است. CPE که در این جا طراحی کردم فقط کلاسی است که از CompositePresentationEventارث برده است و دلیل آن که به صورت string generic استفاده شده است این است که می‌خواهیم در هر درخواست نام ماژول درخواستی را داشته باشیم و به همین دلیل نام آن را ViewRequestedEvent گذاشتم.

      توضیح درباره EventAggregator

      EventAggregator یا به اختصار EA مکانیزمی است در پروژهای ماژولار برای اینکه در Composite UI‌ها بتوانیم بین کامپوننت‌ها ارتباط برقرار کنیم. استفاده از EA وابستگی بین ماژول‌ها را  از بین خواهد برد. برنامه نویسانی که با MVVM Light آشنایی دارند از قابلیت Messaging موجود در این فریم ورک برای ارتباط بین View و  ViewModel استفاده می‌کنند. در Prism این عملیات توسط EA انجام می‌شود. یعنی برای ارتباط با View‌ها باید از EA تعبیه شده در Prism استفاده کنیم. در ادامه مطلب، چگونگی استفاده از EA را خواهید آموخت.
      اینترفیس IModuleService که فقط شامل یک متد است:
      namespace FirstPrismSample .Common
      {
          public interface IModuleServices
          {     
              void ActivateView(string viewName);
          }
      }
      کلاس ModuleService که اینترفیس بالا را پیاده سازی کرده است:
      using Microsoft.Practices.Composite.Regions;
      using Microsoft.Practices.Unity;
      
      namespace FirstPrismSample.Common
      {
          public class ModuleServices : IModuleServices
          {     
              private readonly IUnityContainer m_Container;  
           
              public ModuleServices(IUnityContainer container)
              {
                  m_Container = container;
              }      
         
              public void ActivateView(string viewName)
              {        
                  var regionManager = m_Container.Resolve<IRegionManager>();
      
                  // غیر فعال کردن ویو
                  IRegion workspaceRegion = regionManager.Regions["WorkspaceRegion"];
                  var views = workspaceRegion.Views;
                  foreach (var view in views)
                  {
                      workspaceRegion.Deactivate(view);
                  }
      
                  //فعال کردن ویو انتخاب شده 
                  var viewToActivate = regionManager.Regions["WorkspaceRegion"].GetView(viewName);
                  regionManager.Regions["WorkspaceRegion"].Activate(viewToActivate);
              }
          }
      }
      متد ActivateView نام view مورد نظر برای فعال سازی را دریافت می‌کند. برای فعال کردن View ابتدا باید سایر view‌های فعال در RegionManager را غیر فعال کنیم. سپس فقط view مورد نظر در RegionManager انتخاب و فعال می‌شود.

      *نکته: در هر ماژول ارجاع به اسمبلی‌های Prism مورد نیاز است.

      #ماژول طبقه بندی کتاب ها:
      برای شروع یک Class Library جدید به نام ModuleCategory به پروژه اضافه کنید. یک UserControl به نام CategoryView بسازید و کد‌های زیر را در آن کپی کنید.
      <UserControl x:Class="FirstPrismSample.ModuleCategory.CategoryView "
                   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
                   Background="LightGray" FlowDirection="RightToLeft" FontFamily="Tahoma">
          <Grid>
              <Grid.RowDefinitions>
                  <RowDefinition Height="Auto"/>
                  <RowDefinition Height="*"/>
              </Grid.RowDefinitions>
              <TextBlock Text=" طبقه بندی ها"/>
              <ListView Grid.Row="1"  Margin="10" Name="lvCategory">
                  <ListView.View>
                      <GridView>
                          <GridViewColumn Header="کد" Width="50" />
                          <GridViewColumn Header="عنوان" Width="200"  />                  
                      </GridView>
                  </ListView.View>
              </ListView>
          </Grid>
      </UserControl>
      یک کلاس به نام CategoryModule بسازید که اینترفیس IModule رو پیاده سازی کند.
      using Microsoft.Practices.Composite.Events;
      using Microsoft.Practices.Composite.Modularity;
      using Microsoft.Practices.Composite.Regions;
      using Microsoft.Practices.Unity;
      using FirstPrismSample.Common;
      using FirstPrismSample.Common.Events;
      using Microsoft.Practices.Composite.Presentation.Events;
      
      namespace FirstPrismSample.ModuleCategory
      {
          [Module(ModuleName = "ModuleCategory")]
          public class CategoryModule : IModule
          {      
              private readonly IUnityContainer m_Container;
              private readonly string moduleName = "ModuleCategory";
                  
              public CategoryModule(IUnityContainer container)
              {
                  m_Container = container;
              }   
            
              ~CategoryModule()
              {
                  var eventAggregator = m_Container.Resolve<IEventAggregator>();
                  var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();       
                  viewRequestedEvent.Unsubscribe(ViewRequestedEventHandler);
              }
           
              public void Initialize()
              {           
                  var regionManager = m_Container.Resolve<IRegionManager>();
                  regionManager.Regions["WorkspaceRegion"].Add(new CategoryView(), moduleName);
               
                  var eventAggregator = m_Container.Resolve<IEventAggregator>();
                  var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
                  viewRequestedEvent.Subscribe(this.ViewRequestedEventHandler, true);
              }
             
              public void ViewRequestedEventHandler(string moduleName)
              {
                  if (this.moduleName != moduleName) return;
                
                  var moduleServices = m_Container.Resolve<IModuleServices>();
                  moduleServices.ActivateView(moduleName);
              }      
          }
      }
      چند نکته :
      *ModuleAttribute استفاده شده در بالای کلاس برای تعیین نام ماژول استفاده می‌شود. این Attribute دارای دو خاصیت دیگر هم است :
      1. OnDemand : برای تعیین اینکه ماژول باید به صورت OnDemand (بنا به درخواست) لود شود.
      2. StartupLoaded : برای تعیین اینکه ماژول به عنوان ماژول اول پروزه لود شود.(البته این گزینه Obsolute شده است)

      *برای تعریف ماژول کلاس مورد نظر حتما باید اینترفیس IModule را پیاده سازی کند. این اینترفیس فقط شامل یک متد است به نام Initialize.

      *در این پروژه چون View‌های برنامه صرفا جهت نمایش هستند در نتیجه نیاز به ایجاد ViewModel برای آن‌ها نیست. در پروژه‌های اجرایی حتما برای هر View باید ViewModel متناظر با آن تهیه شود.

      توضیح درباره متد Initialize

      در این متد ابتدا با استفاده از Container موجود RegionManager را به دست می‌آوریم. با استفاده از RegionManager می‌تونیم یک CompositeUI طراحی کنیم. در فایل Shell مشاهده کردید که یک صفحه به دو ناحیه تقسیم شد و به هر ناحیه هم یک نام اختصاص دادیم. دستور زیر به یک ناحیه اشاره خواهد داشت:

      regionManager.Regions["WorkspaceRegion"]
      در خط بعد با استفاده از EA یا Event Aggregator توانستیم CPE را بدست بیاوریم. متد Subscribe در کلاس CPE  یک ارجاع قوی به delegate مورد نظر ایجاد می‌کند(پارامتر دوم این متد که از نوع boolean است) که به این معنی است که این delegate هیچ گاه توسط GC جمع آوری نخواهد شد. در نتیجه، قبل از اینکه ماژول بسته شود باید به صورت دستی این کار را انجام دهیم که مخرب را برای همین ایجاد کردیم. اگر به کد‌های مخرب دقت کنید می‌بینید که با استفاده از EA توانستیم ViewRequestEventHandler را Unsubscribe کنیم به دلیل اینکه از ارجاع قوی با strong Reference در متد Subscribe استفاده شده است.
      دستور moduleService.ActiveateView ماژول مورد نظر را در region مورد نظر هاست خواهد کرد.

      #ماژول لیست کتاب ها:
      ابتدا یک Class Library به نام ModuleBook بسازید  و همانند ماژول قبلی نیاز به یک Window و یک کلاس داریم:
      BookWindow که کاملا مشابه به CategoryView است.
      <UserControl x:Class="FirstPrismSample.ModuleBook.BookView"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          Background="LightGray" FontFamily="Tahoma" FlowDirection="RightToLeft">
          <Grid>
              <Grid.RowDefinitions>
                  <RowDefinition Height="Auto"/>
                  <RowDefinition Height="*"/>
              </Grid.RowDefinitions>
              <TextBlock Text="لیست کتاب ها"/>
              <ListView Grid.Row="1" Margin="10" Name="lvBook">
                  <ListView.View>
                      <GridView>
                          <GridViewColumn Header="کد" Width="50"  />
                          <GridViewColumn Header="عنوان" Width="200" />
                          <GridViewColumn Header="نویسنده" Width="150" />
                      </GridView>
                  </ListView.View>
              </ListView>
          </Grid>
      </UserControl>

      کلاس BookModule که پیاده سازی  و توضیحات آن کاملا مشابه به CategoryModule می‌باشد.
      using Microsoft.Practices.Composite.Events;
      using Microsoft.Practices.Composite.Modularity;
      using Microsoft.Practices.Composite.Presentation.Events;
      using Microsoft.Practices.Composite.Regions;
      using Microsoft.Practices.Unity;
      using FirstPrismSample.Common;
      using FirstPrismSample.Common.Events;
      
      namespace FirstPrismSample.ModuleBook
      {
          [Module(ModuleName = "moduleBook")]
          public class BookModule : IModule
          {      
              private readonly IUnityContainer m_Container;
              private readonly string moduleName = "ModuleBook";     
          
              public BookModule(IUnityContainer container)
              {
                  m_Container = container;          
              }     
             
              ~BookModule()
              {           
                  var eventAggregator = m_Container.Resolve<IEventAggregator>();
                  var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
                
                  viewRequestedEvent.Unsubscribe(ViewRequestedEventHandler);
              }     
           
              public void Initialize()
              {           
                  var regionManager = m_Container.Resolve<IRegionManager>();
                  var view = new BookView();
                  regionManager.Regions["WorkspaceRegion"].Add(view, moduleName);
                  regionManager.Regions["WorkspaceRegion"].Deactivate(view);
            
                  var eventAggregator = m_Container.Resolve<IEventAggregator>();
                  var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
                  viewRequestedEvent.Subscribe(this.ViewRequestedEventHandler, true);
              }     
            
              public void ViewRequestedEventHandler(string moduleName)
              {           
                  if (this.moduleName != moduleName) return;
               
                  var moduleServices = m_Container.Resolve<IModuleServices>();
                  moduleServices.ActivateView(m_WorkspaceBName);
              }
          }
      }
      #ماژول Navigator
      برای این ماژول هم ابتدا View مورد نظر را ایجاد می‌کنیم:
      <UserControl x:Class="FirstPrismSample.ModuleNavigator.NavigatorView"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
          <Grid>
              <StackPanel VerticalAlignment="Center">
                  <TextBlock Text="انتخاب ماژول" Foreground="Green" HorizontalAlignment="Center"
                      VerticalAlignment="Center" FontFamily="Tahoma" FontSize="24" FontWeight="Bold" />
              <Button Command="{Binding ShowModuleCategory}" Margin="5" Width="125">طبقه بندی کتاب ها</Button>
              <Button Command="{Binding ShowModuleBook}" Margin="5" Width="125">لیست کتاب ها</Button>
              </StackPanel>
              </Grid>
      </UserControl>
      حال قصد داریم برای این View یک ViewModel بسازیم. نام آن را INavigatorViewModel خواهیم گذاشت:
      public interface INavigatorViewModel
          {    
              ICommand ShowModuleCategory { get; set; }       
              ICommand ShowModuleBook { get; set; }
      
              string ActiveWorkspace { get; set; }       
      
              IUnityContainer Container { get; set; }
      
              event PropertyChangedEventHandler PropertyChanged;
          }
       *در اینترفیس بالا دو Command داریم که هر کدام وظیفه لود یک ماژول را بر عهده دارند.
       *خاصیت ActiveWorkspace برای تعیین workspace فعال تعریف شده است.

      حال به پیاده سازی مثال بالا می‌پردازیم:
      public class NavigatorViewModel : ViewModelBase, INavigatorViewModel
          {        
              public NavigatorViewModel(IUnityContainer container)
              {
                  this.Initialize(container);
              }   
             
              public ICommand ShowModuleCategory { get; set; }
            
              public ICommand ShowModuleBook { get; set; }      
                    
              public string ActiveWorkspace { get; set; }       
      
              public IUnityContainer Container { get; set; }        
           
              private void Initialize(IUnityContainer container)
              {
                  this.Container = container;
                  this.ShowModuleCategory = new ShowModuleCategoryCommand(this);
                  this.ShowModuleBook = new ShowModuleBookCommand(this);
                  this.ActiveWorkspace = "ModuleCategory";
              }        
          }
      تنها نکته مهم در کلاس بالا متد Initialize است که دو Command مورد نظر را پیاده سازی کرده است. ماژول پیش فرض هم ماژول طبقه بندی کتاب‌ها یا ModuleCategory در نظر گرفته شده است.  همان طور که می‌بینید پیاده سازی Command‌ها بالا توسط دو کلاس ShowModuleCategoryCommand و ShowModuleBookCommand انجام شده که در زیر کد‌های آن‌ها را می‌بینید.
      #کد کلاس ShowModuleCategoryCommand  
      public class ShowModuleCategoryCommand : ICommand
          {      
              private readonly NavigatorViewModel viewModel;
              private const string workspaceName = "ModuleCategory";         
      
              public ShowModuleCategoryCommand(NavigatorViewModel viewModel)
              {
                  this.viewModel = viewModel;
              }          
      
              public bool CanExecute(object parameter)
              {
                  return viewModel.ActiveWorkspace != workspaceName;
              }
      
              public event EventHandler CanExecuteChanged
              {
                  add { CommandManager.RequerySuggested += value; }
                  remove { CommandManager.RequerySuggested -= value; }
              }
           
              public void Execute(object parameter)
              {
                  CommandServices.ShowWorkspace(workspaceName, viewModel);
              }      
          }
      #کد کلاس ShowModuleBookCommand  
      public class ShowModuleBookCommand : ICommand
          {
              private readonly NavigatorViewModel viewModel;
              private readonly string workspaceName = "ModuleBook";
      
              public ShowModuleBookCommand( NavigatorViewModel viewModel )
              {
                  this.viewModel = viewModel;
              }
      
              public bool CanExecute( object parameter )
              {
                  return viewModel.ActiveWorkspace != workspaceName;
              }
      
              public event EventHandler CanExecuteChanged
              {
                  add { CommandManager.RequerySuggested += value; }
                  remove { CommandManager.RequerySuggested -= value; }
              }
      
              public void Execute( object parameter )
              {
                  CommandServices.ShowWorkspace( workspaceName , viewModel );
              }
          }
      با توجه به این که فرض است با متد‌های Execute و CanExecute و CanExecuteChanged آشنایی دارید از توضیح این مطالب خودداری خواهم کرد. فقط کلاس CommandServices  در متد Execute دارای متدی به نام ShowWorkspace است که کد‌های زیر را شامل می‌شود:
      public static void ShowWorkspace(string workspaceName, INavigatorViewModel viewModel)
        {           
                  var eventAggregator = viewModel.Container.Resolve<IEventAggregator>();
                  var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
                  viewRequestedEvent.Publish(workspaceName);
              
                  viewModel.ActiveWorkspace = workspaceName;
       }
      در این متد با استفاده از CPE که در پروژه Common ایجاد کردیم ماژول مورد نظر را لود خواهیم کرد. و بعد از آن مقدار ActiveWorkspace جاری در ViewModel به نام ماژول تغییر پیدا می‌کند. متد Publish در CPE این کار را انجام خواهد دارد.

      عدم وابستگی ماژول ها
      همان طور که می‌بینید ماژول‌های پروژه به هم Reference داده نشده اند حتی هیچ Reference هم به پروژه اصلی یعنی جایی که فایل App.xaml قرار دارد، داده نشده است ولی در عین حال باید با هم در ارتباط باشند. برای حل این مسئله این ماژول‌ها باید در فولدر bin پروژه اصلی خود را کپی کنند. بهترین روش استفاده از Pre-Post Build Event خود VS.Net است. برای این کار از پنجره Project Properties وارد برگه Build Events شوید و از قسمت Post Build Event Command Line  استفاده کنید و کد زیر را در آن کپی نمایید:
      xcopy "$(TargetDir)FirstPrismSample.ModuleBook.dll" "$(SolutionDir)FirstPrismSample\bin\$(ConfigurationName)\Modules\" /Y
      قطعا باید به جای FirstPrismSample نام Solution خود و به جای ModuleBook نام ماژول را وارد نمایید.

      مانند:


      مراحل بالا برای هر ماژول باید تکرار شود(ModuleNavigation , ModuleBook , ModuleCategory). بعد از Rebuild  پروژه در فولدر bin پروژه اصلی یک فولدر به نام Module ایجاد می‌شود که اسمبلی هر ماژول در آن کپی خواهد شد.

      ایجاد Bootstrapper
      حال نوبت به Bootstrapper میرسد(در پست قبلی در باره مفهوم Bootstrapper شرح داده شد). در پروژه اصلی یعنی جایی که فایل App.xaml قرار دارد کلاس زیر را ایجاد کنید.
          public class Bootstrapper : UnityBootstrapper
          {     
              protected override void ConfigureContainer()
              {
                  base.ConfigureContainer();
                  Container.RegisterType<IModuleServices, ModuleServices>();
              }
         
              protected override DependencyObject CreateShell()
              {
                  var shell = new Shell();
                  shell.Show();
                  return shell;
              }
         
              protected override IModuleCatalog GetModuleCatalog()
              {           
                  var catalog = new DirectoryModuleCatalog();
                  catalog.ModulePath = @".\Modules";
                  return catalog;
              }
          }
      متد ConfigureContainer برای تزریق وابستگی به وسیله UnityContainer استفاده می‌شود. در این متد باید تمامی Registration‌های مورد نیاز برای DI را انجام دهید. نکته مهم این است که عملیات وهله سازی و Initialization برای  Container  در متد base کلاس UnityBootstrapper انجام خواهد شد پس همیشه باید متد base این کلاس در ابتدای این متد فراخوانی شود در غیر این صورت با خطا متوقف خواهید شد.
      متد CreateShell برای ایجاد و وهله سازی از Shell پروژه استفاده می‌شود. در این جا یک وهله از Shell Window برگشت داده می‌شود.
      متد GetModuleCatalog برای تعیین مسیر ماژول‌ها در پروژه کاربرد دارد. در این متد با استفاده از خاصیت ModulePath کلاس DirectoryModuleCatalog تعیین کرده ایم که ماژول‌های پروژه در فولدر Modules موجود در bin اصلی پروژه قرار دارد. اگر به دستورات کپی در Post Build Event قسمت قبل توجه کنید می‌بینید که دستور ساخت فولدر وجود دارد.
      "$(SolutionDir)FirstPrismSample\bin\$(ConfigurationName)\Modules\" /Y
      *نکته: اگر استفاده از این روش برای شناسایی ماژول‌ها توسط Bootstrapper را چندان جالب نمی‌دانید می‌تونید از MEF استفاده کنید که اسمبلی ماژول‌های پروژه را به راحتی شناسایی می‌کند و در اختیار Bootsrtapper قرار می‌دهد(از آن جا در مستندات مربوط به Prism، بیشتر به استفاده از MEF تاکید شده است من هم در پست‌های بعدی، مثال‌ها را با MEF پیاده سازی خواهم کرد)

      در پایان باید فایل App.xaml را تغییر دهید به گونه ای که متد Run در کلاس Bootstapper ابتدا اجرا شود.
      public partial class App : Application
          {
              protected override void OnStartup(StartupEventArgs e)
              {
                  base.OnStartup(e);
                  var bootstrapper = new Bootstrapper();
                  bootstrapper.Run();
              }
          }


      اجرای پروژه:
      بعد از اجرا، با انتخاب ماژول مورد نظر اطلاعات ماژول در Workspace Content Control لود خواهد شد.

      ادامه دارد...


      مطالب
      EF Code First #10

      حین کار با ORMهای پیشرفته، ویژگی‌های جالب توجهی در اختیار برنامه نویس‌ها قرار می‌گیرد که در زمان استفاده از کلاس‌های متداول SQLHelper از آن‌ها خبری نیست؛ مانند:
      الف) Deferred execution
      ب) Lazy loading
      ج) Eager loading

      نحوه بررسی SQL نهایی تولیدی توسط EF

      برای توضیح موارد فوق، نیاز به مشاهده خروجی SQL نهایی حاصل از ORM است و همچنین شمارش تعداد بار رفت و برگشت به بانک اطلاعاتی. بهترین ابزاری را که برای این منظور می‌توان پیشنهاد داد، برنامه EF Profiler است. برای دریافت آن می‌توانید به این آدرس مراجعه کنید: (^) و (^)

      پس از وارد کردن نام و آدرس ایمیل، یک مجوز یک ماهه آزمایشی، به آدرس ایمیل شما ارسال خواهد شد.
      زمانیکه این فایل را در ابتدای اجرای برنامه به آن معرفی می‌کنید، محل ذخیره سازی نهایی آن جهت بازبینی بعدی، مسیر MyUserName\Local Settings\Application Data\EntityFramework Profiler خواهد بود.

      استفاده از این برنامه هم بسیار ساده است:
      الف) در برنامه خود، ارجاعی را به اسمبلی HibernatingRhinos.Profiler.Appender.dll که در پوشه برنامه EFProf موجود است، اضافه کنید.
      ب) در نقطه آغاز برنامه، متد زیر را فراخوانی نمائید:
      HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();

      نقطه آغاز برنامه می‌تواند متد Application_Start برنامه‌های وب، در متد Program.Main برنامه‌های ویندوزی کنسول و WinForms و در سازنده کلاس App برنامه‌های WPF باشد.
      ج) برنامه EFProf را اجرا کنید.

      مزایای استفاده از این برنامه
      1) وابسته به بانک اطلاعاتی مورد استفاده نیست. (برخلاف برای مثال برنامه معروف SQL Server Profiler که فقط به همراه SQL Server ارائه می‌شود)
      2) خروجی SQL نمایش داده شده را فرمت کرده و به همراه Syntax highlighting نیز هست.
      3) کار این برنامه صرفا به لاگ کردن SQL تولیدی خلاصه نمی‌شود. یک سری از Best practices را نیز به شما گوشزد می‌کند. بنابراین اگر نیاز دارید سیستم خود را بر اساس دیدگاه یک متخصص بررسی کنید (یک Code review ارزشمند)، این ابزار می‌تواند بسیار مفید باشد.
      4) می‌تواند کوئری‌های سنگین و سبک را به خوبی تشخیص داده و گزارشات آماری جالبی را به شما ارائه دهد.
      5) می‌تواند دقیقا مشخص کند، کوئری را که مشاهده می‌کنید از طریق کدام متد در کدام کلاس صادر شده است و دقیقا از چه سطری.
      6) امکان گروه بندی خودکار کوئری‌های صادر شده را بر اساس DbContext مورد استفاده به همراه دارد.
      و ...

      استفاده از این برنامه حین کار با EF «الزامی» است! (البته نسخه‌های NH و سایر ORMهای دیگر آن نیز موجود است و این مباحث در مورد تمام ORMهای پیشرفته صادق است)
      مدام باید بررسی کرد که صفحه جاری چه تعداد کوئری را به بانک اطلاعاتی ارسال کرده و به چه نحوی. همچنین آیا می‌توان با اعمال اصلاحاتی، این وضع را بهبود بخشید. بنابراین عدم استفاده از این برنامه حین کار با ORMs، همانند راه رفتن در خواب است! ممکن است تصور کنید برنامه دارد به خوبی کار می‌کند اما ... در پشت صحنه فقط صفحه جاری برنامه، 100 کوئری را به بانک اطلاعاتی ارسال کرده، در حالیکه شما تنها نیاز به یک کوئری داشته‌اید.


      کلاس‌های مدل مثال جاری

      کلاس‌های مدل مثال جاری از یک دپارتمان که دارای تعدادی کارمند می‌باشد، تشکیل شده است. ضمنا هر کارمند تنها در یک دپارتمان می‌تواند مشغول به کار باشد و رابطه many-to-many نیست :

      using System.Collections.Generic;

      namespace EF_Sample06.Models
      {
      public class Department
      {
      public int DepartmentId { get; set; }
      public string Name { get; set; }

      //Creates Employee navigation property for Lazy Loading (1:many)
      public virtual ICollection<Employee> Employees { get; set; }
      }
      }

      namespace EF_Sample06.Models
      {
      public class Employee
      {
      public int EmployeeId { get; set; }
      public string FirstName { get; set; }
      public string LastName { get; set; }

      //Creates Department navigation property for Lazy Loading
      public virtual Department Department { get; set; }
      }
      }

      نگاشت دستی این کلاس‌ها هم ضرورتی ندارد، زیرا قراردادهای توکار EF Code first را رعایت کرده و EF در اینجا به سادگی می‌تواند primary key و روابط one-to-many را بر اساس navigation properties تعریف شده، تشخیص دهد.

      در اینجا کلاس Context برنامه به شرح زیر است:

      using System.Data.Entity;
      using EF_Sample06.Models;

      namespace EF_Sample06.DataLayer
      {
      public class Sample06Context : DbContext
      {
      public DbSet<Department> Departments { set; get; }
      public DbSet<Employee> Employees { set; get; }
      }
      }


      و تنظیمات ابتدایی نحوه به روز رسانی و آغاز بانک اطلاعاتی نیز مطابق کدهای زیر می‌باشد:

      using System.Collections.Generic;
      using System.Data.Entity.Migrations;
      using EF_Sample06.Models;

      namespace EF_Sample06.DataLayer
      {
      public class Configuration : DbMigrationsConfiguration<Sample06Context>
      {
      public Configuration()
      {
      AutomaticMigrationsEnabled = true;
      AutomaticMigrationDataLossAllowed = true;
      }

      protected override void Seed(Sample06Context context)
      {
      var employee1 = new Employee { FirstName = "f name1", LastName = "l name1" };
      var employee2 = new Employee { FirstName = "f name2", LastName = "l name2" };
      var employee3 = new Employee { FirstName = "f name3", LastName = "l name3" };
      var employee4 = new Employee { FirstName = "f name4", LastName = "l name4" };

      var dept1 = new Department { Name = "dept 1", Employees = new List<Employee> { employee1, employee2 } };
      var dept2 = new Department { Name = "dept 2", Employees = new List<Employee> { employee3 } };
      var dept3 = new Department { Name = "dept 3", Employees = new List<Employee> { employee4 } };

      context.Departments.Add(dept1);
      context.Departments.Add(dept2);
      context.Departments.Add(dept3);
      base.Seed(context);
      }
      }
      }

      نکته: تهیه خروجی XML از نگاشت‌های خودکار تهیه شده

      اگر علاقمند باشید که پشت صحنه نگاشت‌های خودکار EF Code first را در یک فایل XML جهت بررسی بیشتر ذخیره کنید، می‌توان از متد کمکی زیر استفاده کرد:

      void ExportMappings(DbContext context, string edmxFile)
      {
      var settings = new XmlWriterSettings { Indent = true };
      using (XmlWriter writer = XmlWriter.Create(edmxFile, settings))
      {
      System.Data.Entity.Infrastructure.EdmxWriter.WriteEdmx(context, writer);
      }
      }

      بهتر است پسوند فایل XML تولیدی را edmx قید کنید تا بتوان آن‌را با دوبار کلیک بر روی فایل، در ویژوال استودیو نیز مشاهده کرد:

      using (var db = new Sample06Context())
      {
      ExportMappings(db, "mappings.edmx");
      }



      الف) بررسی Deferred execution یا بارگذاری به تاخیر افتاده

      برای توضیح مفهوم Deferred loading/execution بهترین مثالی را که می‌توان ارائه داد، صفحات جستجوی ترکیبی در برنامه‌ها است. برای مثال یک صفحه جستجو را طراحی کرده‌اید که حاوی دو تکست باکس دریافت FirstName و LastName کاربر است. کنار هر کدام از این تکست باکس‌ها نیز یک چک‌باکس قرار دارد. به عبارتی کاربر می‌تواند جستجویی ترکیبی را در اینجا انجام دهد. نحوه پیاده سازی صحیح این نوع مثال‌ها در EF Code first به چه نحوی است؟

      using System;
      using System.Collections.Generic;
      using System.Data.Entity;
      using System.Linq;
      using EF_Sample06.DataLayer;
      using EF_Sample06.Models;

      namespace EF_Sample06
      {
      class Program
      {
      static IList<Employee> FindEmployees(string fName, string lName, bool byName, bool byLName)
      {
      using (var db = new Sample06Context())
      {
      IQueryable<Employee> query = db.Employees.AsQueryable();

      if (byLName)
      {
      query = query.Where(x => x.LastName == lName);
      }

      if (byName)
      {
      query = query.Where(x => x.FirstName == fName);
      }

      return query.ToList();
      }
      }

      static void Main(string[] args)
      {
      // note: remove this line if you received : create database is not supported by this provider.
      HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();

      Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample06Context, Configuration>());

      var list = FindEmployees("f name1", "l name1", true, true);
      foreach (var item in list)
      {
      Console.WriteLine(item.FirstName);
      }
      }
      }
      }

      نحوه صحیح این نوع پیاده سازی ترکیبی را در متد FindEmployees مشاهده می‌کنید. نکته مهم آن، استفاده از نوع IQueryable و متد AsQueryable است و امکان ترکیب کوئری‌ها با هم.
      به نظر شما با فراخوانی متد FindEmployees به نحو زیر که هر دو شرط آن توسط کاربر انتخاب شده است، چه تعداد کوئری به بانک اطلاعاتی ارسال می‌شود؟

      var list = FindEmployees("f name1", "l name1", true, true);

      شاید پاسخ دهید که سه بار : یکبار در متد db.Employees.AsQueryable و دوبار هم در حین ورود به بدنه شرط‌های یاد شده و اینجا است که کسانی که قبلا با رویه‌های ذخیره شده کار کرده باشند، شروع به فریاد و فغان می‌کنند که ما قبلا این مسایل رو با یک SP در یک رفت و برگشت مدیریت می‌کردیم!
      پاسخ صحیح: «فقط یکبار»! آن‌هم تنها در زمان فراخوانی متد ToList و نه قبل از آن.
      برای اثبات این مدعا نیاز است به خروجی SQL لاگ شده توسط EF Profiler مراجعه کرد:

      SELECT [Extent1].[EmployeeId]              AS [EmployeeId],
      [Extent1].[FirstName] AS [FirstName],
      [Extent1].[LastName] AS [LastName],
      [Extent1].[Department_DepartmentId] AS [Department_DepartmentId]
      FROM [dbo].[Employees] AS [Extent1]
      WHERE ([Extent1].[LastName] = 'l name1' /* @p__linq__0 */)
      AND ([Extent1].[FirstName] = 'f name1' /* @p__linq__1 */)


      IQueryable قلب LINQ است و تنها بیانگر یک عبارت (expression) از رکوردهایی می‌باشد که مد نظر شما است و نه بیشتر. برای مثال زمانیکه یک IQueryable را همانند مثال فوق فیلتر می‌کنید، هنوز چیزی از بانک اطلاعاتی یا منبع داده‌ای دریافت نشده است. هنوز هیچ اتفاقی رخ نداده است و هنوز رفت و برگشتی به منبع داده‌ای صورت نگرفته است. به آن باید به شکل یک expression builder نگاه کرد و نه لیستی از اشیاء فیلتر شده‌ی ما. به این مفهوم، deferred execution (اجرای به تاخیر افتاده) نیز گفته می‌شود.
      کوئری LINQ شما تنها زمانی بر روی بانک اطلاعاتی اجرا می‌شود که کاری بر روی آن صورت گیرد مانند فراخوانی متد ToList، فراخوانی متد First یا FirstOrDefault و امثال آن. تا پیش از این فقط به شکل یک عبارت در برنامه وجود دارد و نه بیشتر.
      اطلاعات بیشتر: «تفاوت بین IQueryable و IEnumerable در حین کار با ORMs»



      ب) بررسی Lazy Loading یا واکشی در صورت نیاز

      در مطلب جاری اگر به کلاس‌های مدل برنامه دقت کنید، تعدادی از خواص به صورت virtual تعریف شده‌اند. چرا؟
      تعریف یک خاصیت به صورت virtual، پایه و اساس lazy loading است و به کمک آن، تا به اطلاعات شیءایی نیاز نباشد، وهله سازی نخواهد شد. به این ترتیب می‌توان به کارآیی بیشتری در حین کار با ORMs رسید. برای مثال در کلاس‌های فوق، اگر تنها نیاز به دریافت نام یک دپارتمان هست، نباید حین وهله سازی از شیء دپارتمان، شیء لیست کارمندان مرتبط با آن نیز وهله سازی شده و از بانک اطلاعاتی دریافت شوند. به این وهله سازی با تاخیر، lazy loading گفته می‌شود.
      Lazy loading پیاده سازی ساده‌ای نداشته و مبتنی است بر بکارگیری AOP frameworks یا کتابخانه‌هایی که امکان تشکیل اشیاء Proxy پویا را در پشت صحنه فراهم می‌کنند. علت virtual تعریف کردن خواص رابط نیز به همین مساله بر می‌گردد، تا این نوع کتابخانه‌ها بتوانند در نحوه تعریف اینگونه خواص virtual در زمان اجرا، در پشت صحنه دخل و تصرف کنند. البته حین استفاده از EF یا انواع و اقسام ORMs دیگر با این نوع پیچیدگی‌ها روبرو نخواهیم شد و تشکیل اشیاء Proxy در پشت صحنه انجام می‌شوند.

      یک مثال: قصد داریم اولین دپارتمان ثبت شده در حین آغاز برنامه را یافته و سپس لیست کارمندان آن‌را نمایش دهیم:

      using (var db = new Sample06Context())
      {
      var dept1 = db.Departments.Find(1);
      if (dept1 != null)
      {
      Console.WriteLine(dept1.Name);
      foreach (var item in dept1.Employees)
      {
      Console.WriteLine(item.FirstName);
      }
      }
      }



      رفتار یک ORM جهت تعیین اینکه آیا نیاز است برای دریافت اطلاعات بین جداول Join صورت گیرد یا خیر، واکشی حریصانه و غیرحریصانه را مشخص می‌سازد.
      در حالت واکشی حریصانه به ORM خواهیم گفت که لطفا جهت دریافت اطلاعات فیلدهای جداول مختلف، از همان ابتدای کار در پشت صحنه، Join های لازم را تدارک ببین. در حالت واکشی غیرحریصانه به ORM خواهیم گفت به هیچ عنوان حق نداری Join ایی را تشکیل دهی. هر زمانی که نیاز به اطلاعات فیلدی از جدولی دیگر بود باید به صورت مستقیم به آن مراجعه کرده و آن مقدار را دریافت کنی.
      به صورت خلاصه برنامه نویس در حین کار با ORM های پیشرفته نیازی نیست Join بنویسد. تنها باید ORM را طوری تنظیم کند که آیا اینکار را حتما خودش در پشت صحنه انجام دهد (واکشی حریصانه)، یا اینکه خیر، به هیچ عنوان SQL های تولیدی در پشت صحنه نباید حاوی Join باشند (lazy loading).

      در مثال فوق به صورت خودکار دو کوئری به بانک اطلاعاتی ارسال می‌گردد:

      SELECT [Limit1].[DepartmentId] AS [DepartmentId],
      [Limit1].[Name] AS [Name]
      FROM (SELECT TOP (2) [Extent1].[DepartmentId] AS [DepartmentId],
      [Extent1].[Name] AS [Name]
      FROM [dbo].[Departments] AS [Extent1]
      WHERE [Extent1].[DepartmentId] = 1 /* @p0 */) AS [Limit1]


      SELECT [Extent1].[EmployeeId] AS [EmployeeId],
      [Extent1].[FirstName] AS [FirstName],
      [Extent1].[LastName] AS [LastName],
      [Extent1].[Department_DepartmentId] AS [Department_DepartmentId]
      FROM [dbo].[Employees] AS [Extent1]
      WHERE ([Extent1].[Department_DepartmentId] IS NOT NULL)
      AND ([Extent1].[Department_DepartmentId] = 1 /* @EntityKeyValue1 */)

      یکبار زمانیکه قرار است اطلاعات دپارتمان‌ یک (db.Departments.Find) دریافت شود. تا این لحظه خبری از جدول Employees نیست. چون lazy loading فعال است و فقط اطلاعاتی را که نیاز داشته‌ایم فراهم کرده است.
      زمانیکه برنامه به حلقه می‌رسد، نیاز است اطلاعات dept1.Employees را دریافت کند. در اینجا است که کوئری دوم، به بانک اطلاعاتی صادر خواهد شد (بارگذاری در صورت نیاز).


      ج) بررسی Eager Loading یا واکشی حریصانه

      حالت lazy loading بسیار جذاب به نظر می‌رسد؛ برای مثال می‌توان خواص حجیم یک جدول را به جدول مرتبط دیگری منتقل کرد. مثلا فیلد‌های متنی طولانی یا اطلاعات باینری فایل‌های ذخیره شده، تصاویر و امثال آن. به این ترتیب تا زمانیکه نیازی به اینگونه اطلاعات نباشد، lazy loading از بارگذاری آن‌ها جلوگیری کرده و سبب افزایش کارآیی برنامه می‌شود.
      اما ... همین lazy loading در صورت استفاده نا آگاهانه می‌تواند سرور بانک اطلاعاتی را در یک برنامه چندکاربره از پا درآورد! نیازی هم نیست تا شخصی به سایت شما حمله کند. مهاجم اصلی همان برنامه نویس کم اطلاع است!
      اینبار مثال زیر را درنظر بگیرید که بجای دریافت اطلاعات یک شخص، مثلا قصد داریم، اطلاعات کلیه دپارتمان‌ها را توسط یک Grid نمایش دهیم (فرقی نمی‌کند برنامه وب یا ویندوز باشد؛ اصول یکی است):

      using (var db = new Sample06Context())
      {
      foreach (var dept in db.Departments)
      {
      Console.WriteLine(dept.Name);
      foreach (var item in dept.Employees)
      {
      Console.WriteLine(item.FirstName);
      }
      }
      }
      یک نکته: اگر سعی کنیم کد فوق را اجرا کنیم به خطای زیر برخواهیم خورد:

      There is already an open DataReader associated with this Command which must be closed first

      برای رفع این مشکل نیاز است گزینه MultipleActiveResultSets=True را به کانکشن استرینگ اضافه کرد:

      <connectionStrings>
      <clear/>
      <add
      name="Sample06Context"
      connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true;MultipleActiveResultSets=True;"
      providerName="System.Data.SqlClient"
      />
      </connectionStrings>

      سؤال: به نظر شما در دو حلقه تو در توی فوق چندبار رفت و برگشت به بانک اطلاعاتی صورت می‌گیرد؟ با توجه به اینکه در متد Seed ذکر شده در ابتدای مطلب، تعداد رکوردها مشخص است.
      پاسخ: 7 بار!


      و اینجا است که عنوان شد استفاده از EF Profiler در حین توسعه برنامه‌های مبتنی بر ORM «الزامی» است! اگر از این نکته اطلاعی نداشتید، بهتر است یکبار تمام صفحات گزارش‌گیری برنامه‌های خود را که حاوی یک Grid هستند، توسط EF Profiler بررسی کنید. اگر در این برنامه پیغام خطای n+1 select را دریافت کردید، یعنی در حال استفاده ناصحیح از امکانات lazy loading می‌باشید.

      آیا می‌توان این وضعیت را بهبود بخشید؟ زمانیکه کار ما گزارشگیری از اطلاعات با تعداد رکوردهای بالا است، استفاده ناصحیح از ویژگی Lazy loading می‌تواند به شدت کارآیی بانک اطلاعاتی را پایین بیاورد. برای حل این مساله در زمان‌های قدیم (!) بین جداول join می‌نوشتند؛ الان چطور؟
      در EF متدی به نام Include جهت Eager loading اطلاعات موجودیت‌های مرتبط به هم درنظر گرفته شده است که در پشت صحنه همینکار را انجام می‌دهد:

      using (var db = new Sample06Context())
      {
      foreach (var dept in db.Departments.Include(x => x.Employees))
      {
      Console.WriteLine(dept.Name);
      foreach (var item in dept.Employees)
      {
      Console.WriteLine(item.FirstName);
      }
      }
      }

      همانطور که ملاحظه می‌کنید اینبار به کمک متد Include، نسبت به واکشی حریصانه Employees اقدام کرده‌ایم. اکنون اگر برنامه را اجرا کنیم، فقط یک رفت و برگشت به بانک اطلاعاتی انجام خواهد شد و کار Join نویسی به صورت خودکار توسط EF مدیریت می‌گردد:

      SELECT [Project1].[DepartmentId]            AS [DepartmentId],
      [Project1].[Name] AS [Name],
      [Project1].[C1] AS [C1],
      [Project1].[EmployeeId] AS [EmployeeId],
      [Project1].[FirstName] AS [FirstName],
      [Project1].[LastName] AS [LastName],
      [Project1].[Department_DepartmentId] AS [Department_DepartmentId]
      FROM (SELECT [Extent1].[DepartmentId] AS [DepartmentId],
      [Extent1].[Name] AS [Name],
      [Extent2].[EmployeeId] AS [EmployeeId],
      [Extent2].[FirstName] AS [FirstName],
      [Extent2].[LastName] AS [LastName],
      [Extent2].[Department_DepartmentId] AS [Department_DepartmentId],
      CASE
      WHEN ([Extent2].[EmployeeId] IS NULL) THEN CAST(NULL AS int)
      ELSE 1
      END AS [C1]
      FROM [dbo].[Departments] AS [Extent1]
      LEFT OUTER JOIN [dbo].[Employees] AS [Extent2]
      ON [Extent1].[DepartmentId] = [Extent2].[Department_DepartmentId]) AS [Project1]
      ORDER BY [Project1].[DepartmentId] ASC,
      [Project1].[C1] ASC


      متد Include در نگارش‌های اخیر EF پیشرفت کرده است و همانند مثال فوق، امکان کار با lambda expressions را جهت تعریف خواص مورد نظر به صورت strongly typed ارائه می‌دهد. در نگارش‌های قبلی این متد، تنها امکان استفاده از رشته‌ها برای معرفی خواص وجود داشت.
      همچنین توسط متد Include امکان eager loading چندین سطح با هم نیز وجود دارد؛ مثلا x.Employees.Kids و همانند آن.


      چند نکته در مورد نحوه خاموش کردن Lazy loading

      امکان خاموش کردن Lazy loading در تمام کلاس‌های برنامه با تنظیم خاصیت Configuration.LazyLoadingEnabled کلاس Context برنامه به نحو زیر میسر است:

      public class Sample06Context : DbContext
      {
      public Sample06Context()
      {
      this.Configuration.LazyLoadingEnabled = false;
      }

      یا اگر تنها در مورد یک کلاس نیاز است این خاموش سازی صورت گیرد، کلمه کلیدی virtual را حذف کنید. برای مثال با نوشتن public ICollection<Employee> Employees بجای public virtual ICollection<Employee> Employees در اولین بار وهله سازی کلاس دپارتمان، لیست کارمندان آن به نال تنظیم می‌شود. البته در این حالت null object pattern را نیز فراموش نکنید (وهله سازی پیش فرض Employees در سازنده کلاس):

      public class Department
      {
      public int DepartmentId { get; set; }
      public string Name { get; set; }

      public ICollection<Employee> Employees { get; set; }
      public Department()
      {
      Employees = new HashSet<Employee>();
      }
      }

      به این ترتیب به خطای null reference object بر نخواهیم خورد. همچنین وهله سازی، با مقدار دهی لیست دریافتی از بانک اطلاعاتی متفاوت است. در اینجا نیز باید از متد Include استفاده کرد.

      بنابراین در صورت خاموش کردن lazy loading، حتما نیاز است از متد Include استفاده شود. اگرlazy loading فعال است، جهت تبدیل آن به eager loading از متد Include استفاده کنید (اما اجباری نیست).
      اشتراک‌ها
      لیست استان ها / شهرستان ها و شهر های ایران به همراه مختصات سال 97

      منابع مختلفی در  اینترنت موجود هست برای دریافت دیتای مرتبط به تقسیمات کشوری ولی خیلی از موارد یا تقسیم بندی کاملی ندارند یا قدیمی هستند یا فرمت مناسبی ندارند.

      برای یکی از نرم افزارها نیاز بود  لیست به روز (سال 96-97) شهر‌های ایران رو به تفکیک استان/شهرستان/شهر به همراه مختصات آنها برای نمایش روی نقشه به صورت اطلاعات پایه در نرم افزار داشته باشیم... این شد که این فایل رو با توجه به دیتای سایت آمار کشور و چند سایت دیگر جمع آوری کردیم. 

      در نهایت این لیست رو به فرمت json به اشتراک گذاشتم تا دوستان هم اگر نیازی داشتند استفاده کنند.

      لیست استان ها / شهرستان ها و شهر های ایران به همراه مختصات سال 97
      نظرات مطالب
      تهیه قالب برای ارسال ایمیل‌ها در ASP.NET Core توسط Razor Viewها
      درود
      آیا امکان دارد کد داخل فایل view را بصورت string (بعنوان مثال از بانک اطلاعاتی یا ساخته شده بصورت دستی ) مانند همین کد رندر کنیم .
      مثال :
      کد html ای بصورت string داریم که در بانک اطلاعاتی ذخیره شده است :
      @model string
      <div>@Model</div>
      <div><CutumTagHelper asp-id="1000" /></div>
      حال در قسمتی از View اصلی میخواهیم این مقدار string خوانده شده از بانک را ابتدا بصورت یک view مجزا رندر و نتیجه آن را بصورت Html نمایش دهیم :
      بجای
      ...
      
      @Html.Raw(Model.HtmlData)
      
      ....
      چنین کدی داشته باشیم :

      ...
      
      @Html.SomeNameLikeRenderedRaw(Model.HtmlData,Model.SomeDataAsDynamicViewModel)
      
      ....

      نظرات مطالب
      ارتقاء به ASP.NET Core 1.0 - قسمت 19 - بومی سازی
      بعد از تلاش بسیار متوجه شدم، با اینکه پیام خطا ساخت generator در ویژوال استودیو نمایش داده می‌شود، ولی عملا به درستی ساخته می‌شود.

      مشکل از معرفی baseName  بود که باید به جای

                  _stringLocalizer = stringLocalizerFactory.Create(
                       baseName: "Controllers.LoginController",
                       location: "Zagros.ExternalResources");
                  _htmlLocalizer = htmlLocalizerFactory.Create(
                      baseName: "Controllers.LoginController",
                      location: "Zagros.ExternalResources");

      به دلیل اینکه در مسیر اصلی نیستیم، باید به این صورت معرفی شود (همراه مسیر فایل):

                  _stringLocalizer = stringLocalizerFactory.Create(
                       baseName: "Areas.Identity.Controllers.LoginController",
                       location: "Zagros.ExternalResources");
                  _htmlLocalizer = htmlLocalizerFactory.Create(
                      baseName: "Areas.Identity.Controllers.LoginController",
                      location: "Zagros.ExternalResources");


      نظرات مطالب
      ایجاد نصاب یک قالب پروژه جدید چند پروژه‌ای در ویژوال استودیو
      سلام؛ در بخش آخر که Visual Studio Content Installer  می باشد طبق لینک  ارجاعی شما برای vs 2013 پشتیبانی ندارد در قسمت نسخه‌های پشتیبانی 2005و2008و2010و2012  است ولی 2013 نیست و در صورت اجرای فایل .vsi  با خطای زیر مواجه می‌شویم :
      Installation stoped becase the directory for projectType value did not exsit .. the projectType is invalid for your installation of Visual Studio 
      در صورت حذف ProjectType/Attribute  نصب انجام می‌شود ولی در لیست پروژه‌ها نمایش داده نمی‌شود.
      با تشکر از شما
      نظرات مطالب
      ارسال ویدیو بصورت Async توسط Web Api
      سلام
      امروز این مطلب رو دیدم و چند روز پیش خودم انجامش داده بودم. نکته‌ی عجیب اینه که وقتی از این حالت برای پخش ویدئو استفاده می‌کنیم، پلیر میزان فریم‌های بافر شده از ویدیو را نمایش نمیده، در واقع کاربر متوجه نمیشه که تا کجای فیلم از سرور دانلود شده (در صورتی که در حالت پخش مستقیم ویدیو از لینک مستقیم اینگونه نیست).

      ممنون میشم اگر به این سه سوال پاسخ بدین :
      1- مزیت این روش نسبت به روشی که از لینک مستقیم فایل ویدیو استفاد می‌کنیم چیه ؟
      2- آیا استفاده از این روش باری بر روی پردازنده، رم و... سرور اضافه می‌کنه ؟
      3- برای پخش ویدیو از این روش استفاده کنیم بهتره یا از لینک مستقیم ؟

      با تشکر
      نظرات مطالب
      ASP.NET MVC #17
      خلاصه موارد ممکن:
      - در یک صفحه چندبار از Html.AntiForgeryToken استفاده شده‌است. این مورد کوکی آنتی‌فورجری را تخریب می‌کند و نهایتا اطلاعات آن قابل رمزگشایی و مقایسه در سمت سرور نخواهد بود.
      - تنظیم AntiForgeryConfig.SuppressIdentityHeuristicChecks = true را به فایل global.asax.cs نیز اضافه کنید. آنتی‌فورجری توکن وضعیت کاربر لاگین شده به سیستم را نیز نگهداری و رمزنگاری می‌کند. در این حالت اگر در یک برگه‌ی دیگر لاگ آف کنید و در برگه‌ی قبلی سعی در ارسال فرم، آنتی‌فورجری توکن یک پیام خطا را نمایش می‌دهد. با تنظیم SuppressIdentityHeuristicChecks = true این بررسی وضعیت لاگین شخص حذف خواهد شد.
      - حالت تنظیم machine key یاد شده، در یک web farm با چندین سرور ممکن است رخ دهد. اگر چنین حالتی را ندارید، تنظیمی را تغییر ندهید.