نظرات مطالب
RavenDB؛ تجربه متفاوت از پایگاه داده
سلام
ببخشید که دیر به سوال شما پاسخ دادم...
یه راه دیگه، علاوه بر راهی که توسط جناب آقای نصیری ارائه شده است، وجود دارد.
در پوشه Server فایل Raven.Server.exe را با Notepad باز کنید، سپس مقدار تنظیمات با کلید "Raven/AnonymousAccess " را به "All" تغییر دهید.توجه کنید که به بزرگ و کوچک بودن حروف حساس است.

در ضمن RavenDB از نظر سابقه و تعداد کاربران، قابل مقایسه با پایگاه داده هایی مثل SQL نیست و حق با شماست...
مطالب
C# 12.0 - Interceptors
به C# 12 و دات‌نت 8، ویژگی «آزمایشی» جدیدی به نام Interceptors اضافه شده‌است که به آن «monkey patching» هم می‌گویند. هدف از آن، جایگزین کردن یک پیاده سازی، با پیاده سازی دیگری است. به این ترتیب توسعه دهندگان دات‌نتی می‌توانند فراخوانی متدهایی خاص را ره‌گیری کرده (interception) و سپس آن‌را به فراخوانی یک پیاده سازی جدید، هدایت کنند.


Interceptor چیست؟

از زمان ارائه‌ی NET 8 preview 6 SDK. به بعد، امکان ره‌گیری هر متدی از کدهای برنامه، به دات‌نت اضافه شده‌است؛ به همین جهت از واژه‌ی Interceptor/ره‌گیر در اینجا استفاده می‌شود. خود تیم دات‌نت از این قابلیت در جهت بازنویسی پویای قسمت‌هایی از کدهای زیرساخت دات‌نت که از Reflection استفاده می‌کنند، با نگارش‌های کامپایل شده‌ی مختص به برنامه‌ی شما، کمک می‌گیرند. به این ترتیب سرعت و کارآیی برنامه‌های دات‌نت 8، بهبود قابل ملاحظه‌ای را پیدا کرده‌اند. برای مثال ahead-of-time compilation (AOT) در دات‌نت 8 و ASP.NET Core 8x بر اساس این ویژگی پیاده سازی شده‌است. این ویژگی جدید، مکمل source generators است که در نگارش‌های پیشین دات‌نت ارائه شده بود.


بررسی  Interceptors با تهیه‌ی یک مثال ساده

فرض کنید می‌خواهیم فراخوانی متد GetText زیر را ره‌گیری کرده و سپس آن‌را با نمونه‌ی دیگری جایگزین کنیم:
namespace CS8Tests;

public class InterceptorsSample
{
    public string GetText(string text)
    {
        return $"{text}, World!";
    }
}
برای اینکار ابتدا نیاز است یک فایل جدید را به نام InterceptsLocationAttribute.cs با محتوای زیر به پروژه اضافه کرد:
namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public sealed class InterceptsLocationAttribute : Attribute
{
    public InterceptsLocationAttribute(string filePath, int line, int character)
    {
    }
}
همانطور که در مقدمه‌ی بحث هم عنوان شد، این ویژگی هنوز آزمایشی است و نهایی نشده و ویژگی فوق نیز هنوز به دات‌نت اضافه نشده‌است. به همین جهت فعلا باید آن‌را به صورت دستی به پروژه اضافه کرد و احتمالا در نگارش‌های بعدی دات‌نت، امضای آن تغییر خواهد کرد ... یا حتی ممکن است بطور کامل حذف شود!

سپس فرض کنید فراخوانی متد GetText در فایل Program.cs برنامه به صورت زیر انجام شده‌است:
using CS8Tests;

var example = new InterceptorsSample();
var text = example.GetText("Hello");
Console.WriteLine(text); //Hello, World!
یعنی متد GetText، در سطر چهارم و کاراکتر 20 ام آن فراخوانی شده‌است. این اعداد مهم هستند!

در ادامه از این اطلاعات در ره‌گیر سفارشی زیر استفاده خواهیم کرد:
using System.Runtime.CompilerServices;

namespace CS8Tests;

public static class MyInterceptor
{
    [InterceptsLocation("C:\\Path\\To\\CS8Tests\\Program.cs", 4, 20)] 
    public static string InterceptorMethod(this InterceptorsSample example, string text)
    {
        return $"{text}, DNT!";
    }
}
این ره‌گیر که به صورت متدی الحاقی برای کلاس InterceptorsSample دربرگیرنده‌ی متد GetText تهیه می‌شود، کار جایگزینی فراخوانی آن‌را در سطر چهارم و کاراکتر 20 ام فایل Program.cs انجام می‌دهد. امضای پارامترهای این متد، باید با امضای پارامترهای متد ره‌گیری شده، یکی باشد.

اکنون اگر برنامه را اجرا کنیم ... با خطای زیر مواجه می‌شویم:
 error CS9137: The 'interceptors' experimental feature is not enabled in this namespace. Add
'<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);CS8Tests</InterceptorsPreviewNamespaces>'
to your project.
عنوان می‌کند که این ویژگی آزمایشی است و باید فایل csproj. را به صورت زیر تغییر داد تا بتوان از آن استفاده نمود:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <!--<NoWarn>Test001</NoWarn>-->
    <InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);CS8Tests</InterceptorsPreviewNamespaces>
  </PropertyGroup>
</Project>
اینبار برنامه کامپایل شده و اجرا می‌شود. در این حالت خروجی جدید برنامه، خروجی تامین شده‌ی توسط ره‌گیر سفارشی ما است:
Hello, DNT!


سؤال: آیا ره‌گیری انجام شده، در زمان کامپایل انجام می‌شود یا در زمان اجرا؟

برای این مورد می‌توان به Low-Level C# code تولیدی مراجعه کرد. برای مشاهده‌ی یک چنین کدهایی می‌توانید از منوی Tools->IL Viewer برنامه‌ی Rider استفاده کرده و در برگه‌ی ظاهر شده، گزینه‌ی Low-Level C# آن‌را انتخاب نمائید:
using CS8Tests;
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal class Program
{
  private static void <Main>$(string[] args)
  {
    Console.WriteLine(new InterceptorsSample().InterceptorMethod("Hello"));
  }

  public Program()
  {
    base..ctor();
  }
}
همانطور که مشاهده می‌کنید، این ره‌گیری و جایگزینی، در زمان کامپایل انجام شده و کامپایلر، به‌طور کامل نحوه‌ی فراخوانی متد GetText اصلی را به متد ره‌گیر ما تغییر داده و بازنویسی کرده‌است.


سؤال: آیا این قابلیت واقعا کاربردی است؟!

اکنون شاید این سؤال مطرح شود که ... واقعا چه کسی قرار است مسیر کامل یک فایل، شماره سطر و شماره ستون فراخوانی متدی را به اینگونه در اختیار سیستم ره‌گیری قرار دهد؟! آیا واقعا این قابلیت، یک قابلیت کاربردی و مناسب است؟!
اینجا است که اهمیت source generators مشخص می‌شود. توسط source generators دسترسی کاملی به syntax trees وجود دارد و همچنین یکسری اطلاعات تکمیلی مانند FilePath و سپس CSharpSyntaxNodeها که دسترسی به داده‌های متد ()GetLocation را دارند که مکان دقیق سطر و ستون‌های فراخوانی‌ها را مشخص می‌کند.


کاربردهای فعلی ره‌گیرها در دات نت 8

در دات نت 8، این موارد با استفاده از ره‌گیرها بهینه سازی شده و سرعت آن‌ها افزایش یافته‌اند:
- فراخوانی‌هایی که تمام اطلاعات آن‌ها در زمان کامپایل فراهم است، مانند Regex.IsMatch(@"a+b+") که از یک الگوی ثابت و مشخص استفاده می‌کند، ره‌گیری شده و پیاده سازی آن با کدی استاتیک، جایگزین می‌شود.
- در ASP.NET Minimal API، استفاده از lambda expressions جهت ارائه‌ی تعاریفی مانند:
app.MapGet("/products", handler: (int? page, int? pageLength, MyDb db) => { ... })
مرسوم است. این نوع فراخوانی‌ها نیز توسط ره‌گیرها برای جایگزینی handler آن‌ها با کدهای استاتیک، جهت بالابردن کارآیی و کاهش تخصیص‌های حافظه انجام می‌شود.
- بهبود کارآیی foreach loops جهت استفاده از ریاضیات برداری و SIMD در صورت امکان.
- بهبود کارآیی تزریق وابستگی‌ها، زمانیکه به تعاریف مشخصی مانند ()<provider.Register<MyService ختم می‌شود.
- بجای استفاده از expression trees در زمان اجرای برنامه، اکنون می‌توان کدهای SQL معادل را در زمان کامپایل برنامه تولید کرد.
- بهبود کارآیی Serializers، زمانیکه از یک نوع مشخص مانند ()<Serialize<MyType استفاده می‌شود و کامپایلر می‌تواند آن‌را با کدهای زمان کامپایل، جایگزین کند.


