اشتراک‌ها
تعریف Business Intelligence
Business intelligence (BI) is a technology-driven process for analyzing data and presenting actionable information to help corporate executives, business managers and other end users make more informed business decisions
تعریف Business Intelligence
مطالب
آموزش BrightStarDb (قسمت اول)
در طی این پست ها با مفاهیم NoSql آشنا شدید. همچنین در این دوره مفاهیم و مبانی RavenDb (یکی از بی نقص‌ترین دیتابیس‌های NoSql) بررسی شد. اما قرار است در طی چند پست با یکی دیگر از انواع دیتابیس‌های NoSql  طراحی شده برای دات نت به نام  BrightStarDb یا به اختصار  B*Db آشنا شویم.

*در دنیای NoSql پیاده سازی‌های متفاوتی از دیتابیس‌ها انجام شده است و هر دیتابیس، ویژگی‌ها و مزایا و معایب خاص خودش را دارد. باید قبول کرد که همیشه و همه جا نمی‌توان از یک پایگاه داده NoSql مشخص استفاده نماییم (دلایلی نظیر محدودیت‌های License، هزینه پیاده سازی و...). در نتیجه بررسی یک دیتابیس دیگر با شرایط و توانمندی‌های خاص آن خالی از سود نیست.
از ویژگی مهم این دیتابیس می‌توان به عناوین زیر اشاره کرد.
» این دیتاییس در گروه Graph databases‌ها قرار دارد و از  زبان SPARQL (بخوانید Sparkle) برای  کوئری گرفتن و کار با داده‌ها بهره می‌برد؛
» متن باز و رایگان است
» پشتیبانی از دات نت چهار به بعد؛
» قابل استفاده در Mobile Application - Windows phone 7 , 8؛
» بدون شما (Schema Less) و با قابیلت ذخیره سازی triple و به فرمت RDF
» پشتیبانی از Linq و  OData؛
» پشتیبانی از تراکنش‌ها ؛
» پیاده سازی مدل برنامه به صورت Code First؛
» سرعت بالا جهت ذخیره سازی و لود اطلاعات؛
» نیاز به پیکربندی‌های خاص جهت پیاده سازی ندارد؛
» ارائه افزونه رایگان Polaris جهت کوئری گفتن و نمایش Visual داده ها.
و غیره که در ادامه با آن‌ها آشنا خواهید شد.

در B*Db دو روش برای ذخیره سازی اطلاعات وجود دارد:
» Append Only : در این روش تمامی تغییرات (عملیات نوشتن) در انتهای فایل index اضافه خواهد شد. این روش مزایای زیر را به دنبال خواهد داشت:
  • عملیات نوشتن هیچگاه عملیات خواندن اطلاعات را block نمی‌کند. در نتیجه هر تعداد عملیات خواندن فایل (منظور اجرای کوئری‌های SPQRL است) می‌تواند به صورت موازی بر روی Store‌ها اجرا شود.
  • به دلیل اینکه عمل ویرایش واقعی هیچ گاه انجام نمی‌شود (داده‌ها فقط اضافه خواهند شد) همیشه می‌توانید وضعیت Store را به حالت‌های قبلی بازگردانید.
  • عملیات نوشتن اطلاعات بسیار سریع خواهد بود.
از معایب این روش این است که حجم Store‌ها فقط با افزایش داده‌ها زیاد نمی‌شود، بلکه با هر عملیات ویرایش نیز حجم فایل‌های Store افزایش پیاده خواهد کرد. در نتیجه از این روش فقط زمانی که از نظر فضای هارد دیسک محدودیت ندارید استفاده کنید(روش پیش فرض در B*Db نیز همین حالت است)

» Rewritable : در این روش در هنگام اجرای عملیات نوشتن، ابتدا یک کپی از اطلاعات گرفته میشود. سپس داده‌های مورد نظر به کپی گرفته شده اعمال می‌شوند. تا زمانیکه عملیات نوشتن اطلاعات به پایان نرسد، هر گونه دسترسی به اطلاعات جهت عملیات Read بر روی داده اصلی اجرا می‌شود. با استفاده از این روش عملیات Read و Write هیچ گونه تداخلی با هم نخواهند داشت. (چیزی شبیه به ^)

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

همان طور که پیشتر گفته شد B*Db  برای ذخیره سازی اطلاعات از سند RDF بهره می‌برد. البته با RDF Syntax‌های متفاوت :

هم چنین در B*Db چهار روش برای دست یابی با داده‌ها (پیاده سازی عملیات CRUD) وجود دارد از قبیل:
» B*Db EntityFramewok
» Data Object Layer
» RDF Client Api
» Dynamic API
که هر کدام در طی پست‌های متفاوت بررسی خواهد شد.

بررسی یک مثال با روش B*Db EntityFramework

برای شروع ابتدا یک پروژه جدید از نوع Console Application ایجاد کنید. سپس از طریق Nuget اقدام به نصب Package  زیر نمایید:
pm> install-Package BirghtStarDb
پکیج بالا تمام کتابخانه‌های لازم جهت کار با B*Db را شامل می‌شود. اگر قصد ندارید از افزونه‌های مربوط به EntityFramework و Code First استفاده نمایید می‌توانید Package زیر را نصب نمایید:
PM> Install-Package BirghtStarDbLibs
این پکیج فقط شامل کتابخانه‌های لازم جهت کار با استفاده از SPRQL است.
بعد از نصب پکیج‌های بالا یک فایل Text Template با نام MyEntityContext.tt  نیز به پروژه افزوده خواهد شد. این فایل جهت تولید خودکار مدل‌های برنامه استفاده می‌شود. اما برای این کار لازم است به ازای هر مدل ابتدا یک اینترفیس ایجاد نمایید. برای مثال:
 [Entity]
    public interface IBook
    {
        public int Code { get; set; }
        public string Title { get; set; }
    }
نکته:
» نیاز به ایجاد یک خاصیت به عنوان Id وجود ندارد. به صورت پیش فرض خاصیت Id با نوع string برای هر مدل پیاده سازی می‌شود. اما اگر قصد دارید این نام خاصیت را تغییر دهید می‌توانید به صورت زیر عمل کنید:
[Entity]
    public interface IBook
    {
        [Identifier]
        public string MyId { get;  }
        public int Code { get; set; }   
        public string Title { get; set; }
    }
در مثال بالا خاصیت MyId به جای خاصیت Id در نظر گرفته می‌شود. مزین شدن با Identifier  و همچنین نداشتن متد set را فراموش نکنید. بعد از ایجاد اینترفیس مورد نظر و اجرای Run Custom Tool بر روی فایل Text Template.tt کلاسی به نام Book به صورت زیر ساخته می‌شود:

استفاده از اینترفیس برای ساخت مدل انعطاف پذیری بالایی را در اختیار ما قرار می‌دهد که بعدا مفصل بحث خواهد شد. برای عملیات درج داده می‌توان به صورت زیر عمل کنید:

 MyEntityContext context = new MyEntityContext("type=embedded;storesdirectory=c:\brightstar;storename=test");
            var book = context.Books.Create();
            book.Code = 1;
            book.Title = "Test";

            context.Books.Add(book);

            context.SaveChanges();
با یک نگاه می‌توان به شباهت مدل پیاده سازی شده بالا به EntityFramework پی برد. اما نکته مهم در مثال بالا ConnectionString پاس داده شده به Context پروژه است. در B*Db چهار روش برای دستیابی به اطلاعات ذخیره شده وجود دارد:
»embedded : در این حالت از طریق آدرس فیزیکی فایل مورد نظر می‌توان یک Connection ایجاد کرد.
»rest : یا استفاده از HTTP یا HTTPS می‌توان به سرویس B*Db دسترسی داشت.
»dotNetRdf : برای ارتباط با یک Store دیگر با استفاده از اتصال دهنده‌های DotNetRDf.
»sparql : اتصال به منبع داده ای دیگر با استفاده از پروتکل SPARQL
در هنگام ایجاد اتصال باید نوع مورد نظر را از حتما تعیین نمایید. با استفاده از storedirctory مکان فیزیکی فایل تعیین خواهد شد.
مطالب
انقیاد داده‌ها در WPF (بخش دوم)
در ادامه‌ی بخش اول از سری انقیاد داده‌ها در WPF، نحوه‌ی انقیاد داده‌ها در لیست را بررسی می‌کنیم.
• One Way Binding بخش اول
• INPC  بخش اول
• Tow Way Binding بخش اول
• List Binding  بخش دوم
• Element Binding بخش دوم
• Data Conversion بخش دوم

انقیاد در لیست List Binding
در ابتدا متدی با نام GetEmployees را با ساختار زیر، به کلاس Employee ایجاد شده‌ی در بخش اول این سری آموزشی، اضافه می‌کنیم: 
public static ObservableCollection<Employee> GetEmployees()
        {
            var employees = new ObservableCollection<Employee>();
            employees.Add(new Employee() { Name = "Mahdi", Title = "Manager" });
            employees.Add(new Employee() { Name = "Nima", Title = "Teacher" });
            employees.Add(new Employee() { Name = "Rahim", Title = "Assistant" });
            employees.Add(new Employee() { Name = "Saeed", Title = "Administrator" });
            return employees;
        }
در کد بالا از نوع داده‌ی جنریک ObservableCollection برای برگردان لیست کارمندان استفاده کردیم.
توجه ObservableCollection در فضای نام System.Collections.ObjectModel قرار دارد.

ObservableCollection  چیست؟
یک مجموعه‌ی پویا (Dynamic Collection) است که بر اثر عملیات‌هایی همچون به‌روز رسانی، حذف و ایجاد اشیاء در آن، یک اعلان صادر می‌شود و View از این اعلان مطلع می‌شود؛ شبیه به کاری که INotifyPropertyChanged انجام می‌دهد. 
در اینجا کلاس Employee با پیاده سازی اینترفیس INPC (معرفی شده در قسمت قبل) به یک کلاس Observable تبدیل شده است. بدین معنی که هر تغییری در وضعیت خصوصیات خود را اعلان می‌کند. با قرار دادن این شیء Observable در یک مجموعه که خود نیز از نوع Observable می‌باشد، از این پس هر تغییری در مجموعه نیز مستقیما View ما را تحت تاثیر قرار می‌دهد.
برای درک بهتر این موضوع در بخش markup، یک Combobox را قرار می‌دهیم:
<ComboBox Name="President" ItemsSource="{Binding}" FontSize="30"
                  Height="50"
                  Width="550">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock Text="{Binding Title}" Margin="5,0,0,0"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
 </ComboBox>
