*در دنیای 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 برای ذخیره سازی اطلاعات از سند 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
PM> Install-Package BirghtStarDbLibs
بعد از نصب پکیجهای بالا یک فایل 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; } }
استفاده از اینترفیس برای ساخت مدل انعطاف پذیری بالایی را در اختیار ما قرار میدهد که بعدا مفصل بحث خواهد شد. برای عملیات درج داده میتوان به صورت زیر عمل کنید:
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();
»embedded : در این حالت از طریق آدرس فیزیکی فایل مورد نظر میتوان یک Connection ایجاد کرد.
»rest : یا استفاده از HTTP یا HTTPS میتوان به سرویس B*Db دسترسی داشت.
»dotNetRdf : برای ارتباط با یک Store دیگر با استفاده از اتصال دهندههای DotNetRDf.
»sparql : اتصال به منبع داده ای دیگر با استفاده از پروتکل SPARQL
در هنگام ایجاد اتصال باید نوع مورد نظر را از حتما تعیین نمایید. با استفاده از storedirctory مکان فیزیکی فایل تعیین خواهد شد.
انقیاد در لیست List Binding
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; }
<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>
private ObservableCollection<Employee> employees; public MainWindow() { InitializeComponent(); employees = Employee.GetEmployees(); DataContext = employees; }
<Grid> <StackPanel Orientation="Horizontal"> <Slider Name="mySlider" Minimum="0" Maximum="100" Width="300"/> <TextBlock Margin="5" Text="{Binding Value,ElementName=mySlider}"/> </StackPanel> </Grid>
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 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>
در زمانیکه در عملیات Data Binding نوع دادهی خصوصیت ما در Source (منبع داده) با نوع دادهی خصوصیت ما در target (کنترل یا View) متفاوت است، به یک مبدل در حین Binding نیاز داریم. این کار را از طریق یک کلاس که اینترفیس IValueConvertor را پیاده سازی کرده است، انجام میدهیم.
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(); } }
xmlns:local="clr-namespace:DataConversion"
<Window.Resources> <local:DateConverter x:Key="MyConverter"/> </Window.Resources>
<TextBlock Text="{Binding BornDate,Converter={StaticResource MyConverter}}" Margin="5,0,0,0"/>
- 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" />
<div id="chart"></div>
<div id="chart" style="width: 400px; height: 600px"></div>
$("#chart").kendoChart();
همانطور که مشاهده میکنید هیچ دادهایی را هنوز برای چارت تعیین نکردهایم؛ در نتیجه همانند تصویر فوق یک چارت خالی بر روی صفحه نمایش داده میشود. برای چارت فوق میتوانیم خواصی از قبیل عنوان و ... را تعیین کنیم:
$("#chart").kendoChart({ title: { text: "چارت آزمایشی" } });
نمایش دادهها بر روی چارت:
دادهها را میتوان هم به صورت local و هم به صورت remote دریافت و بر روی چارت نمایش داد. اینکار را میتوانیم توسط قسمت series انجام دهیم:
$("#chart").kendoChart({ title: { text: "عنوان چارت" }, series: [ { name: "دادههای چارت", data: [200, 450, 300, 125] } ] });
$("#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" } ] });
موارد فوق را میتوانیم به صورت یک افزونه نیز کپسوله کنیم.
کدهای افزونه 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)
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
بهترین حالتی که دو کلاس به طور مطلق هیچ وابستگی به یکدیگر ندارند. در این صورت میتوان یکی کلاسها را حذف کرد، بدون اینکه تأثیری بر روی سایر آنها داشته باشد. البته وجود برنامهای کاربردی با این نوع اتصال ممکن نخواهد بود. بهترین چیزی که میشود با این نوع اتصال ایجاد کرد، Class Libraryای میباشد که شامل مجموعه ای از کلاسهای مستقل بوده، به طوری که هیچ تأثیری بر روی یکدیگر ندارند.
در این شکل از اتصال، یک کلاس به واسط عمومی کلاس دیگر وابسته میباشد؛ به این معنی که از عملیاتی که کلاس مورد نظر در واسط عمومی خود قرار داده است، استفاده میکند.
این نوع اتصال زمانی رخ میدهد که یک کلاس از جزئیات پیاده سازی کلاس دیگر با داشتن اجازه دسترسی از جانب آن، استفاده کند. به عنوان مثال، مکانیزم کلاسهای friend در زبان سی پلاس پلاس، که امکان این را میدهد کلاس X اجازه دوستی به کلاس Y را اعطا کند و در این صورت کلاس Y میتواند به جزئیات پیاده سازی خصوصی کلاس X دسترسی داشته باشد.
این نوع اتصال هم به مانند Overt میباشد؛ با این تفاوت که هیچ اجازه دسترسی به کلاس Y داده نشده است. اگر زبانی داشته باشیم که به کلاس Y اجازه دهد خود را به عنوان دوست کلاس X معرفی کند، در این صورت نوع اتصال بین دو کلاس X و Y از نوع Covert میباشد. به عنوان مثال واقعی، میتوان به استفاده از Reflection در دات نت اشاره کرد.
آخرین نوع اتصال که بدترین حالت هم محسوب میشود، مربوط است به زمانیکه کلاس X به هر طریقی که شده از جزئیات داخلی کلاس Y آگاه باشد و از اعضای عمومی دادهای (public data member) آن کلاس استفاده کند. منظور این است که با تغییر این دادههای کلاس متوجه میشود که بر روی عملیات b از کلاس چه تأثیری میگذارد و با اتکاء به این دستاورد، جزئیات داخلی خود را پیاده سازی میکند و یک اتصال پنهان را با کلاس Y ایجاد کرده است. در این حالت یک وابستگی قوی به صورت پنهان مابین رفتاری از کلاس Y و پیاده سازی کلاس X ایجاد شده است.
اتصال و پیوستگی مابین کلاسها باید از نوع 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 از سایر کلاسها دریافت میکنند، شناسایی کند. زیرا این نوع کلاسها این قاعده شهودی را نقض کردهاند.
مثال واقعی
یا برعکس آن ضد الگوی 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) و باید آنها را به کلاسهای جدا نگاشت کنید.
به کلاس Dictionary در تصویر زیر توجه کنید.
برای تعداد کمی داده، بهترین پیاده سازی با استفاده از List و در مقابل برای تعداد داده زیاد بهترین پیاده سازی، استفاده از HashTable میباشد. هر یک از این پیاده سازیها، به متدهایی برای add و find کلمات نیاز دارند. طراحی سمت چپ تصویر نشان از نقض این قاعده شهودی دارد.
در طرح سمت چپ، استفاده کننده باید بداند که چقدر داده وارد کند. از طرفی نمایش جزئیات پیاده سازی در نام کلاس هم ایده خوبی نیست (طرح سمت چپ). بهترین راه حل که در مقالات آینده به آنها خواهیم رسید، بحث استفاده از ارث بری میباشد. به این ترتیب، با استفاده از یک کلاس Dictionary که نمایش جزئیات داخلی خود را مخفی کرده و در شرایط لازم نمایش جزئیات داخلی خود را تغییر دهد. منظور این است که استفاده کننده درگیر جزئیات داخلی آن نشود و این جزئیات که کدام نوع PDict یا HDict استفاده خواهد شد، از دید او مخفی باشد.
در این بخش تعاریف، خصوصیات، مزایا و معایب مرتبط با این نوع سیستمها بررسی میشوند.
تعریف سیستمهای توزیع شده
تعاریف مختلفی توسط اشخاص و گروههای مختلف، از سیستمهای توزیع شده وجود دارد. در اینجا سعی شده که اکثر این تعاریف معرفی شوند؛ بگونهای که با ارائه نکات کلیدی این تعاریف به درک بهتری از خصوصیات سیستمهای توزیع شده برسیم.
تعاریف سیستمهای توزیع شده
تعریف اول: سیستمهای توزیع شده از مجموعهای از سخت افزارها و نرم افزارها که برای رسیدن به یک هدف واحد از طریق شبکه با یکدیگر در ارتباطند تشکیل شده.
تعریف دوم: یک مجموعه از 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- مدیریت دشوارتر: بدلیل اینکه سیستمهای توزیع شده از زیرسیستمهای زیادی تشکیل شده و هر یک از این زیر سیستمها در سخت افزارهای متفاوتی اجرا شدهاند، مدیریت و سازماندهی این نوع سیستمها دشوارتر از سیستمهای متمرکز است.
بدلیل اینکه همیشه قسمتهای مرتبط با پیاده سازی این نوع سیستمها از تعاریف آنها جذابتر است، سعی کردهام در این بخش و بخش بعد که اهداف و اصطلاحات مرتبط با سیستمهای توزیع شده را بررسی میکند، کمتر وارد جزئیات مفاهیم شوم. در صورت نیاز به توضیح بیشتر در مورد قسمت خاصی از این مفاهیم، در قسمت نظرات آنها را مطرح کنید.
برای مثال، جدول 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); }
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; } } }
namespace SplittingTableSample.DomainClasses { public class PostMetaDataConfig : EntityTypeConfiguration<PostMetaData> { public PostMetaDataConfig() { HasKey(x => x.PostId); HasRequired(x => x.Post).WithRequiredDependent(x => x.PostMetaData); } } }
var post = context.Posts.Find(1);
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 ، واقعا احتیاج به چنین سطح نرمال سازی و تبدیل آن به دو جدول مجزا را داشت؟ مطمئنا خیر. آیا واقعا راه حلی وجود دارد که ما در سمت کدهای خود با دو موجودیت مجزا کار کنیم، در صورتی که در دیتابیس این دو موجودیت، ساختار یک جدول را تشکیل دهند. در اینجا روشی مطرح میشود به نام تقسیم جدول (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); } } }
اگر دستورات زیر را اجرا کنید:
var post = context.Posts.Find(1); Console.WriteLine(post.PostMetaData.Body);
برای متد 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
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
حال اگر بخواهیم با یک رفت و آمد به دیتابیس کلیه اطلاعات را واکشی کنیم، میتوانیم از Eager Loading استفاده کنیم:
var post = context.Posts.Include(x => x.PostMetaData).SingleOrDefault(x => x.Id == 1);
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]
دریافت کدهای این بخش: 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
حال اگر بخواهیم یک کپی از جدول را در دیتابیس جاری ایجاد کنیم، ساختار آن به صورت زیر خواهد بود :
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
نکته: هر وقت نیاز بود که فقط فیلدهای یک جدول را دریافت کنید، میتواند از کدی همانند فوق استفاده کنید؛ با یک شرط که همیشه false برگرداند. ولی راه بهتری که توصیه میکنم استفاده از Top در دستور Select میباشد. نمونهای از دستور فوق:
select top(0) * into databaseto.dbo.emp from databasefrm.dbo.emp
روش 2: ویزارد
جهت تهیه کارهای فوق به صورت ویزارد، به صورت خلاصه فقط به روند انجام کار بسنده میکنیم:
1- SSMS را باز کنید.
2- بر روی دیتابیس مورد نظر کلیک راست کرده و از منوی ظاهر شده Task را انتخاب نموده و در کادر بازشو Export data را انتخاب کنید.
3- در پنجرهی ظاهر شده بر روی دکمه next کلیک کرده و در پنجره بعدی، نوع اعتبار سنجی را انتخاب کرده و دیتابیس مورد نظر را انتخاب نمایید (databasefrm).
4- همانند مرحله 3 است با این تفاوت که اینبار دیتابیس مقصد را انتخاب میکنیم (databaseto).
5- در پنجرهی بعدی گزینه اول را انتخاب کرده (copy data from ...) و بعد از کلیک بر روی next در پنجره ظاهر شده، جدول یا جداول مورد نظر را انتخاب کنید.
روش 3 : تولید اسکریپت
با استفاده از دو روش فوق فقط میتوانستیم ساختار جداول و دادههای آن را انتقال بدهیم. برای انتقال کامل جداول مثل تریگرها، قیدها و ... میبایست از جدول یا جداول اسکریپت تولید و در نهایست اسکریپت را اجرا نماییم.
انتخاب دیتابیس مورد نظر و بعد انتخاب مواردی که قصد داریم از آنها اسکریپت ایجاد کنیم و در پایان اسکریپت مورد نظر را بر روی دیتابیس مقصد (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>
<script type="text/javascript"> $(function () { var viewModel = kendo.observable({ accepted: false, course: new model() }); }); </script>
اتصال 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>
<script type="text/javascript"> $(function () { var model = kendo.data.Model.define({ // ... }); var viewModel = kendo.observable({ // ... }); kendo.bind($("#coursesSection"), viewModel); }); </script>
<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>
ارسال دادههای تغییر کردهی 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 به 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>
در متد 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>
مدیریت سمت سرور ثبت، ویرایش و حذف اطلاعات
در حالت ثبت، متد 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); } } }
نمایش آنی اطلاعات ثبت شده در یک لیست
ردیفهای اضافه شده به منبع داده را میتوان بلافاصله در همان سمت کلاینت توسط 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>
<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 به 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>
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
KendoUI07.zip