محدودیت‌های ره‌گیرها در دات‌نت 8

- ره‌گیرهای دات‌نت 8 فقط با متدها کار می‌کنند.
- مسیر ارائه شده حتما باید یک مسیر کامل و مشخص باشد. یعنی اگر این قطعه کد، به سیستم دیگری منتقل شود، کامپایل نخواهد شد و امکان ارائه‌ی مسیرهای نسبی وجود ندارد.
- امضای متدها، حتما باید یکی باشد. یعنی نمی‌توان یک ره‌گیر جنریک را تعریف کرد.
مطالب
VS Code برای توسعه دهندگان ASP.NET Core - قسمت سوم - گردش کاری‌های متداول
در قسمت قبل، دو عمل متداول نحوه‌ی ایجاد و اجرای یک پروژه‌ی جدید ASP.NET Core را بررسی کردیم. در ادامه می‌خواهیم معادل سایر اعمالی را که می‌توان با نگارش کامل ویژوال استودیو انجام داد، در VSCode نیز برشماریم.


کار با IDE و حرکت بین کدها

در ادامه‌، همان پروژه‌ای را که در قسمت قبل ایجاد کردیم، مجددا با وارد شدن به پوشه‌ی آن و اجرای دستور . code، توسط VSCode باز خواهیم کرد. سپس فایل Program.cs آن‌را باز کنید. فرض کنید در سطر ذیل آن:
 .UseStartup<Startup>()
می‌خواهیم به نحو ساده‌تری به کلاس Startup آن مراجعه کنیم. برای اینکار دو روش وجود دارد:
الف) اشاره‌گر ماوس را به آن نزدیک کنید و سپس دکمه‌ی Ctrl را نگه دارید. به این ترتیب واژه‌ی Startup تبدیل به یک لینک خواهد شد که با کلیک بر روی آن می‌توان به کلاس Startup رسید.
ب) روش دوم، قرار دادن اشاره‌گر متنی بر روی واژه Startup و سپس فشردن دکمه‌ی F12 است. این گزینه بر روی منوی کلیک راست بر روی این واژه نیز وجود دارد.

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

در این حالت اگر خواستید به مکان قبلی بازگردید فقط کافی است دکمه‌های alt + left cursor key را بفشارید.


در اینجا امکان یافتن ارجاعات به یک کلاس، مشاهده‌ی پیاده سازی‌ها و همچنین یک Refactoring به نام rename symbol نیز موجود است که با استفاده‌ی از آن، تمام ارجاعات به این کلاس در IDE نیز تغییرنام خواهند یافت.


یافتن سریع فایل‌ها در IDE

در یک پروژه‌ی بزرگ، برای یافتن سریع یک فایل، تنها کافی است دکمه‌های Ctrl+P را فشرده و در صفحه‌ی دیالوگ باز شده، قسمتی از نام آن‌را جستجو کنید:


همچنین اگر می‌خواهید محتوای فایل‌ها را جهت یافتن واژه‌ای خاص جستجو کنید، کلیدهای Ctrl+Shift+F (منوی Edit بالای صفحه و یا دومین آیکن در نوار ابزار عمودی سمت چپ صفحه) چنین امکانی را فراهم می‌کنند:



افزودن فایل‌های جدید به پروژه

فرض کنید می‌خواهیم یک کنترلر جدید را به پوشه‌ی Controllers اضافه کنیم:


برای اینکار ابتدا پوشه‌ی Controllers را انتخاب کنید. در همین حال، نوار ابزاری ظاهر می‌شود که توسط آن می‌توان در این پوشه، یک فایل جدید و یا یک پوشه‌ی جدید را ایجاد کرد.
اگر علاقمند به ایجاد فایل یا پوشه‌ای در ریشه‌ی پروژه باشید، باید تمام فایل‌ها و پوشه‌های موجود، در حالت انتخاب نشده قرار بگیرند. به همین جهت فقط کافی است در یک مکان خالی، در لیست فایل‌ها کلیک کنید تا فایل‌ها و پوشه‌ها از حالت انتخاب شده خارج شوند. اکنون نوار ابزار ظاهر شده‌ی افزودن فایل‌ها، به پوشه‌ی ریشه‌ی پروژه اشاره می‌کند.

در ادامه فایل جدید AboutController.cs را در پوشه‌ی Controllers ایجاد کنید. مشاهده خواهید کرد که یک فایل کاملا خالی ایجاد شده‌است. در VSCode به ازای پسوندهای مختلف فایل‌ها، قالب‌های از پیش آماده شده‌ای برای آن‌ها درنظر گرفته نمی‌شود و نمای ابتدایی تمام آن‌ها خالی است.
اما در اینجا اگر کلمه‌ی name را تایپ کنیم، دو پیشنهاد افزودن فضای نام را ارائه می‌دهد:


اولی صرفا نام namespace را در صفحه درج خواهد کرد. دومی به یک code snippet اشاره می‌کند و کار آن ایجاد قالب یک فضای نام جدید است. برای درج آن فقط کافی است دکمه‌ی tab را بفشارید.
همین کار را در مورد class نیز می‌توان تکرار کرد و در اینجا در intellisense ظاهر شده یا می‌توان واژه‌ی class را درج کرد و یا code snippet آن‌را انتخاب نمود که یک کلاس جدید را ایجاد می‌کند:


یک نکته: در VSCode نیازی نیست تا مدام دکمه‌های Ctrl+S را جهت ذخیره سازی فایل‌های تغییر کرده فشرد. می‌توان از منوی فایل، گزینه‌ی Auto Save را انتخاب کرد تا این‌کار را به صورت خودکار انجام دهد.


ایجاد Code Snippets جدید

هرچند تا اینجا با استفاده از code snippets پیش فرض فضاهای نام و کلاس‌ها، یک کلاس جدید را ایجاد کردیم، اما روش ساده‌تری نیز برای انجام این‌کارها و تکمیل کنترلر وجود دارد.
برای این منظور به منوی File -> Preferences -> User snippets مراجعه کنید و سپس از لیست ظاهر شده، زبان #C را انتخاب کنید:


به این ترتیب یک قالب جدید code snippet تولید خواهد شد. در اینجا می‌خواهیم برای تولید Actionهای یک کنترلر نیز یک code snippet جدید را تهیه کنیم:
{
    "MVC Action": {
        "prefix": "mvcAction",
        "body": [
            "public IActionResult ${1:ActionName}()",
            "{",
            " $0 ",
            " return View();",
            "}"
        ],
        "description": "Creates a simple MVC action method."
    }
}
- کدهای snippet، در داخل آرایه‌ی body درج می‌شوند. هر عضو این آرایه یک سطر از کدها را تشکیل خواهد داد.
- در این آرایه، 0$ جائی است که اشاره‌گر متنی پس از درج snippet قرار می‌گیرد و 1$ به نامی که قرار است توسط کاربر تکمیل شود، اشاره می‌کند که در اینجا یک نام پیش فرض مانند ActionName را هم می‌توان برای آن درنظر گرفت.
- در اینجا prefix نامی است که اگر در صفحه تایپ شود، منوی انتخاب این code snippet را ظاهر می‌کند:



استفاده از بسته‌های Code Snippets آماده

خوشبختانه پشتیبانی جامعه‌ی توسعه دهندگان از VSCode بسیار مطلوب است و علاوه بر افزونه‌های قابل توجهی که برای آن نوشته شده‌اند، بسته‌های Code Snippets آماده‌ای نیز جهت بالابردن سرعت کار با آن وجود دارند. برای دریافت آن‌ها، به نوار ابزار عمودی که در سمت چپ صفحه وجود دارد، مراجعه کنید و گزینه‌ی extensions آن‌را انتخاب نمائید:


در اینجا اگر aspnetcore را جستجو کنید، لیست بسته‌های code snippets برچسب گذاری شده‌ی با aspnetcore ظاهر می‌شود. در همینجا اگر یکی از آن‌ها را انتخاب کنید، در سمت راست صفحه می‌توانید توضیحات آن را نیز مشاهده و مطالعه نمائید (بدون نیاز به خروج از IDE).
برای مثال wilderminds-aspnetcore-snippets را نصب کنید. پس از آن تنها کافی است mvc6 را درون یک کلاس کنترلر تایپ نمائید تا امکانات آن ظاهر شوند:


برای نمونه پس از ایجاد یک فایل خالی کنترلر، انتخاب code snippet ایی به نام mvc6-controller، سبب ایجاد یک کنترلر کامل به همراه اکشن متدهایی پیش فرض می‌شود. بنابراین معادل قالب‌های new items در نگارش کامل ویژوال استودیو در اینجا می‌توان از Code Snippets استفاده کرد.