در کد xaml بالا چند نکته اهمیت دارد:
• در Tag مربوط به تعریف Combobox از طریق خصوصیت ItemSource اعلام کردیم که عملیات DataBinding اتفاق خواهد افتاد. پس Combobox ما به منبع داده‌ی پیش فرض در WPF که همان DataContext است مرتبط می‌شود. برای پر کردن منبع داده نیز همانند بخش قبل از طریق سازنده اقدام می‌کنیم.
 private ObservableCollection<Employee> employees;
        public MainWindow()
        {
            InitializeComponent();
            employees = Employee.GetEmployees();
            DataContext = employees;
        }
• برای نمایش عناصر در ComboBox، کار کمی متفاوت‌تر از عملیات انقیاد در یک TextBlock است. در کنترل‌هایی که قرار است لیستی از داده‌ها را پس از عملیات انقیاد نمایش دهند، می‌بایستی قالب نمایش داده‌ها مشخص شود.
حال باید نحوه‌ی نمایش و اعضایی از مجموعه که قرار است، در کنترل به نمایش در آیند مشخص شوند. در اینجا از Combobox.ItemTemplate برای مشخص کردن قالب و همچنین از DataTemplate، برای مشخص کردن داده‌های منتخب ما از مجموعه، استفاده شده است.
بعد از اجرای برنامه اطلاعات نام و عنوان، در Combobox نمایش داده می‌شوند. برای تست ویژگی Observable بودن مجموعه هم کافی است یک دکمه را بر روی صفحه قرار دهید و یک آیتم جدید را به لیست employees واقع در بخش CodeBehind اضافه کنید. مشاهده خواهید کرد که View ما با تغییر منبع داده، بطور اتوماتیک به‌روز می‌شود.

 انقیاد عناصر Element Binding 
در این بخش قصد داریم از یک کنترل به‌عنوان منبع داده استفاده کنیم. کنترل منتخب ما در این مثال یک Slider می‌باشد. در بخش markup کد زیر را اضافه می‌کنیم:
<Grid>
        <StackPanel Orientation="Horizontal">
            <Slider Name="mySlider" Minimum="0" Maximum="100"
                    Width="300"/>
            <TextBlock Margin="5" Text="{Binding Value,ElementName=mySlider}"/>
        </StackPanel>
    </Grid>
Value خصوصیتی است که مقدار Slider را برمی گرداند. پس از اجرای برنامه و حرکت ولوم اسلایدر مشاهده می‌کنید که مقدار جاری اسلایدر در textblock نمایش داده می‌شود.

Data Conversion 
برای درک بهتر مفهوم تبدیل داده در Data Binding با یک مثال کار را شروع می‌کنیم. به کلاس Employee موجود در بخش قبل، یک خصوصیت جدید را به نام تاریخ تولد، اضافه می‌کنیم:
public DateTime BornDate
        {
            get { return _bornDate; }
            set
            {
                _bornDate = value;
                OnPropertyChanged();
            }
        }
و متدی که لیستی از پرسنل را برای ما تولید می‌کرد، به شکل زیر بازنویسی می‌کنیم:
public static ObservableCollection<Employee> GetEmployees()
        {
            var employees = new ObservableCollection<Employee>();
            employees.Add(new Employee()
            { Name = "Mahdi", Title = "Manager", BornDate = DateTime.Parse("2008/8/8") });
            employees.Add(new Employee()
            { Name = "Nima", Title = "Teacher", BornDate = DateTime.Parse("2012/3/14") });
            employees.Add(new Employee()
            { Name = "Rahim", Title = "Assistant", BornDate = DateTime.Parse("2009/11/18") });
            employees.Add(new Employee()
            { Name = "Saeed", Title = "Administrator", BornDate = DateTime.Parse("2014/7/28") });
            return employees;
        }
حال قصد داریم اطلاعات را در یک ListBox، در بخش Markup نمایش دهیم:
<ListBox ItemsSource="{Binding}"
                 BorderThickness="1" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Width="100"/>
                        <TextBlock Text="{Binding Title}" Width="100" Margin="5,0,0,0"/>
                        <TextBlock Text="{Binding BornDate}"  Margin="5,0,0,0"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
 همانطور که قبلا اشاره شد، در نمایش لیست‌ها باید قالب و داده‌های مورد نظر برای نمایش، مشخص شوند.
پس از اجرا، خروجی برنامه به شکل زیر است:
 

 همه چیز همانطور که انتظار داشتیم به نمایش درآمد؛ اما اگر بخواهیم تاریخ میلادی، در زمان نمایش در View، بصورت شمسی نمایش داده شود، چطور؟
عملیات تبدیل داده یا همان Data Conversion در اینجا به کمک ما می‌آید.
به شکل زیر دقت کنید:

در زمانیکه در عملیات Data Binding نوع داده‌ی خصوصیت ما در Source (منبع داده) با نوع داده‌ی خصوصیت ما در target  (کنترل یا View) متفاوت است، به یک مبدل در حین Binding نیاز داریم. این کار را از طریق یک کلاس که اینترفیس IValueConvertor را پیاده سازی کرده است، انجام می‌دهیم.

  Converter به معنای مبدل است و اینجا وظیفه‌ی تبدیل مقدار گرفته شده‌ی از منبع داده را به نوع مورد نظر ما در View، دارد.
در مثال ذکر شده می‌خواهیم تاریخ میلادی موجود در منبع داده را به یک رشته (تاریخ شمسی) تبدیل کنیم و در View نمایش دهیم. (تبدیل نوع داده‌ی میلادی به رشته)
کلاسی را با نام DateConverter ایجاد می‌کنیم و اینترفیس IValueConverter را به شکل زیر پیاده سازی می‌کنیم:
public class DateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            PersianCalendar pc = new PersianCalendar();
            var persianDate = string.Format
                ($"{pc.GetYear(date)}/{pc.GetMonth(date)}/{pc.GetDayOfMonth(date)}");
            return persianDate;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
اینترفیس IValueConverter دو متد دارد:
• Convert جهت تبدیل اطلاعات از مبداء به مقصد 
• ConvertBack برای تبدیل اطلاعات از مقصد به مبداء
در متد Convert همانطور که می‌بینید مقدار تاریخ میلادی ارسال شده، به یک رشته تبدیل شده و بازگردانده می‌شود.

حال برای استفاده از این مبدل باید مراحل زیر انجام شود: 
• اضافه کردن فضای نام مبدل به بخش فضاهای نام در View؛ که در اینجا همان آدرس پروژه‌ی جاری است:
 xmlns:local="clr-namespace:DataConversion"
• ایجاد یک شیء از مبدل و اضافه کردن آن به بخش Resource‌ها در view جاری:
<Window.Resources>
        <local:DateConverter x:Key="MyConverter"/>
    </Window.Resources>
markup مربوط به Resource یک پارامتر می‌گیرد و آن هم Key  است. Key کلید آیتم موجود در دیکشنری Resource است. اینجا کلید را MyConverter تعریف کردیم تا در بخش بعدی به‌راحتی بتوانیم از طریق آن اطلاعات را از Resource بازیابی کنیم.
• ارجاع به Static Resource که در مرحله‌ی قبل مقدار دهی کردیم:
<TextBlock Text="{Binding BornDate,Converter={StaticResource MyConverter}}"  Margin="5,0,0,0"/>
پس از طی مراحل بالا، برنامه را مجددا اجرا کنید. اینبار خروجی به شکل زیر است:

مطالب
رسم نمودار توسط Kendo Chart
پیشتر مطالبی در سایت، درباره KenoUI و همچنین ویجت‌های وب آن منتشر گردید. در این مطلب نگاهی خواهیم داشت بر تعدادی از ویجت‌های Kendo UI جهت رسم نمودار. توسط Kendo UI می‌توانیم نمودار‌های زیر را ترسیم کنیم:
  • Bar and Column
  • Line and Vertical Line
  • Area and Vertical Area
  • Bullet
  • Pie and Donut
  • Scatter
  • Scatter Line
  • Bubble
  • Radar and Polar

برای رسم نمودار می‌توانیم به صورت زیر عمل کنیم:

1- ابتدا باید استایل‌های مربوط به Data Visualization را به صفحه اضافه کنیم:

<link href="Content/kendo.dataviz.min.css" rel="stylesheet" />
<link href="Content/kendo.dataviz.default.min.css" rel="stylesheet" />
2- سپس یک عنصر را بر روی صفحه جهت نمایش نمودار، تعیین می‌کنیم:
<div id="chart"></div>
برای عنصر فوق می‌توانیم درون CSS و یا به صورت inline طول و عرضی را برای چارت تعیین کنیم:
<div id="chart" style="width: 400px; height: 600px"></div>
با فراخوانی تابع KendoChart، چارت بر روی صفحه نمایش داده می‌شود:
$("#chart").kendoChart();

همانطور که مشاهده می‌کنید هیچ داده‌ایی را هنوز برای چارت تعیین نکرده‌ایم؛ در نتیجه همانند تصویر فوق یک چارت خالی بر روی صفحه نمایش داده می‌شود. برای چارت فوق می‌توانیم خواصی از قبیل عنوان و ... را تعیین کنیم:
$("#chart").kendoChart({
    title: {
         text: "چارت آزمایشی"
    }
});

نمایش داده‌ها بر روی چارت:

داده‌ها را می‌توان هم به صورت local و هم به صورت remote دریافت و بر روی چارت نمایش داد. اینکار را می‌توانیم توسط قسمت series انجام دهیم:
$("#chart").kendoChart({
    title: {
         text: "عنوان چارت"
    },
    series: [
         { name: "داده‌های چارت", data: [200, 450, 300, 125] }
    ]
});
برای تعیین برچسب برای هر یک از داده‌ها نیز می‌توانیم خاصیت category axis را مقداردهی کنیم:
$("#chart").kendoChart({
                title: {
                    text: "عنوان چارت"
                },
                series: [
                     {
                         name: "داده‌های چارت",
                         data: [200, 450, 300, 125]
                     }
                ],
                categoryAxis: {
                    categories: [2000, 2001, 2002, 2003]
                }
            });

دریافت اطلاعات از سرور:
کدهای سمت سرور:
public class ProductsController : ApiController
    {
        public IEnumerable<ProductViewModel> Get()
        {
            var products = _productService.GetAllProducts();
            var query = products.GroupBy(p => p.Name).Select(p => new ProductViewModel
            {
                Value = p.Key,
                Count = p.Count()
            });
            return query;
        }
    }

    public class ProductViewModel
    {
        public string Value { get; set; }
        public int Count { get; set; }
    }

سپس برای دریافت اطلاعات از سمت سرور باید DataSource مربوط به چارت را مقداردهی کنیم:
var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    }
                },
                error: function (e) {
                    alert(e.errorThrown.stack);
                },
                pageSize: 5,
                sort: { field: "Id", dir: "desc" }
            });

            $("#chart").kendoChart({
                title: {
                    text: "عنوان چارت"
                },
                dataSource: productsDataSource,
                series: [
                    {
                        field: "Count",
                        categoryField: "Value",
                        aggregate: "sum"
                    }
                ]
            });
همانطور که مشاهده می‌کنید در این حالت باید برای سری، field و categoryField را مشخص کنیم.
موارد فوق را می‌توانیم به صورت یک افزونه نیز کپسوله کنیم.

کدهای افزونه jquery.ChartAjax:
(function($) {
    $.fn.ShowChart = function(options) {
        var defaults = {
            url: '/',
            text: 'نمودار دایره ایی',
            theme: 'blueOpal',
            font: '13px bbc-nassim-bold',
            legendPosition: 'left',
            seriesField: 'Count',
            seriesCategoryField: 'Value',
            titlePosition: 'top',
            chartWidth: 400,
            chartHeight: 400
        };
        var options = $.extend(defaults, options);
        return this.each(function() {
            var chartDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: options.url,
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    }
                },
                error: function (e) {
                    // handle error
                }
            });
            $(this).kendoChart({
                chartArea: {
                    height: options.chartHeight
                },
                theme: options.theme,
                title: {
                    text: options.text,
                    font: options.font,
                    position: options.titlePosition
                },
                legend: {
                    position: options.legendPosition,
                    labels: {
                        font: options.font
                    }
                },
                seriesDefaults: {
                    labels: {
                        visible: false,
                        format: "{0}%"
                    }
                },
                dataSource: chartDataSource,
                series: [
                    {
                        type: "pie",
                        field: options.seriesField,
                        categoryField: options.seriesCategoryField,
                        aggregate: "sum",
                    }
                ],
                tooltip: {
                    visible: true,
                    template: "${category}: ${value}",
                    font: options.font
                }
            });
            
        });
    };
})(jQuery);
برای افزونه فوق موارد زیر در نظر گرفته شده است:
chartArea : جهت تعیین طول و عرض چارت
theme : جهت تعیین قالب‌های از پیش‌تعریف شده:
  • Black
  • BlueOpal
  • Bootstrap
  • Default
  • Flat
  • HighContrast
  • Material
  • MaterialBlack
  • Metro
  • MetroBlack
  • Moonlight
  • Silver
  • Uniform

title : جهت تعیین عنوان چارت

legend : جهت تنظیم ویژگی‌های قسمت گروه‌بندی چارت

tooltip : جهت تنظیم ویژگی‌های مربوط به نمایش tooltip در هنگام hover بر روی چارت.

لازم به ذکر است در قسمت series می‌توانید نوع چارت را تعیین کنید.

نحوه استفاده از افزونه فوق:
$('#chart').ShowChart({
                        url: "/Report/ByUnit",
                        legendPosition: "bottom"
});


دریافت سورس مثال جاری (KendoChart.zip)

مطالب
طراحی شیء گرا: OO Design Heuristics - قسمت سوم
پیشنهاد می‌کنم قسمت‌های قبل را مطالعه کنید تا با اصطلاحات استفاده شده در ادامه مقالات آشنا باشید. در مقالات آتی، مباحث کمی قابل بحث‌تر خواهند بود.

Class Coupling and Cohesion

تعدادی از قواعد شهودی هم، با Coupling و Cohesion به ترتیب مابین و درون کلاس‌ها، سروکار دارند. تلاش ما در راستای افزایش Cohesion درون کلاس‌ها و سست کردن و کاهش Coupling مابین کلاس‌ها می‌باشد. این قواعد شهودی همین اهداف را در پارادایم action-oriented، در ارتباط با توابع دارند. هدف از Tight Cohesion (انسجام و چسبندگی قوی) در توابع، انسجام بالا و ارتباط نزدیک مابین کدهای موجود در تابع، می‌باشد. هدفی که Loose Coupling (اتصال سست و ضعیف، وابستگی ضعیف) در بین توابع دنبال می‌کند، اشاره دارد به اینکه اگر تابعی قصد استفاده از تابع دیگری را داشته باشد، باید وارد شدن و خروج از آن، از یک نقطه صورت گیرد. این مباحث منجربه مطرح شدن قواعد شهودی از جمله: «یک تابع باید طوری سازماندهی شود که تنها یک دستور return  داشته باشد»، در پارادایم action-oriented می‌شود.
ما در پارادایم شیء گرا، اهداف خود از Loose Coupling و Tight Cohesion را در سطح کلاس مطرح می‌کنیم. 5 شکل اصلی Coupling مابین کلاس‌ها به شرح زیر می‌باشد:

  •  Ni Coupling
  •  Export Coupling
  •  Overt Coupling
  •  Covert Coupling
  •  Surreptitious Coupling
Nil Coupling

بهترین حالتی که دو کلاس به طور مطلق هیچ وابستگی به یکدیگر ندارند. در این صورت می‌توان یکی کلاس‌ها را حذف کرد، بدون اینکه تأثیری بر روی سایر آنها داشته باشد. البته وجود برنامه‌ای کاربردی با این نوع اتصال ممکن نخواهد بود. بهترین چیزی که می‌شود با این نوع اتصال ایجاد کرد، Class Libraryای می‌باشد که شامل مجموعه ای از کلاس‌های مستقل بوده، به طوری که هیچ تأثیری بر روی یکدیگر ندارند.

Export Coupling

در این شکل از اتصال، یک کلاس به واسط عمومی کلاس دیگر وابسته می‌باشد؛ به این معنی که از عملیاتی که کلاس مورد نظر در واسط عمومی خود قرار داده است، استفاده می‌کند.

Overt Coupling

این نوع اتصال زمانی رخ می‌دهد که یک کلاس از جزئیات پیاده سازی کلاس دیگر با داشتن اجازه دسترسی از جانب آن، استفاده کند. به عنوان مثال، مکانیزم کلاس‌های friend در زبان سی پلاس پلاس، که امکان این را می‌دهد کلاس X اجازه دوستی به کلاس Y را اعطا کند و در این صورت کلاس Y می‌تواند به جزئیات پیاده سازی خصوصی کلاس X دسترسی داشته باشد.

Cover Coupling

این نوع اتصال هم به مانند Overt می‌باشد؛ با این تفاوت که هیچ اجازه دسترسی به کلاس Y داده نشده است. اگر زبانی داشته باشیم که به کلاس Y اجازه دهد خود را به عنوان دوست کلاس X معرفی کند، در این صورت نوع اتصال بین دو کلاس X و Y از نوع Covert می‌باشد. به عنوان مثال واقعی، می‌توان به استفاده از Reflection در دات نت اشاره کرد.

Surreptitious Coupling (اتصال پنهان)

آخرین نوع اتصال که بدترین حالت هم محسوب می‌شود، مربوط است به زمانیکه کلاس X به هر طریقی که شده از جزئیات داخلی کلاس Y آگاه باشد و از اعضای عمومی داده‌ای (public data member) آن کلاس استفاده کند. منظور این است که با تغییر این داده‌های کلاس متوجه میشود که بر روی عملیات b از کلاس چه تأثیری می‌گذارد و با اتکاء به این دستاورد، جزئیات داخلی خود را پیاده سازی می‌کند و یک اتصال پنهان را با کلاس Y ایجاد کرده است. در این حالت یک وابستگی قوی به صورت پنهان مابین رفتاری از کلاس Y و پیاده سازی کلاس X ایجاد شده است.

قاعده شهودی 2.7
اتصال و پیوستگی مابین کلاس‌ها باید از نوع Nil یا Export باشد؛ به این معنی که یک کلاس فقط از واسط عمومی کلاس دیگر استفاده کند یا کاری با آن نداشته باشد. (Classes should only exhibit nil or export coupling with other classes, that is, a class should only use operations in the public interface of another class or have nothing to do with that class.)
بجز این دو نوع اتصال، بقیه شکل‌های اتصال به طریقی اجازه دسترسی به جزئیات پیاده سازی کلاس‌ها را اعطا می‌کنند. در نتیجه باعث ایجاد وابستگی مابین پیاده سازی دو کلاس می‌شوند. این وابستگی ما بین پیاده سازی‌ها به محض نیاز به تغییر پیاده سازی یکی از کلاس‌ها ، باعث به وجود آمدن مشکلات نگهداری خواهند شد.
Cohesion درون کلاس‌ها سعی بر این دارد که مطمئن شود تمام اجزای یک کلاس به شدت باهم مرتبط هستند. تعدادی از قواعد شهودی نیز در ادامه بر این خصوصیت دلالت دارند.

قاعده شهودی 2.8
یک کلاس باید یک و تنها یک Key Abstraction را تسخیر نماید. (A class should capture one and only one key abstraction)
یک Key Abstraction به عنوان یک Entity در Domain Model تعریف می‌شود و اغلب در غالب اسم در اسناد و مشخصات نیازمندی‌ها ظاهر می‌شوند. هر کدام از آنها باید فقط به یک کلاس نگاشت پیدا کنند. اگر این نگاشت به بیش از یک کلاس انجام گیرد، در نتیجه احتمالا طراح هر تابع را به عنوان یک کلاس تسخیر کرده است. اگر بیش از یک Key Abstraction به یک کلاس نگاشت پیدا کرده باشد، پس احتمالا طراح یک سیستم متمرکز را طراحی کرده است. این کلاس‌ها Vague Classes نامیده می‌شوند و باید آنها در دو کلاس یا بیشتر، تسخیر شوند.

قاعده شهودی 2.9
داده و رفتار مرتبط را در یک جا (کلاس) نگه دارید. (Keep related data and behavior in one place)
در واقع هدفی که این قاعده به دنبال آن می‌باشد این است که هر دو جزء تشکیل دهنده یک Key Abstraction ، یعنی همان داده و رفتار، باید توسط فقط یک کلاس تسخیر شوند. با نقض این قاعده، توسعه دهنده باید با قرار داد (Convention) خاصی برنامه نویسی کند. 
راه شناسایی
طراح باید کلاس‌هایی را که مرتبا داده‌های مورد نیاز خود را با متدهای get از سایر کلاس‌ها دریافت می‌کنند، شناسایی کند. زیرا این نوع کلاس‌ها این قاعده شهودی را نقض کرده‌اند.