درکل برای یافتن مواردی مشابه، بهتر است واژه‌ی کلیدی snippets را در قسمت extensions جستجو نمائید و آن‌ها صرفا مختص به #C یا ASP.NET Core نیستند.


مدیریت ارجاعات و بسته‌های نیوگت در برنامه‌های ASP.NET Core

تا اینجا مدیریت فایل‌ها و نحوه‌ی تکمیل آن‌ها را توسط code snippets، بررسی کردیم. قدم بعدی و گردش کاری مهم مورد نیاز دیگر، نحوه‌ی افزودن ارجاعات به پروژه در VSCode است.
مدیریت ارجاعات در نگارش‌های جدید ASP.NET Core، در فایل csproj برنامه انجام می‌شوند. در اینجا است که بسته‌های نیوگت جدید، ارجاعات به پروژه‌های دیگر و حتی شماره فریم ورک مورد استفاده تعیین می‌شوند.
برای نمونه بسته‌ی نیوگت DNTPersianUtils.Core را به لیست ارجاعات آن اضافه کنید:
 <PackageReference Include="DNTPersianUtils.Core" Version="2.2.0" />
بلافاصله با انجام این تغییر و ذخیره‌ی فایل csproj، گزینه‌ی بازیابی و نصب آن نیز ظاهر می‌شود:


در اینجا با کلیک بر روی لینک و یا دکمه‌ی restore ، کار دریافت این بسته و نصب آن انجام خواهد شد.
اگر علاقمند بودید تا اینکار را در خط فرمان به صورت دستی انجام دهید، دکمه‌های Ctrl+ back-tick را فشرده، تا امکانات خط فرمان درون VSCode ظاهر شوند و سپس دستور dotnet restore را صادر کنید.

روش دوم ثبت بسته‌های نیوگت در فایل csproj، مراجعه به خط فرمان (فشردن دکمه‌های دکمه‌های Ctrl+ back-tick) و صدور دستور ذیل است:
 > dotnet add package DNTPersianUtils.Core
به این ترتیب با استفاده از امکانات dotnet-cli نیز می‌توان آخرین نگارش یک بسته‌ی نیوگت را دریافت و به صورت خودکار به پروژه اضافه نمود. اگر نگارش خاصی مدنظر است، باید توسط پرچم version-- آن‌را در انتهای دستور مشخص کرد.


دیباگ برنامه‌های ASP.NET Core در VSCode

در قسمت قبل با فرامین dotnet run و dotnet build و همچنین نحوه‌ی اجرای سریع آن‌ها آشنا شدیم. در ادامه اگر به نوار ابزار عمودی کنار صفحه‌ی آن دقت کنید، گزینه‌ی دیباگ نیز وجود دارد:


در اینجا دو نوع نحوه‌ی برپایی و اجرای برنامه را مشاهده می‌کنید که هر دو مورد در فایل vscode\launch.json. زمانیکه دیباگ پروژه را در ابتدای باز کردن آن در VSCode فعال می‌کنیم، تعریف شده‌اند.
برای بررسی آن فایل HomeController.cs را گشوده و در ابتدای متد About آن یک break point را قرار دهید (با حرکت دادن اشاره‌گر ماوس، جائیکه شماره سطرها قرار دارند، علامت درج break point ظاهر می‌شود که با کلیک بعدی، دائمی خواهد شد و برعکس):


پس از آن تنها کاری که باید انجام داد، فشردن دکمه‌ی F5 است که به معنای اجرای برنامه به همراه اتصال دیباگر به آن می‌باشد (در قسمت قبل، Ctrl+F5 را بررسی کردیم که به معنای اجرای برنامه، بدون اتصال دیباگر به آن است).
در ادامه پس از اجرای برنامه، بر روی لینک About کلیک کنید تا اکشن متد آن اجرا شود. بلافاصله کنترل کار به VSCode بازگشته و سطری که بر روی آن break-point قرار داده بودیم، ظاهر می‌شود:


اطلاعات بیشتر آن در برگه‌ی دیباگ ظاهر می‌شوند و در کل تجربه‌ی کاربری آن همانند سایر IDEهایی است که تاکنون با آن‌ها کار کرده‌اید.

یک نکته: در اینجا در داخل فایل‌های View (فایل‌های razor) نیز می‌توان break point قرار داد.


برپایی یک Watcher Build

ابزار ویژه‌ای به همراه ابزارهای Build مخصوص پروژه‌های NET Core. وجود دارد به نام watcher که تغییرات پوشه‌های برنامه را تحت نظر قرار داده و با هر تغییری، پروژه را کامپایل می‌کند. به این ترتیب به سرعت می‌توان آخرین تغییرات برنامه را در مرورگر بررسی کرد. برای نصب آن، تنظیم ذیل را به فایل csproj برنامه اضافه کنید:
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />
  </ItemGroup>
پس از آن دکمه‌های Ctrl+ back-tick را فشرده تا امکانات خط فرمان، درون VSCode ظاهر شود و سپس دستور dotnet restore را صادر کنید.

اکنون برای استفاده‌ی از آن تنها کافی است دستور ذیل را صادر کنید:
 >dotnet watch run