مثال واقعی
استفاده از الگوی Domain Model ارائه شده توسط اقای Martin Fowler که دقیقا اشاره به این قاعده دارد.
یا برعکس آن ضد الگوی Anemic Domain Model که ناقض این قاعده می‌باشد. 
در قسمت اول اشاره کردیم این قواعد را به راحتی می‌توان در صورت نیاز نقض کرد. بعضی از مواقع نیاز به طراحی فیزیکی است که باعث تغییر در طراحی منطقی شده و چه بسا می‌تواند باعث نقض هر کدام از این قواعد شهودی نیز شود. اگر به بخش پروژه‌های سایت رجوع کنید این نقض کاملا مشهود (DomainClasses و ServiceLayer موجود در طراحی فیزیکی آنها) می‌باشد (بیشتر از Anemic Domain Model استفاده شده است)؛ ولی نمی‌توان گفت که این کار اشتباه است.

قاعده شهودی 2.10
اطلاعات نامرتبط به هم را در کلاس‌های جدا از هم قرار دهید. ((Spin off nonrelated information into another class (i.e., noncommunicating behavior) 

هدف از این قاعده این است که اگر کلاسی داریم که یکسری از متدهایش با بخشی از داده و یکسری دیگر با بخش دیگر داده‌ها کار میکنند، در واقع شما دو Key Abstraction را به یک کلاس نگاشت کرده اید (Vague Class) و باید آنها را به کلاس‌های جدا نگاشت کنید.

شکل 2.6 A class with noncommunicating behavior

مثال واقعی

به کلاس Dictionary در تصویر زیر توجه کنید.
برای تعداد کمی داده، بهترین پیاده سازی با استفاده از List و در مقابل برای تعداد داده زیاد بهترین پیاده سازی، استفاده از HashTable می‌باشد. هر یک از این پیاده سازی‌ها، به متدهایی برای add و find کلمات نیاز دارند. طراحی سمت چپ تصویر نشان از نقض این قاعده شهودی دارد.

شکل 2.7 (Noncommunicating behavior (real-world example

در طرح سمت چپ، استفاده کننده باید بداند که چقدر داده وارد کند. از طرفی نمایش جزئیات پیاده سازی در نام کلاس هم ایده خوبی نیست (طرح سمت چپ). بهترین راه حل که در مقالات آینده به آنها خواهیم رسید، بحث استفاده از ارث بری می‌باشد. به این ترتیب، با استفاده از  یک کلاس Dictionary که نمایش جزئیات داخلی خود را مخفی کرده و در شرایط لازم نمایش جزئیات داخلی خود را تغییر دهد. منظور این است که استفاده کننده درگیر جزئیات داخلی آن نشود و این جزئیات که کدام نوع PDict یا HDict استفاده خواهد شد، از دید او مخفی باشد.

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

 در این بخش تعاریف، خصوصیات، مزایا و معایب مرتبط با این نوع سیستم‌ها بررسی می‌شوند.


تعریف سیستمهای توزیع شده

تعاریف مختلفی توسط اشخاص و گروه‌های مختلف، از سیستم‌های توزیع شده وجود دارد. در اینجا سعی شده که اکثر این تعاریف معرفی شوند؛ بگونه‌ای که با ارائه نکات کلیدی این تعاریف به درک بهتری از خصوصیات سیستم‌های توزیع شده برسیم.


تعاریف سیستم‌های توزیع شده

تعریف اول: سیستمهای توزیع  شده از مجموعه‌ای از سخت افزارها و نرم افزارها که برای رسیدن به یک هدف واحد از طریق شبکه با یکدیگر در ارتباطند تشکیل شده.
تعریف دوم: یک مجموعه از Computerهای مستقل که از نظر کاربر یک Computer و یک سیستم واحد و منسجمند.
تعریف سوم: سیستم‌های توزیع شده در واقع هنری هستند که بوسیله آنها می‌توانید مشکلاتی را که در یک کامپیوتر وجود دارند، با استفاده از چند کامپیوتر رفع کنید و معمولا به این دلیل انتخاب می‌شوند که این مشکلات توسط یک کامپیوتر رفع نمی‌شوند.
تعریف چهارم: زیر سیستمهایی که بصورت همزمان می‌توانند پردازش یک سیستم بزرگ را انجام دهند؛ البته همزمانی که در آن مانند سیستمهای Parallel از یک حافظه مشترک استفاده نشود.
تعریف پنجم: مجموعه ای از پردازشهای مرتبط به هم، بصورتی که هر پردازش از حافظه داخلی مرتبط با خودش استفاده کند و تمام این پردازش‌ها از طریق Message passing در سطح شبکه با یکدیگر در ارتباطند.
تعریف ششم: تقسیم بندی وظایف یک سیستم بزرگ به زیرسیستمهایی که در سخت افزارهای مجزا اجرا می‌شوند و کاربر هیچ تصوری از وجود این زیر سیستمها ندارد.

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


نکات کلیدی یا خصوصیات سیستمهای توزیع شده

1- داشتن یک هدف واحد در سیستم‌های توزیع شده، زیر سیستمهایی که از نظر کاربر یک سیستم واحد و متمرکزند:  این یکی از خصوصیات و نکات کلیدی سیستم‌های توزیع شده‌است که تمام اجزاء و زیرسیستم‌ها در راه رسیدن به یک هدف واحد با یکدیگر در ارتباط اند. ارتباطی که باعث همکاری آنها می‌شود و همکاری که باید بصورت کامل از دید کاربر مخفی بماند (داشتن یک هدف واحد یکی از تفاوتهای سیستم‌های توزیع شده با Cloud distributed systemها می‌باشد. چون در Cloud distributed systemها لزوما تمام اجزا برای رسیدن به یک هدف مشترک با یکدیگر کار نمی‌کنند).

2- کامپیوتر‌های مستقل، حافظه‌های داخلی جداگانه، عدم وجود حافظه مشترک:  یکی از مهمترین تفاوتهای سیستم‌های توزیع شده با سیستمهای Parallel، عدم وجود حافظه مشترک بین پردازش‌های جداگانه است. یعنی در این نوع سیستم‌ها، هر زیرسیستم در یک کامپیوتر مجزا که حافظه داخلی خودش را دارد اجرا می‌شود.

3- تقسیم بندی وظایف: که یکی از نکات کلیدی این نوع سیستمها می‌باشد. تقسیم بندی می‌تواند به هر دلیلی که شما درنظر می‌گیرید صورت بپذیرد. دلایلی برای بالا بردن کارآیی، امنیت، در دسترس بودن، یا حتی دلایل مربوط به Business سیستم شما.

4- ارتباط از طریق شبکه، ارتباطی که از طریق Message passing صورت می‌پذیرد: این خصوصیت در واقع پایه تمام تعاریف سیستم‌های توزیع شده‌است. در سیستم‌های توزیع شده همه چیز از ارتباط‌هایی که از طریق شبکه صورت می‌پذیرد، شروع می‌شود .


مزایای استفاده از سیستم‌های توزیع شده

1- کارآیی بسیار بالاتر: بدلیل همزمان اجرا شدن کارها در سخت افزارهای مختلف، کارآیی این نوع سیستم‌ها بسیار بیشتر از سیستم‌های متمرکز است.
2- قابلیت همکاری بیشتر: بدلیل اینکه این سیستمها ذاتا توزیع شده هستند، با کمترین هزینه و پیچیدگی می‌توانند با سایر سیستمها همکاری لازم را داشته باشند.
3- قابلیت در دسترس بودن و اطمینان بیشتر: در این سیستم‌ها با روشهای مختلفی مانند replication، به راحتی می‌توان این دو قابلیت را در بالاترین سطح قرار داد.
4- مقیاس پذیری: مقیاس پذیری در این سیستمها با قرار دادن کامپوننت‌هایی که قابلیت استفاده مجدد بالایی را دارند در سرور‌های جدید به راحتی و بدون از دسترس خارج شدن سیستم صورت می‌پذیرد.
5- قابلیت گسترش: برای گسترش سیستم و اضافه کردن نیازمندی‌های جدید در این سیستم‌ها، به راحتی می‌توان کامپوننت‌ها و زیرسیستمهای جدیدی را پیاده سازی کرد و بدون از دسترس خارج شدن سیستم و به گونه‌ای که به راحتی با سایر قسمت‌های موجود در ارتباط باشند، آنها را به سیستم اضافه کرد.
6- بهره وری بالاتر و زمان توسعه کمتر: بدلیل تقسیم بندی قسمتهای بزرگ به قسمتهای کوچکتر، تیمهای مختلف می‌توانند بصورت همزمان قسمتهای کوچک را توسعه دهند.
7- قابلیت استفاده مجدد بسیار بالا: در این نوع سیستم‌ها به راحتی می‌توانید از یک زیرسیستم یا کامپوننت خاص که یکبار پیاده سازی شده و در سخت افزار جداگانه‌ای اجرا شده، در تمام Application‌ها استفاده کنید.
8- کاهش هزینه: در مواردی مانند قابلیت استفاده‌ی مجدد بالا و توسعه پذیری سیستم، می‌توانند باعث کاهش هزینه‌ها شوند (در صورت انتخاب نادرست این نوع سیستم، این مزیت می‌تواند تبدیل به یکی از معایب سیستم شود).
9- امنیت: بدلیل اینکه هر زیرسیستم در یک سخت افزار جداگانه اجرا می‌شود که مکان آن از قبل مشخص نیست و همچنین تقسیم بندی قسمت‌هایی که نیاز به امنیت بالایی دارند، می‌تواند بر اساس نیاز و در سخت افزارهایی که حتی به اینترنت هم متصل نیستند، صورت بپذیرد. این نوع سیستم می‌تواند از امنیت بالایی برخوردار باشد (البته در صورت طراحی نادرست، امنیت می‌تواند بعنوان یکی از معایب این نوع سیستمها نیز مطرح شود).


معایب  استفاده از سیستم‌های توزیع شده

1- پیچیدگی در انتخاب، طراحی و پیاده سازی سیستمهای توزیع شده: یکی از اصلی‌ترین معایب سیستم‌های توزیع شده، پیچیدگی‌هایی است که در انتخاب، طراحی و پیاده سازی آنها وجود دارد. به دلیل پیچیدگی‌هایی که در هریک از این قسمت‌ها وجود دارد، در صورت اتخاذ تصمیمات نادرست، در هر یک از این قسمتها اکثر مزایای آنها می‌توانند تبدیل به معایب این نوع سیستم‌ها شوند.

2- بالا رفتن زمان طراحی و پیاده سازی: بدلیل بوجود آمدن مفاهیم جدید و پیچیدگی که در این نوع سیستم وجود دارد و همچنین بدلیل کم بودن نیروی متخصص در این نوع سیستم، زمان توسعه آنها می‌تواند بیشتر از سیستم‌های متمرکز باشد.

3- هزینه طراحی و پیاده سازی بیشتر: دلایل 1 و 2 می‌توانند باعث بالا رفتن هزینه‌های طراحی و پیاده سازی این نوع سیستم‌ها شوند.

4- هزینه‌های بیشتر مرتبط با شبکه: در این نوع سیستم‌ها بدلیل استفاده بیشتر از منابع، مانند سخت افزار‌ها و ابزارهای مرتبط با شبکه، هزینه‌های مرتبط با استفاده از منابع، مانند برق و شبکه بیشتر از سیستم‌های متمرکز است.

5- کاهش امنیت: توزیع نادرست منابع سیستم در سخت افزارهای متفاوت و مدیریت نادرست این منابع باعث کاهش امنیت این نوع سیستم‌ها می‌شود.

6- مدیریت دشوارتر: بدلیل اینکه سیستمهای توزیع شده از زیرسیستم‌های زیادی تشکیل شده و هر یک از این زیر سیستمها در سخت افزارهای متفاوتی اجرا شده‌اند، مدیریت و سازماندهی این نوع سیستم‌ها دشوار‌تر از سیستم‌های متمرکز است.

بدلیل اینکه همیشه قسمتهای مرتبط با پیاده سازی این نوع سیستم‌ها از تعاریف آنها جذابتر است، سعی کرده‌ام در این بخش و بخش بعد که اهداف و اصطلاحات مرتبط با سیستمهای توزیع شده را بررسی میکند، کمتر وارد جزئیات مفاهیم شوم. در صورت نیاز به توضیح بیشتر در مورد قسمت خاصی از این مفاهیم، در قسمت نظرات آن‌ها را مطرح کنید.
اشتراک‌ها
ذخیره ASP.NET Session در پایگاه داده Redis NoSQL
Redis یک پایگاه داده سرعت بالای NoSQL است که داده‌ها را بصورت key-value  ذخیره می‌کنید. کلاس RedisSessionStateProvider امکان ذخیره Session‌های  ASP.NET را در پایگاه داده Redis به جای حافظه فراهم می‌کنند.
ذخیره ASP.NET Session در پایگاه داده Redis NoSQL
مطالب
تقسیم جدول در Entity Framework Code First
سناریو هایی هستند که در آن ها، تعداد ستون‌های یک جدول، بیش از اندازه زیاد می‌شوند و یا آن جدول حاوی فیلدهایی هست که منابع زیادی مصرف می‌کنند، به مانند فیلدهای متنی طولانی یا عکس. معمولا متوجه می‌شویم که در اکثر مواقع، به هنگام واکشی اطلاعات آن جدول، احتیاجی به داده‌های آن فیلد‌ها نداریم و با واکشی بی مورد آن ها، سربار اضافه ای به سیستم تحمیل می‌کنیم، چرا که این داده‌ها ، منابع حافظه ای ما را به هدر می‌دهند.
برای مثال، جدول Post مدل بلاگ را در نظر بگیرید که در آن دو فیلد Body و Image تعریف شده اند.فیلد Body از نوع nvarchar max و فیلد Image از نوع varbinary max است و بدیهی است که این دو داده، به هنگام واکشی حافظه‌ی زیادی مصرف می‌کنند.موارد بسیاری وجود دارند که ما به اطلاعات این دو فیلد احتیاجی نداریم از جمله: نمایش پست‌های پر بازدید، پسته هایی که اخیرا ارسال شده اند و اصولا ما فقط به چند فیلد جدول Post احتیاج داریم  و نه همه‌ی آن ها.
namespace SplittingTableSample.DomainClasses
{
    public class Post
    {
        public virtual int Id { get; set; }
        public virtual string Title { get; set; }
        public virtual DateTime CreatedDate { get; set; }
        public virtual string  Body { get; set; }
        public virtual byte[] Image { get; set; }
    }
}

دلیل اینکه در مدل فوق، تمامی خواص به صورت virtual تعریف شده اند، فعال سازی پروکسی‌های ردیابی تغییر است. اگر دستور زیر را برای واکشی اطلاعات post با id=1 انجام دهیم: 

            using (var context = new MyDbContext())
            {
                var post = context.Posts.Find(1);
            }
خروجی زیر را در SQL Server Profiler مشاهده خواهید کرد:

exec sp_executesql N'SELECT TOP (2) 
[Extent1].[Id] AS [Id], 
[Extent1].[Title] AS [Title], 
[Extent1].[CreatedDate] AS [CreatedDate], 
[Extent1].[Body] AS [Body], 
[Extent1].[Image] AS [Image]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[Id] = @p0',N'@p0 int',@p0=1

همان طور که مشاهده می‌کنید، با اجرای دستور فوق تمامی فیلد‌های جدول Posts که id آن‌ها برابر 1 بود واکشی شدند، ولی من تنها به فیلدهای Id و Title آن احتیاج داشتم. خب شاید بگویید که من به سادگی با projection، این مشکل را حل می‌کنم و تنها از فیلد هایی که به آن‌ها احتیاج دارم، کوئری می‌گیرم. همه‌ی این‌ها درست، اما projection هم مشکلات خود را دارد،به صورت پیش فرض، نوع بدون نام بر می‌گرداند و اگر بخواهیم این گونه نباشد، باید مقادیر آن را به یک کلاس(مثلا viewmodel) نگاشت کنیم و کلی مشکل دیگر.
راه حل دیگری که برای حل این مشکل ارائه می‌شود و برای نرمال سازی جداول نیز کاربرد دارد این است که، جدول Posts را به دو جدول مجزا که با یکدیگر رابطه‌ی یک به یک دارند تقسیم کنیم، فیلد‌های پر مصرف را در یک جدول و فیلدهای حجیم و کم مصرف را در جدول دیگری تعریف کنیم و سپس یک رابطه‌ی یک به یک بین آن دو برقرار می‌کنیم.
به  طور مثال این کار را بر روی جدول Posts ، به شکل زیر انجام شده است:
 
namespace SplittingTableSample.DomainClasses
{
    public class Post
    {
        public virtual int Id { get; set; }
        public virtual string Title { get; set; }
        public virtual DateTime CreatedDate { get; set; }
        public virtual PostMetaData PostMetaData { get; set; }
    }
}
namespace SplittingTableSample.DomainClasses
{
    public class PostMetaData
    {
        public virtual int PostId { get; set; }
        public virtual string Body { get; set; }
        public virtual byte[] Image { get; set; }
        public virtual Post Post { get; set; }
    }
}
همان طور که می‌بینید، خواص حجیم به جدول دیگری به نام PostMetaData منتقل شده و با تعریف خواص راهبری ارجاعی در هر دو کلاس،رابطه‌ی یک به یک بین آن‌ها برقرار شده است.جز الزامات تعریف روابط یک به یک این است که، با استفاده از Fluent API یا Data Annotations ، طرف‌های Depenedent و Principal، صریحا به EF معرفی شوند.

namespace SplittingTableSample.DomainClasses
{
    public class PostMetaDataConfig : EntityTypeConfiguration<PostMetaData>
    {
        public PostMetaDataConfig()
        {
            HasKey(x => x.PostId);
            HasRequired(x => x.Post).WithRequiredDependent(x => x.PostMetaData);
        }
    }
}
اولین نکته ای که باید به آن توجه شود، این است که در کلاس PostMetaData، قوانین پیش فرض EF برای تعیین کلید اصلی نقض شده است و به همین دلیل، صراحتا با استفاده از متد HasKey ، کلید اصلی به EF معرفی شده است. نکته‌ی مهم دیگری که به آن باید توجه شود این است که هر دو سر رابطه به صورت Required تعریف شده است. دلیل این موضوع هم با توجه به مطلبی که قرار است گفته شود،کمی جلوتر خواهید فهمید. حال اگر تعاریف DbSet‌ها را نیز اصلاح کنیم و دستور زیر را اجرا کنیم:

var post = context.Posts.Find(1);
خروجی sql زیر را مشاهده خواهید کرد:

exec sp_executesql N'SELECT TOP (2) 
[Extent1].[Id] AS [Id], 
[Extent1].[Title] AS [Title], 
[Extent1].[CreatedDate] AS [CreatedDate]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[Id] = @p0',N'@p0 int',@p0=1
خیلی خوب! دیگر خبری از  فیلدهای اضافی Body و Image نیست. دلیل اینکه در اینجا join  بین دو جدول مشاهده نمی‌شود، قابلیت lazy loading است، که با virtual تعریف کردن خواص راهبری حاصل شده است. پس lazy loading در اینجا واقعا مفید است.
اما راه حل ذکر شده نیز کاملا بدون ایراد نیست. مشکل اساسی آن تعدد تعداد جداول آن است. آیا جدول Post ، واقعا احتیاج به چنین سطح نرمال سازی و تبدیل آن به دو جدول مجزا را داشت؟ مطمئنا خیر. آیا واقعا راه حلی وجود دارد که ما در سمت کد‌های خود با دو موجودیت مجزا کار کنیم، در صورتی که در دیتابیس این دو موجودیت، ساختار یک جدول را تشکیل دهند. در اینجا روشی مطرح می‌شود به نام تقسیم جدول (Table Splitting).
برای انجام این کار فقط چند تنظیم ساده لازم است:
1) فیلد‌های موجودیت مورد نظر را به موجودیت‌های کوچکتر، نگاشت می‌کنیم.
2) بین موجودیت‌های کوچک تر، رابطه‌ی یک به یک که هر دو سر رابطه Required هستند، رابطه برقرار می‌کنم.
3) با استفاده از Fluent API یا DataAnnotations، تمامی موجودیت‌ها را به یک نام در دیتابیس نگاشت می‌کنیم.
برای مثال، تنظیمات Fluent برای کلاس Post و PostMetaData که رابطه‌ی بین آن‌ها یک به یک است را مشاهده می‌کنید:
 
namespace SplittingTableSample.DomainClasses
{
    public class PostConfig : EntityTypeConfiguration<Post>
    {
        public PostConfig()
        {
            ToTable("Posts");
        }
    }
}
namespace SplittingTableSample.DomainClasses
{
    public class PostMetaDataConfig : EntityTypeConfiguration<PostMetaData>
    {
        public PostMetaDataConfig()
        {
            ToTable("Posts");
            HasKey(x => x.PostId);
            HasRequired(x => x.Post).WithRequiredDependent(x => x.PostMetaData);
        }
    }
}
نکته مهم این است که در هر دو کلاس(حتی کلاس Post) باید با استفاده از متد ToTable، کلاس‌ها را به یک نام در دیتابیس نگاشت کنیم. در نتیجه با استفاده از متد ToTable در هر دو موجودیت، آنها در دیتابیس به جدولی به نام  Posts نگاشت خواهند شد. تصویر زیر پس از اجرای برنامه، بیان گر این موضوع خواهد بود.

اگر دستورات زیر را اجرا کنید:


var post = context.Posts.Find(1);
Console.WriteLine(post.PostMetaData.Body);
خروجی زیر را در  SQL Server Profiler مشاهده خواهید کرد:
برای متد Find خروجی زیر:
 
exec sp_executesql N'SELECT TOP (2) 
[Extent1].[Id] AS [Id], 
[Extent1].[Title] AS [Title], 
[Extent1].[CreatedDate] AS [CreatedDate]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[Id] = @p0',N'@p0 int',@p0=1
و برای post.PostMetaData.Body دستور sql زیر را مشاهده می‌کنید:

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Body] AS [Body], 
[Extent1].[Image] AS [Image]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[Id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
دلیل این که در اینجا ،دو دستور sql به دیتابیس ارسال شده است، فعال بودن ویژگی lazy loading ،به دلیل virtual  تعریف کردن خواص راهبری موجودیت‌ها است.
حال اگر بخواهیم با یک رفت و آمد به دیتابیس کلیه اطلاعات را واکشی کنیم، می‌توانیم از Eager Loading استفاده کنیم:
 
var post = context.Posts.Include(x => x.PostMetaData).SingleOrDefault(x => x.Id == 1);
که خروجی sql آن نیز به شکل زیر است:

SELECT 
[Limit1].[Id] AS [Id], 
[Limit1].[Title] AS [Title], 
[Limit1].[CreatedDate] AS [CreatedDate], 
[Extent2].[Id] AS [Id1], 
[Extent2].[Body] AS [Body], 
[Extent2].[Image] AS [Image]
FROM   (SELECT TOP (2) [Extent1].[Id] AS [Id], [Extent1].[Title] AS [Title], [Extent1].[CreatedDate] AS [CreatedDate]
FROM [dbo].[Posts] AS [Extent1]
WHERE 1 = [Extent1].[Id] ) AS [Limit1]
LEFT OUTER JOIN [dbo].[Posts] AS [Extent2] ON [Limit1].[Id] = [Extent2].[Id]
در نتیجه با کمک این تکنیک توانستیم، با چند موجودیت، در قالب یک جدول رفتار کنیم و از مزیت‌های آن همچون lazy loading، نیز بهره مند شویم.

دریافت کد‌های این بخش: SplittingTable-Sample.rar 
مطالب
کپی کردن ساختار و داده‌های یک جدول از یک دیتابیس به دیتابیسی دیگر
در مواقعی ممکن است نیاز داشته باشیم که جدول یا جدول‌هایی از یک پایگاه داده را به یک پایگاه داده دیگر انتقال دهیم. در این مقاله قصد داریم روند انجام این کار را هم به صورت کوئری و هم به صورت ویزارد(گرافیکی) انجام دهیم.

برای شروع کار ابتدا دو دیتابیس به اسم‌های databasefrm و databaseto می‌سازیم. دیتابیس databasefrm شامل یک جدول به اسم emp با سه فیلد ID,Name,Address می‌باشد. قصد داریم جدول tmp از دیتابیس databasefrm را به دیتابیس databaseto انتقال دهیم. برای انجام این کار، یکی از روش‌های زیر را استفاده خواهیم کرد:

روش 1 : استفاده از کوئری

ساختار کلی انجام این عمل به صورت زیر خواهد بود:
Select * into DestinationDB.dbo.tableName from SourceDB.dbo.SourceTable
مثال :
select * into databaseto.dbo.emp from databasefrm.dbo.Emp
با اجرای دستور فوق یک کپی از جدول emp به همراه تمامی داده‌های آن به دیتابیس databaseto منتقل و ایجاد می‌شوند. اگر بخواهیم تمامی ایندکس‌ها، تریگر‌ها و قید‌ها (Constraint) نیز منتقل شوند، برای اینکار نیاز به تولید یک اسکریپت خواهد بود (در ادامه).

حال اگر بخواهیم یک کپی از  جدول را در دیتابیس جاری ایجاد کنیم، ساختار آن به صورت زیر خواهد بود  :
select * into newtable from SourceTable
که نمونه ای از آن برای دیتابیس ما به صورت زیرخواهد بود :
 select * into  emp1 from emp

می‌توانیم فقط فیلدهایی مشخص را به جدول دیگر کپی کنیم. برای انجا این کار کافیست به جای *  اسم فیلد‌های مورد نیاز را نوشت که ساختار دستوری آن به صورت زیر است :
select col1, col2 into <destination_table> from <source_table>
که برای مثال ما به صورت زیر خواهد بود :
select Id,Name into databaseto.dbo.emp1 from databasefrm.dbo.Emp

بعد از اجرای کوئری فوق نتیجه به صورت زیر خواهد بود :

کد فوق باعث کپی کردن فیلد‌های Id,Name شده است.

اگر بخواهیم فقط ساختار جدول را کپی کنیم روند کار به صورت زیر خواهد بود :

select *into <destination_database.dbo.destination table> from _
<source_database.dbo.source table> where 1 = 2
که نمونه ای از آن برای مثال ما به صورت زیر خواهد بود :
select * into databaseto.dbo.emp from 
databasefrm.dbo.emp where 1 = 2
کاربرد where در دستور فوق برای این است که عنوان فیلد‌ها را بگیریم و در جدول دیگری ذخیره کنیم.

نکته: هر وقت نیاز بود که فقط فیلد‌های یک جدول را دریافت کنید، می‌تواند از کدی همانند فوق استفاده کنید؛ با یک شرط که همیشه false برگرداند. ولی راه بهتری که توصیه میکنم استفاده از Top در دستور  Select می‌باشد. نمونه‌ای از دستور فوق:
select top(0) * into databaseto.dbo.emp from 
databasefrm.dbo.emp
همانطور که مشاهده می‌کنید دیگر در دستور فوق خبری از where نیست.

روش 2: ویزارد

جهت تهیه کارهای فوق به صورت ویزارد، به صورت خلاصه فقط به روند انجام کار بسنده می‌کنیم:
1- SSMS را باز کنید.
2- بر روی دیتابیس مورد نظر کلیک راست کرده و از منوی ظاهر شده Task را انتخاب نموده و در کادر بازشو Export data را انتخاب کنید.
3- در پنجره‌ی ظاهر شده بر روی دکمه next کلیک کرده و در پنجره بعدی، نوع اعتبار سنجی را انتخاب کرده و دیتابیس مورد نظر را انتخاب نمایید (databasefrm).
4- همانند مرحله 3 است با این تفاوت که اینبار دیتابیس مقصد را انتخاب می‌کنیم (databaseto).
5- در پنجره‌ی بعدی گزینه اول را انتخاب کرده (copy data from ...) و بعد از کلیک بر روی next در پنجره ظاهر شده، جدول یا جداول مورد نظر را انتخاب کنید.

روش 3 : تولید اسکریپت

 
با استفاده از دو روش فوق فقط می‌توانستیم ساختار جداول و داده‌های آن را انتقال بدهیم. برای انتقال کامل جداول مثل تریگرها، قیدها و ... می‌بایست از جدول یا جداول اسکریپت تولید و در نهایست اسکریپت را اجرا نماییم.
Right click on datbase >>Task>>Generate script>>next

انتخاب دیتابیس مورد نظر و بعد انتخاب مواردی که قصد داریم از آنها اسکریپت ایجاد کنیم و در پایان اسکریپت مورد نظر را بر روی دیتابیس مقصد (databaseto) اجرا می‌کنیم.

و در پایان نهایت تشکر را از تمام عزیزان و دوستان نویسنده‌ی سایت دارم. امیدوارم در سال 94 شاهد موفقیت‌های خوبی در حوزه‌ی نرم افزار باشیم.
مطالب
Kendo UI MVVM
پیشنیازها
- «استفاده از Kendo UI templates »
- «اعتبار سنجی ورودی‌های کاربر در Kendo UI»
- «فعال سازی عملیات CRUD در Kendo UI Grid» جهت آشنایی با نحوه‌ی تعریف DataSource ایی که می‌تواند اطلاعات را ثبت، حذف و یا ویرایش کند.


در این مطلب قصد داریم به یک چنین صفحه‌ای برسیم که در آن در ابتدای نمایش، لیست ثبت نام‌های موجود، از سرور دریافت و توسط یک Kendo UI template نمایش داده می‌شود. سپس امکان ویرایش و حذف هر ردیف، وجود خواهد داشت، به همراه امکان افزودن ردیف‌های جدید. در این بین مدیریت نمایش لیست ثبت نام‌ها توسط امکانات binding توکار فریم ورک MVVM مخصوص Kendo UI صورت خواهد گرفت. همچنین کلیه اعمال مرتبط با هر ردیف نیز توسط data binding دو طرفه مدیریت خواهد شد.



Kendo UI MVVM

الگوی MVVM یا Model-View-ViewModel که برای اولین بار جهت کاربردهای WPF و Silverlight معرفی شد، برای ساده سازی اتصال تغییرات کنترل‌های برنامه به خواص ViewModel یک View کاربرد دارد. برای مثال با تغییر عنصر انتخابی یک DropDownList در یک View، بلافاصله خاصیت متصل به آن که در ViewModel برنامه تعریف شده‌است، مقدار دهی و به روز خواهد شد. هدف نهایی آن نیز جدا سازی منطق کدهای UI، از کدهای جاوا اسکریپتی سمت کاربر است. برای این منظور کتابخانه‌هایی مانند Knockout.js به صورت اختصاصی برای این کار تهیه شده‌اند؛ اما Kendo UI نیز جهت یکپارچگی هرچه تمامتر اجزای آن، دارای یک فریم ورک MVVM توکار نیز می‌باشد. طراحی آن نیز بسیار شبیه به Knockout.js است؛ اما با سازگاری 100 درصد با کل مجموعه.
پیاده سازی الگوی MVVM از 4 قسمت تشکیل می‌شود:
- Model که بیانگر خواص متناظر با اشیاء رابط کاربری است.
- View همان رابط کاربری است که به کاربر نمایش داده می‌شود.
- ViewModel واسطی است بین Model و View. کار آن انتقال داده‌ها و رویدادها از View به مدل است و در حالت binding دوطرفه، عکس آن نیز صحیح می‌باشد.
- Declarative data binding جهت رهایی برنامه نویس‌ها از نوشتن کدهای هماهنگ سازی اطلاعات المان‌های View و خواص ViewModel کاربرد دارد.

در ادامه این اجزا را با پیاده سازی مثالی که در ابتدای بحث مطرح شد، دنبال می‌کنیم.


تعریف Model و ViewModel

در سمت سرور، مدل ثبت نام برنامه چنین شکلی را دارد:
namespace KendoUI07.Models
{
    public class Registration
    {
        public int Id { set; get; }
        public string UserName { set; get; }
        public string CourseName { set; get; }
        public int Credit { set; get; }
        public string Email { set; get; }
        public string Tel { set; get; }
    }
}
در سمت کاربر، این مدل را به نحو ذیل می‌توان تعریف کرد:
    <script type="text/javascript">
        $(function () {
            var model = kendo.data.Model.define({
                id: "Id",
                fields: {
                    Id: { type: 'number' }, // leave this set to 0 or undefined, so Kendo knows it is new.
                    UserName: { type: 'string' },
                    CourseName: { type: 'string' },
                    Credit: { type: 'number' },
                    Email: { type: 'string' },
                    Tel: { type: 'string' }
                }
            });
        });
    </script>
و ViewModel برنامه در ساده‌ترین شکل آن اکنون چنین تعریفی را خواهد یافت:
    <script type="text/javascript">
        $(function () {
            var viewModel = kendo.observable({
                accepted: false,
                course: new model()
            });
        });
    </script>
یک viewModel در Kendo UI به صورت یک observable object تعریف می‌شود که می‌تواند دارای تعدادی خاصیت و متد دلخواه باشد. هر خاصیت آن به یک عنصر HTML متصل خواهد شد. در اینجا این اتصال دو طرفه است؛ به این معنا که تغییرات UI به خواص viewModel و برعکس منتقل و منعکس می‌شوند.


اتصال ViewModel به View برنامه

تعریف فرم ثبت نام را در اینجا ملاحظه می‌کنید. فیلدهای مختلف آن بر اساس نکات اعتبارسنجی HTML 5 با ویژگی‌های خاص آن، مزین شده‌اند. جزئیات آن‌را در مطلب «اعتبار سنجی ورودی‌های کاربر در Kendo UI» پیشتر بررسی کرده‌ایم.
اگر به تعریف هر فیلد دقت کنید، ویژگی data-bind جدیدی را هم ملاحظه خواهید کرد:
    <div id="coursesSection" class="k-rtl k-header">
        <div class="box-col">
            <form id="myForm" data-role="validator" novalidate="novalidate">
                <h3>ثبت نام</h3>
                <ul>
                    <li>
                        <label for="Id">Id</label>
                        <span id="Id" data-bind="text:course.Id"></span>
                    </li>
                    <li>
                        <label for="UserName">نام</label>
                        <input type="text" id="UserName" name="UserName" class="k-textbox"
                               data-bind="value:course.UserName"
                               required />
                    </li>
                    <li>
                        <label for="CourseName">دوره</label>
                        <input type="text" dir="ltr" id="CourseName" name="CourseName" required
                               data-bind="value:course.CourseName" />
                        <span class="k-invalid-msg" data-for="CourseName"></span>
                    </li>
                    <li>
                        <label for="Credit">مبلغ پرداختی</label>
                        <input id="Credit" name="Credit" type="number" min="1000" max="6000"
                               required data-max-msg="عددی بین 1000 و 6000" dir="ltr"
                               data-bind="value:course.Credit"
                               class="k-textbox k-input" />
                        <span class="k-invalid-msg" data-for="Credit"></span>
                    </li>
                    <li>
                        <label for="Email">پست الکترونیک</label>
                        <input type="email" id="Email" dir="ltr" name="Email"
                               data-bind="value:course.Email"
                               required class="k-textbox" />
                    </li>
                    <li>
                        <label for="Tel">تلفن</label>
                        <input type="tel" id="Tel" name="Tel" dir="ltr" pattern="\d{8}"
                               required class="k-textbox"
                               data-bind="value:course.Tel"
                               data-pattern-msg="8 رقم" />
                    </li>
                    <li>
                        <input type="checkbox" name="Accept"
                               data-bind="checked:accepted"
                               required />
                        شرایط دوره را قبول دارم.
                        <span class="k-invalid-msg" data-for="Accept"></span>
                    </li>
                    <li>
                        <button class="k-button"
                                data-bind="enabled: accepted, click: doSave"
                                type="submit">
                            ارسال
                        </button>
                        <button class="k-button" data-bind="click: resetModel">از نو</button>
                    </li>
                </ul>
                <span id="doneMsg"></span>
            </form>
        </div>
برای اتصال ViewModel تعریف شده به ناحیه‌ی مشخص شده با DIV ایی با Id مساوی coursesSection، می‌توان از متد kendo.bind استفاده کرد.
    <script type="text/javascript">
        $(function () {
            var model = kendo.data.Model.define({
            // ...
            });

            var viewModel = kendo.observable({
            // ...
            });

            kendo.bind($("#coursesSection"), viewModel);
        });
    </script>
به این ترتیب Kendo UI به بر اساس تعریف data-bind یک فیلد، برای مثال تغییرات خواص course.UserName را به text box نام کاربر منتقل می‌کند و همچنین اگر کاربر اطلاعاتی را در این text box وارد کند، بلافاصله این تغییرات در خاصیت course.UserName منعکس خواهند شد.
<input type="text" id="UserName" name="UserName" class="k-textbox"
       data-bind="value:course.UserName"
       required />

بنابراین تا اینجا به صورت خلاصه، مدلی را توسط متد kendo.data.Model.define، معادل مدل سمت سرور خود ایجاد کردیم. سپس وهله‌ای از این مدل را به صورت یک خاصیت جدید دلخواهی در ViewModel تعریف شده توسط متد kendo.observable در معرض دید View برنامه قرار دادیم. در ادامه اتصال ViewModel و View، با فراخوانی متد kendo.bind انجام شد. اکنون برای دریافت تغییرات کنترل‌های برنامه، تنها کافی است ویژگی‌های data-bind ایی را به آن‌ها اضافه کنیم.
در ناحیه‌ی تعریف شده توسط متد kendo.bind، کلیه خواص ViewModel در دسترس هستند. برای مثال اگر به تعریف ViewModel دقت کنید، یک خاصیت دیگر به نام accepted با مقدار false نیز در آن تعریف شده‌است (این خاصیت چون صرفا کاربرد UI داشت، در model برنامه قرار نگرفت). از آن برای اتصال checkbox تعریف شده، به button ارسال اطلاعات، استفاده کرده‌ایم:
<input type="checkbox" name="Accept"
       data-bind="checked:accepted"
       required />

<button class="k-button"
        data-bind="enabled: accepted, click: doSave"
        type="submit">
       ارسال
</button>
برای مثال اگر کاربر این checkbox را انتخاب کند، مقدار خاصیت accepted، مساوی true خواهد شد. تغییر مقدار این خاصیت، توسط ViewModel بلافاصله در کل ناحیه coursesSection منتشر می‌شود. به همین جهت ویژگی enabled: accepted که به معنای مقید بودن فعال یا غیرفعال بودن دکمه بر اساس مقدار خاصیت accepted است، دکمه را فعال می‌کند، یا برعکس و برای انجام این عملیات نیازی نیست کدنویسی خاصی را انجام داد. در اینجا بین checkbox و button یک سیم کشی برقرار است.


ارسال داده‌های تغییر کرده‌ی ViewModel به سرور

تا اینجا 4 جزء اصلی الگوی MVVM که در ابتدای بحث عنوان شد، تکمیل شده‌اند. مدل اطلاعات فرم تعریف گردید. ViewModel ایی که این خواص را به المان‌های فرم متصل می‌کند نیز در ادامه اضافه شده‌است. توسط ویژگی‌های data-bind کار Declarative data binding انجام می‌شود.
در ادامه نیاز است تغییرات ViewModel را به سرور، جهت ثبت، به روز رسانی و حذف نهایی منتقل کرد.
    <script type="text/javascript">
        $(function () {
            var model = kendo.data.Model.define({
                //...
            });

            var dataSource = new kendo.data.DataSource({
                type: 'json',
                transport: {
                    read: {
                        url: "api/registrations",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    },
                    create: {
                        url: "api/registrations",
                        contentType: 'application/json; charset=utf-8',
                        type: "POST"
                    },
                    update: {
                        url: function (course) {
                            return "api/registrations/" + course.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "PUT"
                    },
                    destroy: {
                        url: function (course) {
                            return "api/registrations/" + course.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "DELETE"
                    },
                    parameterMap: function (data, type) {
                        // Convert to a JSON string.  Without this step your content will be form encoded.
                        return JSON.stringify(data);
                    }
                },
                schema: {
                    model: model
                },
                error: function (e) {
                    alert(e.errorThrown);
                },
                change: function (e) {
                    // فراخوانی در زمان دریافت اطلاعات از سرور و یا تغییرات محلی
                    viewModel.set("coursesDataSourceRows", new kendo.data.ObservableArray(this.view()));
                }
            });

            var viewModel = kendo.observable({
                //...
            });

            kendo.bind($("#coursesSection"), viewModel);
            dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار
        });
    </script>
در اینجا تعریف DataSource کار با منبع داده راه دور ASP.NET Web API را مشاهده می‌کنید. تعاریف اصلی آن با تعاریف مطرح شده در مطلب «فعال سازی عملیات CRUD در Kendo UI Grid» یکی هستند. هر قسمت آن مانند read، create، update و destory به یکی از متدهای کنترلر ASP.NET Web API اشاره می‌کنند. حالت‌های update و destroy بر اساس Id ردیف انتخابی کار می‌کنند. این Id را باید در قسمت model مربوط به اسکیمای تعریف شده، دقیقا مشخص کرد. عدم تعریف فیلد id، سبب خواهد شد تا عملیات update نیز در حالت create تفسیر شود.


متصل کردن DataSource به ViewModel

تا اینجا DataSource ایی جهت کار با سرور تعریف شده‌است؛ اما مشخص نیست که اگر رکوردی اضافه شد، چگونه باید اطلاعات خودش را به روز کند. برای این منظور خواهیم داشت:
    <script type="text/javascript">
        $(function () {
            $("#coursesSection").kendoValidator({
                // ...
            });

            var model = kendo.data.Model.define({
                // ...
            });

            var dataSource = new kendo.data.DataSource({
                // ...
            });

            var viewModel = kendo.observable({
                accepted: false,
                course: new model(),
                doSave: function (e) {
                    e.preventDefault();
                    console.log("this", this.course);
                    var validator = $("#coursesSection").data("kendoValidator");
                    if (validator.validate()) {
                        if (this.course.Id == 0) {
                            dataSource.add(this.course);
                        }
                        dataSource.sync(); // push to the server
                        this.set("course", new model()); // reset controls
                    }
                },
                resetModel: function (e) {
                    e.preventDefault();
                    this.set("course", new model());
                }            
             });

            kendo.bind($("#coursesSection"), viewModel);
            dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار
        });
    </script>
همانطور که در تعاریف تکمیلی viewModel مشاهده می‌کنید، اینبار دو متد جدید دلخواه doSave و resetModel را اضافه کرده‌ایم.
در متد doSave، ابتدا بررسی می‌کنیم آیا اعتبارسنجی فرم با موفقیت انجام شده‌است یا خیر. اگر بله، توسط متد add منبع داده، اطلاعات فرم جاری را توسط شیء course که هم اکنون به تمامی فیلدهای آن متصل است، اضافه می‌کنیم. در اینجا بررسی شده‌است که آیا Id این اطلاعات صفر است یا خیر. از آنجائیکه از همین متد برای به روز رسانی نیز در ادامه استفاده خواهد شد، در حالت به روز رسانی، Id شیء ثبت شده، از طرف سرور دریافت می‌گردد. بنابراین غیر صفر بودن این Id به معنای عملیات به روز رسانی است و در این حالت نیازی نیست کار بیشتری را انجام داد؛ زیرا شیء متناظر با آن پیشتر به منبع داده اضافه شده‌است.
استفاده از متد add صرفا به معنای مطلع کردن منبع داده محلی از وجود رکوردی جدید است. برای ارسال این تغییرات به سرور، از متد sync آن می‌توان استفاده کرد. متد sync بر اساس متد add یک درخواست POST، بر اساس شیءایی که Id غیر صفر دارد، یک درخواست PUT و با فراخوانی متد remove بر روی منبع داده، یک درخواست DELETE را به سمت سرور ارسال می‌کند.
متد دلخواه  resetModel سبب مقدار دهی مجدد شیء course با یک وهله‌ی جدید از شیء model می‌شود. همینقدر برای پاک کردن تمامی کنترل‌های صفحه کافی است.

تا اینجا دو متد جدید را در ViewModel برنامه تعریف کرده‌ایم. در مورد نحوه‌ی اتصال آن‌ها به View، به کدهای دو دکمه‌ی موجود در فرم دقت کنید:
<button class="k-button"
        data-bind="enabled: accepted, click: doSave"
        type="submit">
       ارسال
</button>
<button class="k-button" data-bind="click: resetModel">از نو</button>
این متدها نیز توسط ویژگی‌های data-bind به هر دکمه نسبت داده شده‌اند. به این ترتیب برای مثال با کلیک کاربر بر روی دکمه‌ی submit، متد doSave موجود در ViewModel فراخوانی می‌شود.


مدیریت سمت سرور ثبت، ویرایش و حذف اطلاعات

در حالت ثبت، متد Post توسط آدرس مشخص شده در قسمت create منبع داده، فراخوانی می‌گردد. نکته‌ی مهمی که در اینجا باید به آن دقت داشت، نحوه‌ی بازگشت Id رکورد جدید ثبت شده‌است.  اگر این تنظیم صورت نگیرد، Id رکورد جدید را در لیست، مساوی صفر مشاهده خواهید کرد و منبع داده این رکورد را همواره به عنوان یک رکورد جدید، مجددا به سرور ارسال می‌کند.
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using KendoUI07.Models;

namespace KendoUI07.Controllers
{
    public class RegistrationsController : ApiController
    {
        public HttpResponseMessage Delete(int id)
        {
            var item = RegistrationsDataSource.LatestRegistrations.FirstOrDefault(x => x.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);

            RegistrationsDataSource.LatestRegistrations.Remove(item);
            return Request.CreateResponse(HttpStatusCode.OK, item);
        }

        public IEnumerable<Registration> Get()
        {
            return RegistrationsDataSource.LatestRegistrations;
        }

        public HttpResponseMessage Post(Registration registration)
        {
            if (!ModelState.IsValid)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            var id = 1;
            var lastItem = RegistrationsDataSource.LatestRegistrations.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }
            registration.Id = id;
            RegistrationsDataSource.LatestRegistrations.Add(registration);

            // ارسال آی دی مهم است تا از ارسال رکوردهای تکراری جلوگیری شود
            return Request.CreateResponse(HttpStatusCode.Created, registration);
        }

        [HttpPut] // Add it to fix this error: The requested resource does not support http method 'PUT'
        public HttpResponseMessage Update(int id, Registration registration)
        {
            var item = RegistrationsDataSource.LatestRegistrations
                                        .Select(
                                            (prod, index) =>
                                                new
                                                {
                                                    Item = prod,
                                                    Index = index
                                                })
                                        .FirstOrDefault(x => x.Item.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);


            if (!ModelState.IsValid || id != registration.Id)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            RegistrationsDataSource.LatestRegistrations[item.Index] = registration;
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}
در اینجا بیشتر امضای این متدها مهم هستند، تا منطق پیاده سازی شده در آن‌ها. همچنین بازگشت Id رکورد جدید، توسط متد Post نیز بسیار مهم است و سبب می‌شود تا DataSource بداند با فراخوانی متد sync آن، باید عملیات Post یا create انجام شود یا Put و update.


نمایش آنی اطلاعات ثبت شده در یک لیست

ردیف‌های اضافه شده به منبع داده را می‌توان بلافاصله در همان سمت کلاینت توسط Kendo UI Template که قابلیت کار با ViewModelها را دارد، نمایش داد:
    <div id="coursesSection" class="k-rtl k-header">
        <div class="box-col">
            <form id="myForm" data-role="validator" novalidate="novalidate">
                           <!--فرم بحث شده در ابتدای مطلب-->
            </form>
        </div>
        <div id="results">
            <table class="metrotable">
                <thead>
                    <tr>
                        <th>Id</th>
                        <th>نام</th>
                        <th>دوره</th>
                        <th>هزینه</th>
                        <th>ایمیل</th>
                        <th>تلفن</th>
                        <th></th>
                        <th></th>
                    </tr>
                </thead>
                <tbody data-template="row-template" data-bind="source: coursesDataSourceRows"></tbody>
                <tfoot data-template="footer-template" data-bind="source: this"></tfoot>
            </table>
            <script id="row-template" type="text/x-kendo-template">
                <tr>
                    <td data-bind="text: Id"></td>
                    <td data-bind="text: UserName"></td>
                    <td dir="ltr" data-bind="text: CourseName"></td>
                    <td>
                        #: kendo.toString(get("Credit"), "c0") #
                    </td>
                    <td data-bind="text: Email"></td>
                    <td data-bind="text: Tel"></td>
                    <td><button class="k-button" data-bind="click: deleteCourse">حذف</button></td>
                    <td><button class="k-button" data-bind="click: editCourse">ویرایش</button></td>
                </tr>
            </script>
            <script id="footer-template" type="text/x-kendo-template">
                <tr>
                    <td colspan="3"></td>
                    <td>
                        جمع کل: #: kendo.toString(totalPrice(), "c0") #
                    </td>
                    <td colspan="2"></td>
                    <td></td>
                    <td></td>
                </tr>
            </script>
        </div>
    </div>
در ناحیه‌ی coursesSection که توسط متد kendo.bind به viewModel برنامه متصل شده‌است، یک جدول را برای نمایش ردیف‌های ثبت شده توسط کاربر اضافه کرده‌ایم. thead آن بیانگر سر ستون جدول است. قسمت tbody و tfoot این جدول توسط دو Kendo UI Template مقدار دهی شد‌ه‌اند. هر کدام نیز منبع داده‌اشان را از view model دریافت می‌کنند. در row-template معادل خواص شیء course را مشاهده می‌کنید. در footer-template متد totalPrice برای نمایش جمع ستون هزینه اضافه شده‌است. بنابراین مطابق این قسمت از View، به یک خاصیت جدید coursesDataSourceRows و سه متد deleteCourse، editCourse و totalPrice نیاز است:
    <script type="text/javascript">
        $(function () {
            // ...
            var viewModel = kendo.observable({
                accepted: false,
                course: new model(),
                coursesDataSourceRows: new kendo.data.ObservableArray([]),
                doSave: function (e) {
                       // ...
                },
                resetModel: function (e) {
                      // ...
                },
                totalPrice: function () {
                    var sum = 0;
                    $.each(this.get("coursesDataSourceRows"), function (index, item) {
                        sum += item.Credit;
                    });
                    return sum;
                },
                deleteCourse: function (e) {
                    // the current data item is passed as the "data" field of the event argument
                    var course = e.data;
                    dataSource.remove(course);
                    dataSource.sync(); // push to the server
                },
                editCourse: function(e) {
                    // the current data item is passed as the "data" field of the event argument
                    var course = e.data;
                    this.set("course", course);
                }
            });

            kendo.bind($("#coursesSection"), viewModel);
            dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار
        });
    </script>
نحوه‌ی اتصال خاصیت جدید coursesDataSourceRows که به عنوان منبع داده ردیف‌های row-template عمل می‌کند، به این صورت است:
- ابتدا خاصیت دلخواه coursesDataSourceRows به viewModel اضافه می‌شود تا در ناحیه‌ی coursesSection در دسترس قرار گیرد.
- سپس اگر به انتهای تعریف DataSource دقت کنید، داریم:
    <script type="text/javascript">
        $(function () {
            var dataSource = new kendo.data.DataSource({
                //...
                change: function (e) {
                    // فراخوانی در زمان دریافت اطلاعات از سرور و یا تغییرات محلی
                    viewModel.set("coursesDataSourceRows", new kendo.data.ObservableArray(this.view()));
                }
            });
        });
    </script>
متد change آن، هر زمانیکه اطلاعاتی در منبع داده تغییر کنند یا اطلاعاتی به سمت سرور ارسال یا دریافت گردد، فراخوانی می‌شود. در همینجا فرصت خواهیم داشت تا خاصیت coursesDataSourceRows را جهت نمایش اطلاعات موجود در منبع داده، مقدار دهی کنیم. همین مقدار دهی ساده سبب اجرای row-template برای تولید ردیف‌های جدول می‌شود. استفاده از new kendo.data.ObservableArray سبب خواهد شد تا اگر اطلاعاتی در فرم برنامه تغییر کند، این اطلاعات بلافاصله در لیست گزارش برنامه نیز منعکس گردد.



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
KendoUI07.zip