?[90mwatch : ?[39mStarted
Hosting environment: Production
Content root path: D:\vs-code-examples\FirstAspNetCoreProject
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
همانطور که مشاهده می‌کنید، پروژه را کامپایل کرده و بر روی پورت 5000 ارائه می‌دهد. به علاوه از این پس با هر تغییری در فایل‌های #C پروژه، این کامپایل خودکار بوده و نیازی به تکرار این عملیات نیست.

یک نکته: اینبار برای دیباگ برنامه، باید گزینه‌ی attach را انتخاب کرد:


اگر بر روی دکمه‌ی سبز رنگ کنار آن کلیک کنید، لیست پروسه‌های دات نتی ظاهر شده و در این حالت می‌توانید دیباگر را به پروسه‌ی dotnet exec ایی که به dll برنامه اشاره می‌کند، متصل کنید (و نه پروسه‌ی dotnet watch run که در حقیقت پروسه‌ی dotnet exec را مدیریت می‌کند).
مطالب
آشنایی و بررسی ابزار Glimpse
در مطلب MiniProfiler ابزار مانیتور کارآیی وب سایت‌ها را بررسی کردیم. اما ابزار Glimpse هم جزو ابزار‌های حرفه‌ای است که در مطلبی آقای هانسلمن در سایت خود به آن پرداخته بودند. اما دیدم جای یک مطلب فارسی در این رابطه خالی است.


Glimpse چیست؟
glimpse یک ابزار حرفه‌ای برای نمایش زمان اجرای کدها، پیکربندی سرور، درخواست‌های وب، اشکال زدایی و بررسی کارآیی وب سایت‌های MVC و Web Forms می‌باشد. البنه بدون آنکه در کد‌های پروژه شما تغییری ایجاد نماید.
ابتدا در پنجره Nuget عبارت glimpse را جستجو و آن را نصب نمایید:


کتابخانه‌های زیادی برای این ابزار آماده شده‌اند:

  • کتابخانه Glimpse Core
    که هسته اصلی ابزار است، حتما باید نصب شود.
  • کتابخانه Glimpse ASP.NET
    برای بررسی وب سایت‌های نوشته شده با ASP.NET Web Forms استفاده می‌شود. البته بری Mvc هم لازم است.
  • کتابخانه Glimpse Mvc2, Glimpse Mvc3، Glimpse Mvc4
    برای بررسی وب سایت‌های نوشته شده با ASP.NET Mvc
  • کتابخانه Glimpse Ado
    برای بررسی و نمایش زمان کوئری بر روی پایگاه داده
  • کتاخانه Glimpse EF4.3، Glimpse EF5، Glimpse EF6
    برای زمانیکه از نگارش‌های مختلف Entity Framework استفاده می‌نماییم
پس از نصب کتابخانه‌های مورد نیاز، پروژه را rebuild و سپس اجرا نمایید. برای فعال کردن glimpse آدرس http://{your-site}/Glimpse.axd را اجرا کنید تا صفحه تنظیمات آن فعال شوند و سپس بر روی گزینه Turn Glimpse on، کلیک کنید. همچنین با گزینه Turn Glimpse off می‌توانید آن را غیر فعال نمایید.

علاوه بر این، تنظیمات استاندارد این ابزار قابل تغییر است.
به صفحه اصلی سایت برگشته و صفحه را بروز رسانی کنید. ابزار glimpse در پایین مرورگر نمایش داده می‌شود.


این ابزار شامل سه قسمت است:
  • HTTP
    اطلاعات Request و زمان پاسخ و اطلاعات سرور نمایش داده می‌شود
  • HOST
    اطلاعات صفحه اجرا شده، زمان پاسخ و تعداد کوئری‌های اجرا شده و زمان آن نمایش داده می‌شوند
  • AJAX
    اطلاعات درخواست‌های اجکسی این صفحه و تعداد آن نمایش داده می‌شوند
بر روی هر یک از این قسمت‌ها با حرکت ماوس، جزئیات آن قسمت نمایش داده می‌شود.

اگر بر روی آیکون g ابزار کلیک کنید، همچون developer tools مرورگر‌ها باز شده و دارای زبانه‌های متعددی می‌باشد. مثلا اگر پلاگین ado و ef5 نصب باشند، در زبانه SQL می‌توانید کوئری‌های اجرا شده و زمان مصرف شده آن‌ها را مشاهده نمایید

زبانه دیگر Timeline است که زمان انقیاد اشیاء و رویدادها را بصورت گرافیکی نمایش می‌دهد.

در مطلب بعدی به جزئیات بیشتری از این ابزار می‌پردازم.
مطالب
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
مطالب
روش های ارث بری در Entity Framework - قسمت دوم
در قسمت اول در مورد روش TPT خواندید. در این قسمت به روش TPH می‌پردازیم.

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


به نظر می‌رسد که این جدول با جداول قسمت قبل شباهتی دارد. بله! فیلدهای جداول مثال قبل در این جدول آمده اند.
  • فیلدهای FirstName و LastName از جدول Persons
  • فیلد HireDate از جدول Instructors
  • فیلد EnrollmentDate، Credits و Degree از جدول Students
  • فیلد AdminDate از جدول Admins
  • فیلدهای BusinessCredits و Discipline از جدول BusinessStudents

یک فیلد با نام PersonCategory نیز اضافه شده است که «مقداری عددی» می‌پذیرد و برای «متمایز کردن رکوردها» استفاده می‌شود:

  • 1 ، نمایانگر Student
  • 2 ، نمایانگر Instructor
  • 3 ، نمایانگر Admin
  • 4 ، نمایانگر BusinessStudent

از این جدول می‌خواهیم به مدل قسمت اول برسیم اما این بار با استفاده از روش TPH. در شکل زیر، جدول Persons به صورت مدل شده در برنامه نشان داده شده است.

حال باید چهار موجودیت مشتق شده را به مدل اضافه کنیم. موجودیت‌های Student، Instructor و Admin را با کلیک راست بر روی EDM Designer و انتخاب گزینه‌ی Entity از منوی Add New ایجاد کنید. در فرمی که باز می‌شود، از قسمت  Person ،Base type را انتخاب کنید. موجودیت BusinessStudent را نیز ایجاد و موجودیت پایه‌ی آن را موجودیت Student در نظر بگیرید. مدل ایجاد شده تا اینجای کار در شکل زیر مشخص است.

حال باید خصیصه‌های موجودیت Person را به موجودیت‌های مشتق شده منتقل کرد. بدین منظور، هر خصیصه از موجودیت Person را انتخاب، کلیدهای Ctrl+X را فشار دهید، سپس بر روی قسمت Properties موجودیت مشتق شده‌ی مورد نظر رفته و کلیدهای Ctrl+V را فشار دهید. نتیجه در شکل زیر نشان داده شده است.

اکنون زمان آن رسیده است تا جدول متناظر با هر یک از موجودیت‌های مشتق شده را معرفی کنیم. تمامی موجودیت‌های مشتق شده از جدول Persons استفاده می‌کنند. بر روی هر یک از آنها کلیک راست کرده و گزینه‌ی Table Mapping را انتخاب کنید. پنجره  ی Mapping Details نشان داده می‌شود. ابتدا بر روی عبارت Add a Table or View و سپس بر روی نشانگر رو به پایینی که کنار آن ظاهر می‌شود کلیک کنید (شکل زیر). 

آیتم Persons را انتخاب کنید. اکنون باید فیلد تفکیک کننده‌ی رکوردها را مشخص کنیم. برای این حالت باید یک شرط ایجاد نمود. در همان پنجره‌ی Mapping Details، عبارتی با عنوان Add a Condition وجود دارد. بر روی آن کلیک و در لیستی که ظاهر می‌شود، آیتم PersonCategory را انتخاب کنید (شکل زیر).

سپس در ستون Value/Property، مقدار آن را "1" قرار دهید (شکل زیر).

تناظر میان موجودیت و جدول Persons و مقداردهی مناسب به فیلد متمایز کننده را برای تمامی موجودیت‌های مشتق شده انجام دهید. دلیل این کار این است که EF بداند هر رکورد در چه زمانی باید به چه موجودیتی تبدیل شود. دقت کنید که پیشتر به مقدار فیلد متمایز کننده برای هر موجودیت اشاره کردیم. نکته‌ی مهم اینکه یک شرط نیز باید برای موجودیت Person ایجاد و مقدار فیلد متمایز کننده‌ی آن را "صفر" تعریف کنید.
مثال ما آماده است. آن را امتحان می‌کنیم.

using (PersonDbEntities context = new PersonDbEntities())
{

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

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

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

    Console.ReadLine();
}
چه کدهای آشنایی! بله، این کدها همان کدهایی هستند که برای مثال روش TPT استفاده کردیم و بدون کوچکترین تغییری در اینجا نیز قابل استفاده هستند.

مزایای روش TPH
  • سرعت بالای عملیات CRUD، به دلیل وجود تمامی داده‌ها در یک جدول
  • تعداد جداول در پایگاه داده، کم و مدیریت آنها آسان‌تر است

معایب روش TPH
  • افزونگی داده ها. مقادیر برخی ستون‌ها برای بعضی از رکوردها، حاوی مقدار NULL است و تعداد این ستون‌ها به تعداد زیر مجموعه‌ها ارتباط دارد
  • عیب اول، باعث می‌شود تا در صورتی که داده‌ها به صورت دستی تغییر پیدا کنند، جامعیت داده‌ها از بین برود
  • افزایش بی دلیل حجم داده ها
  • اضافه و حذف کردن موجودیت‌ها به مدل، عملی زمانبر و پیچیده است
سومین روش، TPC است که در EF Designer، پشتیبانی از پیش تعبیه شده برای آن وجود ندارد و باید از طریق ویرایش فایل edmx. به آن رسید. استفاده از این روش توصیه نمی‌شود.

مطالب
بررسی بارگذاری داده ها در انبار های داده و معرفی الگوهای بکار رفته در آن

مقدمه

در لینکی که چندی پیش به اشتراک گذاشته بودم؛ به مطلبی تحت این عنوان اشاره شده بود: "آیا از KPI باید به انباره داده و هوش تجاری رسید؟" (بر گرفته از وبلاگ آقای جام سحر) که در آن به موانع پیش روی انجام پروژه‌های BI در ایران پرداخته شده است.
این مقاله بر گرفته از فصل سوم یکی از White Paper‌های ماکروسافت با عنوان Microsoft EDW Architecture, Guidance and Deployment Best Practices می‌باشد. که به شرح عملیات Loading در فاز ETL می‌پردازد. از آنجا که به منظور پیاده سازی این نوع پروژه‌ها معمولاً در ایران برون سپاری صورت می‌گیرد و مدیران شرکت‌ها بیشتر درگیر سیستم‌های OLTP هستند و مجری پروژه (شرکت پیمانکار) معمولاً کوتاهترین مسیر را جهت انجام پروژه انتخاب می‌کند(و امروزه نیک میدانیم که "انتخاب مسیرهای کوتاه در زمان کم می‌تواند به پیچیدگی‌های بسیار جدی در دراز مدت منجر شود!") و همچنین از آنجا که متاسفانه به دلیل عدم ثبات مدیریت در ایران معمولاً "مدیریت برای تحویل پروژه تحت فشار است و نه برای مسائل پشتیبانی " و مسائل دیگری از این دست؛ چنانچه در تحویل گیری محصول به درستی تست نرم افزار صورت نگیرد، در نظر گرفتن موارد زیر:
Verification: Are we building the product right? ~ Software correctly implements a specific function
  Validation: Are we building the right product? ~  Software is traceable to customer requirements
پروژه با شکست مواجه می‌شود و انتظارات مدیران بهره بردار را برآورده نمی‌کند. به هر روی در این مقاله به ترجمه مطالب زیر پرداخته می‌شود، توصیه میکنم در صورتی که با خواندن متن انگلیسی مشکلی ندارید، اصل مقاله مذکور خوانده شود.
1- Full Load vs Incremental Load
2- Detecting Net Changes
2-1- Pulling Net Changes – Last Change Column
2-2- Pulling Net Changes – No Last Change Column
2-3- Pushing Net Changes
3- ETL Patterns
3-1- Destination load Patterns
3-2- Versioned Insert Pattern
3-3- Update Pattern
3-4- Versioned Insert: Net Changes 
4- Data Integration Best Practices
4-1- Basic Data Flow Patterns
4-1-1- Update Pattern
4-1-2- Update Pattern – ETL Framework
4-1-3- Versioned Insert Pattern
4-1-4- Update vs. Versioned Insert
4-2- Dimension Patterns
4-3- Fact Table Patterns
4-3-1- Managing Inferred Members

1- Full Load vs Incremental Load

نسل‌های اولیه DW (اختصار Data Warehouse) به شکل Full Loads پیاده سازی می‌شدند، به این طریق که هر بار عملیات بارگذاری صورت می‌گرفت، DW از نو دوباره ساخته می‌شد. شکل زیر مراحل مختلف انجام شده در این روش را نمایش می‌دهد:

پروسه Full Load شامل مراحل زیر بود:

  1. Drop Indexes: از آنجا که Index‌ها زمان بارگذاری را افزایش می‌دادند، این عمل صورت می‌پذیرفت.
  2. Truncate Tables: تمامی رکوردهای موجود در جداول حذف می‌شدند.
  3. Bulk Copy
  4. Load Data
  5. Post Process: شامل عملیاتی نظیر شاخص گذاری روی داده هایی است که اخیراً بارگذاری شده اند و....

روی  هم رفته Full Load مسئله ای مشکل ساز بود، زیرا نیاز به زمانی برای بارگذاری مجدد داده‌ها داشت و مسئله‌ی مهم‌تر نداشتن امکان دستیابی به گزارشاتی تاریخچه ای با ماهیت زمان برای مشتریان کسب وکار بود. به این دلیل که همواره یک کپی از آخرین داده‌های موجود در سیستم عملیاتی درون DW قرار می‌گرفت؛ که با بکارگیری Full Load اغلب قادر به ارائه‌ی این نوع از گزارشات نبودیم، بدین ترتیب سازمان‌ها به نسل دوم روی آورند که در این دیدگاه از مفهوم Incremental Load استفاده می‌شود. اشکال زیر مراحلی که در این روش انجام می‌شود را نمایان می‌سازد:

Incremental Load with an Extract In area

Incremental Load without an Extract In area

مراحل Incremental Load شامل:

  1. بارگذاری تغییرات نسبت به آخرین فرآیند بارگذاری انجام شده
  2. درج / بروزرسانی تغییرات درون Production area
  3. درج / بروزرسانی Consumption area نسبت به Production area


تفاوت‌های اصلی میان Full Load و Incremental Load در این است که در Incremental Load:

  • نیازی به پردازش‌های اضافی جهت حذف شاخص ها، پاک کردن تمامی رکورد‌های جداول و ساخت مجدد شاخص‌ها نیست.
  • البته نیاز به رویه ای جهت شناسایی تغییرات می‌باشد.
  • و همچنین نیاز به بروزرسانی  بعلاوه درج رکوردهای جدید نیز می‌باشد.

ترکیب این عوامل برای ساخت Incremental Load کارآمد تر، منجر به پیچیده‌تر شدن پیاده سازی و نگهداری آن نیز می‌شود.

2- Detecting Net Changes

فرآیند لود افزایشی ETL، بایست قادر به شناسائی رکورد‌های تغییریافته در مبداء باشد، که این عمل با استفاده از هر یک از تکنیک‌های Push یا Pull انجام می‌شود.

  • در تکنیک Pull، فرآیند ETL رکوردهای تغییریافته در مبداء را انتخاب می‌کند:
  • ایده‌آل وجود داشتن یک ستون Last Changed در سیستم مبداء است؛ که از آن می‌توان جهت انتخاب رکوردهای تغییر یافته استفاده نمود.
  • چنانچه ستون Last Changed وجود نداشته باشد، تمامی رکوردهای مبداء باید با رکورد‌های مقصد مقایسه شود.
  • در تکنیک Push، مبداء تغییرات را شناسائی می‌کند و آنها را به سمت مقصد Push می‌کند؛ این درخواست می‌تواند توسط فرآیند ETL انجام شود.
از آنجایی که پردازش ETL معمولاً در زمان هایی که Peak کاری وجود ندارد، اجرا می‌شود، استفاده از مکانیسم Pull برای شناسایی تغییرات نسبت به مکانسیم Push ارجحیت دارد.


2-1- Pulling Net Changes – Last Change Column

بیشتر جداول در سیستم‌های مبداء حاوی ستون هایی هستند که زمان ایجاد و یا اصلاح رکوردها را ثبت می‌کنند. در نوع دیگری از سیستم‌های مبداء ستونی با مقدار عددی وجود دارد، که هر زمان رکوردی تغییر یافت به آن ستون مقداری اضافه می‌شود. هر دوی این تکنیک‌ها به فرآیند ETL اجازه می‌دهند، بطور کارآمدی رکوردهای تغییریافته را انتخاب کند. (با مقایسه، بیشترین مقدار قرار گرفته در آن ستون؛ که در طول آخرین اجرای فرآیند ETL بدست آمده است). نمونه ای از جداول سیستم مبداء که دارای تغییرات زمانی است در شکل زیر نمایش داده می‌شود.

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

2-2- Pulling Net Changes – No Last Change Column

شکل زیر گردش فرآیند را هنگامی که ستون Last Change وجود ندارد؛ نمایش می‌دهد.


این گردش فرآیند شامل:
  1. Join میان مبداء و مقصد با استفاده از یک دستور Left Outer Join است.
  2. تمامی رکورد‌های مبداء که در مقصد وجود ندارند، پردازش می‌شوند.
  3. زمانی که رکوردی در مقصد وجود داشته باشد مقادیر داده‌های مبداء و مقصد مقایسه می‌شوند.
  4. تمامی رکوردهای مبداء که تغییر یافته اند پردازش می‌شوند.
از آنجایی که تمامی رکورد‌ها پردازش می‌شوند، این روش بویژه برای جداول حجیم؛ روش کارآمدی نیست.

2-3- Pushing Net Changes

دو متد متداول Push وجود دارد که در تصویر زیر نمایش داده  شده است.

تفاوت این دو روش به شرح زیر است:

  1. در سناریو اول (شکل سمت چپ)؛ بانک اطلاعاتی رابطه ای سیستم مبداء Transaction Log را مرتب مانیتور می‌کند تا تغییرات را شناسائی کرده و در ادامه تمامی این تغییرات را در جدولی در مقصد درج می‌کند.
  2. در سناریو دوم؛ توسعه دهندگان Trigger هایی ایجاد می‌کنند تا هر زمان که رکوردی تغییر یافت، تغییرات در جدولی که در مقصد وجود دارد درج گردد.

مسئله ای که در هر دو مورد وجود دارد Load اضافه ای است؛ که روی سیستم مبداء وجود دارد و می‌تواند Performance سیستم‌های OLTP را تحت تاثیر قرار دهد. به هر روی سناریو نخست معمولاً کاراتر از سناریویی است که از Trigger استفاده می‌کند.

3- ETL Patterns

پس از شناسائی رکوردهایی که در مبداء تغییر یافته اند، نیاز داریم تا این تغییرات در مقصد اعمال شود. در این قسمت به معرفی الگوهایی که برای اعمال این تغییرات وجود دارد می‌پردازیم.

3-1- Destination load Patterns

تشخیص چگونگی اضافه نمودن تغییرات در مقصد تابع دو عامل زیر است:

  • آیا رکورد هم اینک در مقصد وجود دارد؟
  • الگوی استفاده شده برای جدول مقصد به کدام شکل است؟ (Update یا Versioned Insert)

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

3-2- Versioned Insert Pattern

Kimball Type II Slowly Changing Dimension نمونه ای از الگوی Versioned Insert است؛ که در آن نمونه ای از یک موجودیت دارای ورژن‌های متعددی است. مطابق تصویر زیر؛ این الگو به ستون‌های اضافه ای نیاز دارند که وضعیت نمونه ای از یک رکورد را نمایش دهد.


این ستون‌ها به شرح زیر هستند:

  • Start Date: زمانی که وضعیت آن نمونه از رکورد فعال می‌شود.
  • End Date: زمانی که وضعیت آن نمونه از رکورد غیر فعال می‌شود.
  • Record Status: وضعیت‌های یک رکورد را نشان می‌دهد، که حداقل به شکل Active یا Inactive است.
  • # Version: این ستون که اختیاری می‌باشد، ورژن آن نمونه از رکورد را ثبت می‌کند.


برای مثال شکل زیر؛ بیانگر وضعیت اولیه رکوردی در این الگو است:


فرض کنید که این رکورد در تاریخ March 2 , 2010 در سیستم مبداء تغییر می‌کند. فرآیند ETL این تغییر را شناسائی می‌کند و همانند تصویر زیر؛ به شکل نمونه ای ثانویه از این رکورد، اقدام به درج آن می‌کند.

توجه داشته باشید زمانی که رکورد دوم در جدول درج می‌شود، به منظور بازتاب این تغییر؛ رکورد اول به شکل زیر بروزرسانی می‌گردد:

  • End Date: تا این زمان وضعیت این رکورد فعال بوده است.
  • Record Status:که Active به Inactive تغییر پیدا می‌کند.


در برخی از پیاده سازی‌های DW عمدتاً از الگوی Versioned Insert استفاده می‌شود و هرگز از الگوی Update استفاده نمی‌شود. مزیت این استراتژی در این است که تمامی تاریخچه تغییرات ردیابی و ثبت می‌شود. به هر روی غالباً هزینه ثبت کردن این تغییرات منجر به ایجاد نسخه‌های زیادی از تغییرات می‌شود. تیم DW برای مواردی که تغییرات متاثر از گزارشات تاریخچه ای نیستند، می‌توانند الگوی Update را در نظر گیرند.

3-3- Update Pattern

الگوی Update روی رکورد موجود، تغییرات سیستم مبداء را بروزرسانی می‌کند. مزیت این روش در این است که همواره یک رکورد وجود دارد و در نتیجه باعث ایجاد Query‌های کارآمدتر می‌شود. تصویر زیر بیانگر ستون هایی است که برای پشتیبانی از الگوی Update بایست ایجاد کرد.


این ستون‌ها به شرح زیر هستند:

  • Record Status: وضعیت‌های یک رکورد را نشان می‌دهد که حداقل به شکل Active یا Inactive است.
  • # Version: این ستون که اختیاری می‌باشد، ورژن آن نمونه از رکورد را ثبت می‌کند.


موارد اصلی الگوی Update عبارتند از:

  • تاریخ ثبت نمی‌شود. ابزاری ارزشمند برای نظارت بر داده ها، تغییرات تاریخی است و زمانی که ممیزی داده رخ می‌دهد؛ می‌تواند مفید واقع شود.
  • بروزرسانی‌ها یک الگوی مبتنی بر مجموعه هستند. استفاده از بروزرسانی هر بار یک رکورد در ابزار ETL خیلی کارآمد (موجه) نیست.


یک روش دیگر برای در نظر گرفتن موارد فوق؛ اضافه کردن یک جدول برای درج ورژن‌ها به الگوی Update است که در شکل زیر نشان داده شده است.


اضافه نمودن یک جدول تاریخچه، که تمامی تغییرات سیستم مبداء را ثبت  می‌کند؛ نظارت و ممیزی داده‌ها را نیز فراهم می‌کند و همچنین بروزرسانی‌های کارآمد مبتنی بر مجموعه را برای جداول DW به ارمغان می‌آورد.

3-4- Versioned Insert: Net Changes 

این الگو غالباً در جداول حجیم Fact که بروزرسانی آنها پر هزینه است استفاده می‌شود. شکل زیر منطق استفاده شده در این الگو را نشان می‌دهد.

توجه داشته باشید در این الگو:
  • مقادیر مالی و عددی محاسبه شده؛ به عنوان یک Net Change از نمونه قبلی رکورد در جدول Fact ذخیره می‌شود.
  • هیچ گونه فعالیت Post Processing صورت نمی‌گیرد (از قبیل بروزرسانی جداول Fact پس از کامل شدن Data Flow). هدف استفاده از این الگو اجتناب از بروزرسانی روی جداول بسیار حجیم می‌باشد.
  • عدم بروزرسانی و همچنین اندازه جدول Fact زمینه ای را فراهم می‌کند که منطق شناسائی رکوردهای تغییریافته پیچیده تر  می‌شود. این پیچیدگی از آنجا ناشی می‌شود که نیاز به مقایسه رکوردهای جدول Fact آتی با جدول Fact موجود می‌باشد.

4- Data Integration Best Practices

هم اکنون پس از آشنایی با مفاهیم و الگو‌های توزیع داده‌ها به ارائه تعدادی نمونه می‌پردازیم؛ که بتوان این ایده‌ها و الگوها را در عمل پوشش داد.

4-1- Basic Data Flow Patterns

هر یک از الگوهای Update Pattern و Versioned Insert Pattern می‌توانند برای انواعی از جداول بکار روند که معروفترین آن‌ها توسط Kimball ساخته شده اند.

  • (Slowly Changing Dimension Type I (SCD I: از Update Pattern استفاده می‌کند.
  • (Slowly Changing Dimension Type II (SCD II: از Versioned Insert Pattern استفاده می‌کند.
  • Fact Table: نوع الگویی که استفاده می‌کند به نوع جدول Fact ای که Load خواهد شد بستگی دارد.

4-1-1- Update Pattern 

مطابق تصویر زیر جدولی که تنها حاوی ورژن فعلی رکورد هاست؛ از Update Dataflow Pattern استفاده می‌کند.


مواردی که در مورد این گردش کاری باید در نظر داشت به شرح زیر است:

  • این Data Flow فقط سطرهایی را به یک مقصد اضافه خواهد کرد. SSIS دارای گزینه “Table or view fast load” می‌باشد که بارگذاری‌های انبوه و سریع را پشتیبانی می‌کند.
  • درون یک Data Flow بروزرسانی  رکورد‌ها را می‌توان با استفاده از تبدیل OLE DB Command انجام داد. توجه داشته باشید خروجی‌های این تبدیل در یک دستور Update به ازای هر رکورد بکار می‌رود؛ مفهوم بروزرسانی انبوه در این Data Flow وجود ندارد. بدین ترتیب الگوی فعلی ارائه شده؛ تنها رکوردها را درج می‌کند و هرگز در این Data Flow رکوردها Update نمی‌شوند.
  • هر جدول دارای یک جدول تاریخچه است که برای ذخیره همه فعالیت‌های مرتبط با آن بکار می‌رود. یک رکورد در جدول تاریخچه زمانی درج خواهد شد؛ که رکورد مبداء در مقصد وجود داشته باشد ولی دارای مقداری متفاوت باشد.
  • راه دیگر فرستادن تغییرات رکوردها به یک جدول کاری است که پس از پایان یافتن فرآیند Update ، خالی (Truncate) می‌شود.
  • مزیت نگهداری تمامی رکوردها در یک جدول تاریخچه؛ ایجاد یک دنباله ممیزی است که می‌تواند برای نظارت بر داده‌ها به منظور نمایان ساختن موارد مطرح شده توسط مصرف کننده‌های کسب و کار استفاده شود.
  • گزینه‌های متفاوتی برای تشخیص تغییرات رکوردها وجود دارد که در ادامه به شرح آنها می‌پردازیم.


شکل زیر نمایش دهنده چگونگی پیاده سازی Update Dataflow Pattern در یک SSIS می‌باشد:


این SSIS شامل عناصر زیر است:

  • Destination table lookup:

به منظور تشخیص اینکه رکورد در جدول مقصد وجود دارد از “lkpPersonContact” استفاده می‌کنیم.

  • Change detection logic:

با استفاده از “DidRecordChange” مبداء و مقصد مقایسه می‌شوند. اگر تفاوتی بین مبداء و مقصد وجود نداشت؛ رکورد نادیده گرفته می‌شود. چنانچه بین مبداء و مقصد تفاوت وجود داشت؛ رکورد در جدول تاریخچه درج خواهد شد.

  • Detection Inserts:

رکوردها در جدول مقصد درج خواهند شد در صورتیکه در آن وجود نداشته باشند.

  • Destination History Inserts:

رکوردها در جدول تاریخچه مقصد درج خواهند شد، در صورتیکه (در مقصد) وجود داشته باشند.

پس از اتمام Data Flow یک روال Post-processing مسئولیت بروزرسانی رکوردهای جدول اصلی و رکوردهای ذخیره شده در جدول تاریخچه را بر عهده دارد که می‌تواند مطابق تصویر زیر با استفاده از یک Execute Process Task پیاده سازی شود.


PostProcess مسئولیت اجرای تمامی فعالیت‌های زیر را در این الگو برعهده دارد که شامل:

  • بروزرسانی رکوردهای جداول با استفاده از رکوردهای درج شده در جدول تاریخچه.
  • درج تمامی رکوردهای جدید (نسخه اولیه و در درون جدول تاریخچه). کلید اصلی جداولی که ستون  آنها IDENTITY است مقدار نامشخصی دارد؛ تا زمانی که درج صورت گیرد، این به معنای آن است که پیش از انتقال آنها به جدول تاریخچه نیاز است منتظر درج شدن آنها باشیم.

4-1-2- Update Pattern – ETL Framework

تصویر زیر بیانگر انجام این عملیات با استفاده از ابزارهای ETL است.
در نگاه نخستین ممکن است Data Flow از نوع اصلی خود پیچیده‌تر به نظر آید؛ که در واقع این گونه نیز هست، زیرا در فاز توسعه بیشتر Framework‌ها جهت پیاده سازی به یک زمان اضافه‌تری نیاز دارند. به هر روی این زمان جهت اجتناب از هزینه روزانه تطبیق داده‌ها گرفته خواهد شد.
مزایای حاصل شده از افزودن این منطق اضافی عبارت است از:

  • پشتیبانی از ستون هایی که کارهای ممیزی و نظارت بر داده‌ها را آسانتر می‌کنند.
  • تعداد سطرها شاخص مناسبی است که می‌تواند بهبود آن Data Flow خاص را فراهم کند. ناظر اطلاعات با استفاده از تعداد رکوردها می‌تواند ناهنجاری‌ها را شناسائی کند.

بهره برداران ETL و ناظران اطلاعات می‌توانند با استفاده از خلاصه تعداد رکوردها درک بیشتری درباره فعالیت‌های آن کسب کنند. پس از آنکه تعداد رکوردها، مشکوک به نظر آمد؛ تحقیقات بیشتری می‌تواند اتفاق افتد. (با عمیق‌تر شدن در جزئیات گزارشات)
 

4-1-3- Versioned Insert Pattern

جدولی که به صورت Versioned Insert پر شده است می‌تواند از Versioned Insert Dataflow Pattern استفاده کند. همانند شکل زیر که گردش کار در آن برای کارآئی بیشتر بازنگری شده است.


توجه داشته باشید Data Flow در این روش شامل:

  • تمامی رکوردهای جدید و تغییر یافته در جدول Versioned Insert قرار می‌گیرند.
  • این روش دارای Data Flow ساده‌تری نسبت به الگوی Update می‌باشد.

شکل زیر SSIS versioned insert data flow pattern را نشان می‌دهد:
 

تعدادی نکته در Data Flow فوق وجود دارد که عبارتند از:

  • در شیء “lkpDimGeography” گزینه “Redirect rows to no match output” با مقدار “Ignore Failures” تنظیم شده است.
  • شیء “DidRecordChange” بررسی می‌کند چنانچه ستون‌های مبداء و مقصد یکسان باشند، آیا کلید اصلی جدول مقصد Not Null است. اگر این عبارت True ارزیابی شود، رکورد نادیده گرفته می‌شود.
  • منطق شناسائی تغییرات دربردارنده تغییرات ستون داده ای در مبداء نمی‌باشد.
  • ستون و تعداد رکوردها مشابه با Data Flow قبلی (ETL Framework) می‌باشد.

4-1-4- Update vs. Versioned Insert

الگوی Versioned Insert نسبت الگوی Update دارای پیاده سازی ساده‌تر و فعالیت‌های I/O کمتری است. از منظر دیگر، جدولی که از الگوی Update استفاده می‌کند، دارای تعداد رکوردهای کمتری است که می‌تواند به معنای Performance بهتر نیز تعبیر شود. ممکن است سوالی مطرح شود، اینکه چرا برای انجام کار به جدول تاریخچه نیاز است؛ این جدول را که نمی‌توان Truncate نمود، پس چرا به منظور بروزرسانی از جدول اصلی استفاده می‌شود؟ پاسخ این پرسش در این است که جدول تاریخچه، ناظر اطلاعات و ممیزین داده را قادر می‌سازد، تغییرات در طول زمان را پیگیری نمایند.
 

4-2- Dimension Patterns

بروزرسانی Dimension موارد زیر را شامل می‌شود:

  • پیگیری تاریخچه
  • انجام بروزرسانی
  • تشخیص رکوردهای جدید
  • مدیریت surrogate keys

چنانچه با یک Dimension کوچک مواجه هستید (با مقدار هزاران رکورد یا کمتر، که با صدها هزار رکورد یا بیشتر ضدیت دارد)،  می‌توانید از تبدیل “Slowly Changing Dimension” که بصورت Built-in در SSIS موجود است، استفاده نمائید. به هر روی با آنکه این تبدیل چندین ویژگی محدودکننده Performance دارد، اغلب کارآمدتر از پروسسه هایی که توسط خودتان ایجاد می‌شود. در واقع فرآیند بارگذاری در جداول Dimension با مقایسه داده‌ها بین مبداء و مقصد انجام می‌شود. به طور معمول مقایسه روی یک ورژن جدید و یا مجموعه ای از سطرهای جدید یک جدول با مجموعه داده‌های موجود در جدول متناظرش صورت می‌گیرد. پس از تشخیص چگونگی تغییر در داده ها، یک سری عملیات درج و بروزرسانی انجام می‌شود. شکل زیر نمونه ای از پردازش سریع در Dimension را نمایش می‌دهد؛ که شامل مراحل اساسی زیر است:

  • منبع فوقانی سمت چپ، رکوردها را در یک SSIS از یک سیستم مبداء (یا یک سیستم میانی) به شکل Pull دریافت می‌کند. منبع فوقانی سمت راست، داده‌ها را از خود جدول Dimension به شکل Pull دریافت می‌کند.
  • با استفاده از Merge Join رکوردها از طریق Source Key شان مقایسه می‌شوند. (در شکل بعدی جزئیات این مقایسه نمایش داده شده است.)
  • با استفاده از یک Conditional Spilt داده‌ها ارزیابی می‌شوند؛ سطرها یا مستقیماً در جدول Dimension درج می‌شوند (منبع تحتانی سمت چپ) و یا در یک جدول عملیاتی (منبع تحتانی سمت راست) جهت انجام بروزرسانی درج می‌شوند.
  • در گام پایانی (که نمایش داده نشده) مجموعه ای از بروزرسانی بین جدول عملیاتی و جدول Dimension صورت می‌گیرد.

 

با Merge Join ارتباطی بین رکوردهای مبداء و رکوردهای مقصد برقرار می‌شود. (در این مثال “CustomerAlternateKey”). هنگامی که از این دیدگاه استفاده می‌کنید، خاطر جمع شوید که نوع Join با مقدار “Left outer join” تنظیم شده است؛ بدین ترتیب قادر هستید تا رکوردهای جدید را از مبداء تشخیص دهید؛ از آنجا که هنوز در جدول Dimension قرار نگرفته اند.


گام پایانی به منظور تشخیص اینکه آیا رکورد، جدید یا تغییر یافته است (یا بلاتکلیف است)، مقایسه داده هاست. شکل زیر نمایش می‌دهد چگونه این ارزیابی با استفاده از تبدیل “Conditional Spilt” صورت می‌گیرد.


Conditional Spilt مستقیماً با استفاده از یک Adapter تعریف شده روی مقصد یا یک جدول کاری بروزرسانی که از یک Adapter تعریف شده روی مقصد استفاده می‌کند؛ توسط مجموعه دستور Update زیر، رکوردها را در جدول Dimension قرار می‌دهد. دستور Update زیر مستقیماً با استفاده از روش Join روی جدول Dimension و جدول کاری، مجموعه ای را بصورت انبوه بروزرسانی می‌کند.

UPDATE AdventureWorksDW2008R2.dbo.DimCustomer
    SET AddressLine1 = stgDimCustomerUpdates.AddressLine1
    , AddressLine2 = stgDimCustomerUpdates.AddressLine2
    , BirthDate = stgDimCustomerUpdates.BirthDate
    , CommuteDistance = stgDimCustomerUpdates.CommuteDistance
    , DateFirstPurchase = stgDimCustomerUpdates.DateFirstPurchase
    , EmailAddress = stgDimCustomerUpdates.EmailAddress
    , EnglishEducation = stgDimCustomerUpdates.EnglishEducation
    , EnglishOccupation = stgDimCustomerUpdates.EnglishOccupation
    , FirstName = stgDimCustomerUpdates.FirstName
    , Gender = stgDimCustomerUpdates.Gender
    , GeographyKey = stgDimCustomerUpdates.GeographyKey
    , HouseOwnerFlag = stgDimCustomerUpdates.HouseOwnerFlag
    , LastName = stgDimCustomerUpdates.LastName
    , MaritalStatus = stgDimCustomerUpdates.MaritalStatus
    , MiddleName = stgDimCustomerUpdates.MiddleName
    , NumberCarsOwned = stgDimCustomerUpdates.NumberCarsOwned
    , NumberChildrenAtHome = stgDimCustomerUpdates.NumberChildrenAtHome
    , Phone = stgDimCustomerUpdates.Phone
    , Suffix = stgDimCustomerUpdates.Suffix
    , Title = stgDimCustomerUpdates.Title
    , TotalChildren = stgDimCustomerUpdates.TotalChildren
FROM AdventureWorksDW2008.dbo.DimCustomer DimCustomer
  INNER JOIN dbo.stgDimCustomerUpdates ON
DimCustomer.CustomerAlternateKey = stgDimCustomerUpdates.CustomerAlternateKey

4-3- Fact Table Patterns

جداول Fact به پردازش‌های منحصر به فردی نیازمند هستند، نخست به کلیدهای Surrogate جدول Dimension نیاز دارند تا Measure‌های محاسبه شدنی را بدست آورند. این اعمال از طریق تبدیلات Lookup، Merge Join و Derived Column صورت می‌گیرد. با بروزرسانی ها، تفاضل رکورد‌ها و یا Snapshot بیشتر این فرآیندهای دشوار انجام می‌شوند.

4-3-1- Inserts

روی اغلب جداول Fact عمل درج صورت می‌گیرد؛ که کار متداولی در جدول Fact می‌باشد. شاید ساده‌ترین کار که در فرآیند ساخت ETL صورت می‌گیرد، عملیات درج روی تنها تعدادی از جدول Fact می‌باشد. درج کردن در صورت لزوم بارگذاری انبوه داده ها، مدیریت شاخص‌ها و مدیریت پارتیشن‌ها را شامل می‌شود.

4-3-2- Updates

بروزرسانی روی جداول Fact معمولاً به یکی از سه طریق زیر انجام می‌گیرد:

  • از طریق یک تغییر یا بروزرسانی رکورد
  • از طریق یک دستور Insert خنثی کننده (Via an Insert of a compensating transaction)
  • با استفاده از یک SQL MERGE


در موردی که تغییرات با فرکانس کمی روی جدول Fact صورت می‌گیرد و یا فرآیند بروزرسانی قابل مدیریت است؛ ساده‌ترین روش انجام یک دستور Update روی جدول Fact می‌باشد. نکته  مهمی که هنگام انجام بروزرسانی باید به خاطر داشته باشید، استفاده از روش بروزرسانی مبتنی بر مجموعه است؛ به همان طریق که در قسمت الگوهای Dimension ذکر آن رفت.
در طریقی دیگر (درج compensating) می‌توان اقدام به درج رکورد تغییر یافته نمود، تا ترجیحاً بروزرسانی روی آن صورت گیرد. این استراتژی به سادگی داده‌های جدول Fact میان سیستم مبداء و مقصد را که تغییر یافته اند، به صورت یک رکورد جدید درج خواهد کرد. تصویر زیر مثالی از اجرای موارد فوق را نمایش می‌دهد.
 

در آخرین روش از یک دستور SQL MERGE استفاده می‌شود که در آن با استفاده از ادغام و مقایسه، تمامی داده‌های جدید و تغییر یافته جدول Fact، درج و یا بروزرسانی می‌شوند. نمونه ای از استفاده دستور Merge به شرح زیر است:

MERGE dbo.FactSalesQuota AS T
USING SSIS_PDS.dbo.stgFactSalesQuota AS S
ON T.EmployeeKey = S.EmployeeKey
AND T.DateKey = S.DateKey
WHEN MATCHED AND BY target
THEN INSERT(EmployeeKey, DateKey, CalendarYear, CalendarQuarter, SalesAmountQuota)
VALUES(S.EmployeeKey, S.DateKey, S.CalendarYear, S.CalendarQuarter, S.SalesAmountQuota)
WHEN MATCHED AND T.SalesAmountQuota != S.SalesAmountQuota
THEN UPDATE SET T.SalesAmountQuota = S.SalesAmountQuota
;
اشکال این روش Performance است؛ گرچه این دستور به سادگی عملیات درج و بروزرسانی را انجام می‌دهد ولی به صورت سطر به سطر عملیات انجام می‌شود (در هر زمان یک سطر). در موقعیت هایی که با مقدار زیادی داده مواجه هستید، اغلب بهتر است به صورت انبوه عملیات درج و به صورت مجموعه عملیات بروزرسانی انجام گیرد.

4-3-3- Managing Inferred Members

زمانیکه یک ارجاع در جدول Fact به یک عضو Dimension که هنوز بارگذاری نشده‌است بوجود  آید؛ یک Inferred Member تعبیر می‌شود. به سه طریق می‌توان این Inferred Member‌ها را مدیریت نمود:

  • رکوردهای جدول Fact پیش از درج اسکن شوند؛ ایجاد هر Inferred Member در Dimension و سپس بارگذاری رکوردها در جدول Fact
  • در طول عملیات بارگذاری روی Fact؛ هر رکورد مفقوده شده به یک جدول موقتی ارسال شود، رکوردهای مفقوده شده به Dimension اضافه شود، در ادامه مجدداً آن رکوردهای Fact در جدول Fact بارگذاری شوند.
  • در یک Data Flow زمانی که یک رکورد مفقود شده، بلاتکلیف تعبیر می‌شود؛ آن زمان یک رکورد به Dimension اضافه شود و Surrogate Key بدست آمده را برگردانیم؛ سپس Dimension بارگذاری شود.


شکل زیر این موارد را نمایش می‌دهد:

نظرات مطالب
افزودن یک DataType جدید برای نگه‌داری تاریخ خورشیدی - 1
با درود
New Project -> Database -> SQL Server -> Visual C# SQL CLR Database Project 
- تنظیمات اتصال به پایگاه داده ها 
- انتخاب دکمه Yes
- Add New Item -> User Defined Type 
- کپی کدها
- استفاده از Deploy در منوی Build یا استفاده از روشی که در بخش دوم نوشتار آموزش داده خواهد شد.
نظرات مطالب
آشنایی با NHibernate - قسمت هفتم
بحث entity framework با NHibernate متفاوت است.
در NHibernate این متد BuildSessionFactory فوق کار بارگذاری متادیتا و نگاشت‌ها و غیره رو انجام میده؛ یعنی خودکار نیست و اگر قرار باشه به ازای هر کوئری یکبار فراخوانی شود اصلا نمی‌شود با برنامه کار کرد چون به شدت کند خواهد بود. به همین جهت کش کردن آن‌را با استفاده از الگوی singleton به صورت فوق تنها یکبار باید انجام داد. یکبار در طول عمر برنامه باید نگاشت‌ها صورت گیرد و پس از آن بارها و بارها از آن استفاده شود چون قرار نیست در طول عمر یک برنامه در حال اجرا تغییری کند.
در حالیکه در Entity framework اینکار (بارگذاری متادیتا و نگاشت‌های تعریف شده) به صورت خودکار زمانیکه برنامه برای بار اول اجرا می‌شود رخ داده و به صورت خودکار هم کش می‌شود. ماخذ: (+) ؛ بنابراین برای مدیریت آن اصلا لازم نیست شما کاری انجام دهید.
فقط در Entity framework یک لایه بسیار کم هزینه به نام ObjectContext وجود دارد که توصیه شده در برنامه‌های ASP.NET به ازای هر درخواست ایجاد و تخریب شود که می‌توانید از مقاله فوق ایده بگیرید و اصلا نباید کش شود یا هر بحث دیگری. ماخذ: (+) ؛ حتی اگر اینکار را هم انجام ندادید مهم نیست چون سربار بسیار کمی دارد.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
یک نکته‌ی تکمیلی در مورد نصب نگارش‌های جدید NET Core.

پس از نصب به روز رسانی‌های NET Core.، دستور ذیل را در خط فرمان اجرا کنید:
> dotnet --version
1.0.0-preview2-003131
حاصل آن، عبارتی است که در فایل global.json درج خواهد شد. پس از این تغییر:
{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-preview2-003131"
  }
}
نیاز است یکبار Solution را بسته و مجددا باز کنید. پس از آن به صورت خودکار، بازیابی بسته‌های مرتبط شروع می‌شوند.
به علاوه تنها در این حالت است که اگر به برگه‌ی Updates نیوگت مراجعه کنید، به روز رسانی‌های جدید را مشاهده خواهید کرد:



بنابراین تازمانیکه فایل global.json را با شماره‌ی SDK جدید به روز رسانی نکنید، نیوگت، بسته‌های به روز شده‌ی مرتبط را دریافت نخواهد کرد.

به علاوه اگر Solution شما دارای چندین پروژه است، بهتر است دستور ذیل را در کنسول پاورشل نیوگت وارد کنید تا تمام آن‌ها را به یکباره به روز رسانی کند:
PM> update-package
پس از آن اگر خطای ذیل را دریافت کردید:
Can not find runtime target for framework '.NETCoreApp,Version=v1.0' compatible with one of the target runtimes: 'win10-x64, win81-x64, win8-x64, win7-x64'
در فایل‌های project.json، سطر ناقص ذیل را یافته:
"Microsoft.NETCore.App": "vX",
و آن‌را به مدخل کامل ذیل تبدیل کنید:
"Microsoft.NETCore.App": {
      "version": "vX",
      "type": "platform"
    },