مطالب
آشنایی با CLR: قسمت یازدهم
انتشار نوع‌ها  (Types) به یک ماژول
در این قسمت به نحوه‌ی تبدیل سورس به یک فایل قابل انتشار می‌پردازیم. کد زیر را به عنوان مثال در نظر بگیرید:
public sealed class Program {
  public static void Main() {
    System.Console.WriteLine("Hi");
  }
}
این کد یک ارجاع به نام کنسول دارد که این ارجاع، داخل فایلی به نام mscorlib.dll قرار دارد. پس برنامه‌ی ما نوعی را دارد، که آن نوع توسط شرکت دیگری پیاده سازی شده است. برای ساخت برنامه‌ی کد بالا، کدها را داخل فایلی با نام program.cs قرار داده و با دستور زیر در خط فرمان آن را کامپایل می‌کنیم:
csc.exe /out:Program.exe /t:exe /r:MSCorLib.dll Program.cs
کد بالا با سوئیچ اول می‌گوید که فایلی را با نام program.exe درست کن و با سوئیچ دوم می‌گوید که این برنامه از نوع کنسول هست.
موقعیکه کامپایلر فایل سورس را مورد بررسی قرار می‌دهد، متوجه متد writerline می‌گردد؛ ولی از آنجاکه این نوع توسط شما ایجاد نشده است و یک نوع خارجی است، شما باید یک مجموعه از ارجاعات را به کمپایلر داده تا آن نوع را در آن‌ها بیابد. ارائه این ارجاعات به کامپایلر توسط سوئیچ r/ که در خط بالا استفاده شده است، صورت می‌گیرد.


mscorlib یک فایل سورس است که شامل همه‌ی نوع‌ها، از قبیل int,string,byte و خیلی از نوع‌های دیگر می‌شود. از آنجائیکه استفاده‌ی از این نوع‌ها به طور مکرر توسط برنامه نویس‌ها صورت می‌گیرد، کمپایلر به طور خودکار این کتابخانه را به لیست ارجاعات اضافه می‌کند. به بیان دیگر خط بالا به شکل زیر هم قابل اجراست:
csc.exe /out:Program.exe /t:exe Program.cs

به علاوه از آنجایی که بقیه‌ی سوئیچ‌ها هم مقدار پیش فرضی را دارند، خط زیر هم معادل خطوط بالاست:
csc.exe Program.cs

اگر به هر دلیلی دوست ندارید که سمت mscorlib ارجاعی صورت بگیرید، می‌توانید از دستور زیر استفاده کنید:
/nostdlib

مایکروسافت موقعی از این سوئیچ بالا استفاده کرده است که خواسته است خود mscorlib را بسازد. با اضافه کردن این سوئیچ، کد مثال که حاوی شیء یا نوع کنسول است به خطا برخواهد خورد چون تعریف آن در mscorlib صورت گرفته و شما با سوئیچ بالا دسترسی به آن را ممنوع اعلام کرد‌ه‌اید و از آنجاکه این نوع تعریف نشده، برنامه ازکامپایل بازخواهد ماند.
csc.exe /out:Program.exe /t:exe /nostdlib Program.cs

بیایید نگاه دقیقتری به فایل program.exe ساخته شده بیندازیم؛ دقیقا این فایل چه نوع فایلی است؟ برای بسیاری از مبتدیان، این یک فایل اجرایی است که در هر دو ماشین 32 و 64 بیتی قابل اجراست. ویندوز از سه نوع برنامه پشتیبانی می‌کند: CUI یا برنامه‌های تحت کنسول، برنامه‌هایی با رابط گرافیکی GUI و برنامه‌های مخصوص windows store که سوئیچ‌های آن به شرح زیر است:
//CUI
/t:exe

//GUI
/t:winexe

//Winsows store App
/t:appcontainerexe

قبل از اینکه بحث را در مورد سوئیچ‌ها به پایان برسانیم، اجازه دهید در مورد فایل‌های پاسخگو یا response file صحبت کنیم. یک فایل پاسخگو، فایلی است که شامل مجموعه‌ای از سوئیچ‌های خط فرمان می‌شود. موقعی‌که csc.exe اجرا می‌شود، به فایل پاسخگویی که شما به آن معرفی کرده‌اید مراجعه کرده و فرمان را با سوئیچ‌های داخل آن اجرا می‌کند. معرفی یک فایل پاسخگو به کامپایلر توسط علامت @ و سپس نام فایل صورت می‌گیرد و در این فایل هر خط، شامل یک سوئیچ است. مثلا فایل پاسخگوی response.rsp شامل سوئیچ‌های زیر است:
/out:MyProject.exe
/target:winexe

و برای در نظر گرفتن این سوئیچ‌ها فایل پاسخگو را به کامپایلر معرفی می‌کنیم:
csc.exe @MyProject.rsp CodeFile1.cs CodeFile2.cs

این فایل خیلی کار شما را راحت می‌کند و نمی‌گذارد در هر بار کامپایل، مرتب سوئیچ‌های آن را وارد کنید و کیفیت کار را بالا می‌برد. همچنین می‌توانید چندین فایل پاسخگو داشته باشید و هر کدام شامل سوئیچ‌های مختلفی تا اگر خواستید تنظیمات کامپایل را تغییر دهید، به راحتی تنها نام فایل پاسخگو را تغییر دهید. همچنین کامپایلر سی شارپ از چندین فایل پاسخگو هم پشتیبانی می‌کند و می‌توانید هر تعداد فایل پاسخگویی را به آن معرفی کنید. در صورتیکه فایل را با نام csc.rsp نامگذاری کرده باشید، نیازی به معرفی آن نیست چرا که کامپایلر در صورت وجود، آن را به طور خودکار خواهد خواند و به این فایل global response file یا فایل پاسخگوی عمومی گویند.
در صورتیکه چندین فایل پاسخگو که به آن فایل‌های محلی local می‌گویند، معرفی کنید که دستورات آن‌ها(سوئیچ) با دستورات داخل csc.rsp مقدار متفاوتی داشته باشند، فایل‌های محلی الویت بالاتری نسبت به فایل global داشته و تنظمیات آن‌ها روی فایل global رونوشت می‌گردند.

موقعی‌که شما دات نت فریمورک را نصب می‌کنید، فایل csc.rsp را با تنظیمات پیش فرض در مسیر زیر نصب می‌کند:
%SystemRoot%\
Microsoft.NET\Framework(64)\vX.X.X

حروف x نمایانگر نسخه‌ی دات نت فریمورکی هست که شما نصب کرده‌اید. آخرین ورژن از این فایل در زمان نگارش کتاب، شامل سوئیچ‌های زیر بوده است.
# This file contains command­line options that the C#
# command line compiler (CSC) will process as part
# of every compilation, unless the "/noconfig" option
# is specified.
# Reference the common Framework libraries
/r:Accessibility.dll
/r:Microsoft.CSharp.dll
/r:System.Configuration.dll
/r:System.Configuration.Install.dll
/r:System.Core.dll
/r:System.Data.dll
/r:System.Data.DataSetExtensions.dll
/r:System.Data.Linq.dll
/r:System.Data.OracleClient.dll
/r:System.Deployment.dll
/r:System.Design.dll
/r:System.DirectoryServices.dll
/r:System.dll
/r:System.Drawing.Design.dll
/r:System.Drawing.dll
/r:System.EnterpriseServices.dll
/r:System.Management.dll
/r:System.Messaging.dll
/r:System.Runtime.Remoting.dll
/r:System.Runtime.Serialization.dll
/r:System.Runtime.Serialization.Formatters.Soap.dll
/r:System.Security.dll
/r:System.ServiceModel.dll
/r:System.ServiceModel.Web.dll
/r:System.ServiceProcess.dll
/r:System.Transactions.dll
/r:System.Web.dll
/r:System.Web.Extensions.Design.dll
/r:System.Web.Extensions.dll
/r:System.Web.Mobile.dll
/r:System.Web.RegularExpressions.dll
/r:System.Web.Services.dll
/r:System.Windows.Forms.Dll
/r:System.Workflow.Activities.dll
/r:System.Workflow.ComponentModel.dll
/r:System.Workflow.Runtime.dll
/r:System.Xml.dll
/r:System.Xml.Linq.dll

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

نکته: در صورتی که قصد ارجاعی را دارید، می‌توانید آدرس مستقیم اسمبلی را هم ذکر کنید. ولی اگر تنها به نام اسمبلی اکتفا کنید، مسیرهای زیر جهت یافتن اسمبلی بررسی خواهند شد:
  • دایرکتوری برنامه
  • دایرکتوری که شامل فایل csc.exe می‌شود. که خود فایل mscorlib از همانجا خوانده می‌شود و مسیر آن شبیه مسیر زیر است:
%SystemRoot%\Microsoft.NET\Framework\v4.0.#####

  • هر دایرکتوری که توسط سوئیچ lib/ مشخص شده باشد.
  • هر دایرکتوری که توسط متغیر محلی lib مشخص شده باشد.
استفاده از سوئیچ noconfig/ هم باعث می‌شود که فایل‌های پاسخگوی از هر نوعی، چه عمومی و چه محلی، مورد استفاده قرار نگیرند. همچنین شما مجاز هستید که فایل csc.rsp را هم تغییر دهید؛ ولی این نکته را فراموش نکنید، در صورتی که برنامه‌ی شما به سیستمی دیگر منتقل شود، تنظیمات این فایل در آنجا متفاوت خواهد بود و بهتر هست یک فایل محلی را که همراه خودش هست استفاده کنید.
در قسمت بعدی نگاه دیگری بر متادیتا خواهیم داشت.
نظرات مطالب
پشتیبانی توکار از انجام کارهای پس‌زمینه در ASP.NET Core 2x
ارتقاء به NET Core 3.0.: پشتیبانی از ایجاد سرویس‌های پس‌زمینه

یکی از تغییرات مهم قالب ایجاد پروژه‌های ASP.NET Core 3.0، تغییر فایل program.cs آن است که در آن از یک Generic Host بجای روش قبلی Web Host، استفاده شده‌است. علت آن فراهم آوردن امکان استفاده‌ی از قابلیت‌هایی مانند تزریق وابستگی‌ها، logging، تنظیمات برنامه و غیره، در برنامه‌های غیر وب نیز می‌باشد. یکی از این انواع برنامه‌ها، سرویس‌های پس‌زمینه‌ی غیر HTTP هستند. به این ترتیب می‌توان برنامه‌ای شبیه به یک برنامه‌ی وب ASP.NET Core را ایجاد کرد که تنها کارش اجرای سرویس‌های غیر وبی است؛ اما به تمام امکانات و زیر ساخت‌های ASP.NET Core دسترسی دارد.
برای ایجاد این نوع برنامه‌ها در NET Core 3x. می‌توانید دستور زیر را در پوشه‌ی خالی که ایجاد کرده‌اید، اجرا کنید:
dotnet new worker
ساختار برنامه‌ای که توسط این دستور تولید می‌شود به صورت زیر است که بسیار شبیه به ساختار یک برنامه‌ی ASP.NET Core است:
appsettings.Development.json
appsettings.json
MyWorkerServiceApp.csproj
Program.cs
Worker.cs

- فایل csproj آن دارای این محتوا است:
<Project Sdk="Microsoft.NET.Sdk.Worker">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UserSecretsId>dotnet-MyWorkerServiceApp-B76DB08E-FFBB-4AD1-89B5-93BF483D1BD0</UserSecretsId>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0-preview8.19405.4" />
  </ItemGroup>
</Project>
در آن ویژگی Sdk به Microsoft.NET.Sdk.Worker اشاره می‌کند و همچنین از بسته‌ی Microsoft.Extensions.Hosting استفاده شده‌است.

- محتوای فایل Program.cs آن بسیار آشنا است و دقیقا کپی همان فایلی است که در برنامه‌های ASP.NET Core 3x حضور دارد:
namespace MyWorkerServiceApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                });
    }
}
در اینجا یک Generic host را بجای Web host قالب‌های پیشین فایل Program.cs ملاحظه می‌کنید که هدف اصلی آن، عمومی کردن این قالب، برای استفاده‌ی از آن در برنامه‌های غیر وبی نیز می‌باشد.
در متد ConfigureServices، انواع اقسام سرویس‌ها را منجمله یک HostedService که در مطلب جاری به آن پرداخته شده، می‌توان افزود. سرویس Worker ای که در اینجا به آن ارجاعی وجود دارد، به صورت زیر تعریف شده‌است:
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
با ساختار این کلاس نیز آشنا هستید و موضوع اصلی مطلب جاری است.


یک نکته‌ی تکمیلی: روش تبدیل کردن یک BackgroundService به یک Windows Service

اگر برنامه‌ی NET Core. شما در ویندوز اجرا می‌شود، می‌توانید این برنامه‌ی BackgroundService را به یک سرویس ویندوز NT نیز تبدیل کنید. برای اینکار ابتدا بسته‌ی نیوگت Microsoft.Extensions.Hosting.WindowsServices را به پروژه اضافه کنید. سپس جائیکه CreateHostBuilder صورت می‌گیرد، متد UseWindowsService را فراخوانی کنید:
public static IHostBuilder CreateHostBuilder(string[] args) => 
            Host.CreateDefaultBuilder(args) 
                .UseWindowsService() 
                .ConfigureServices((hostContext, services) => 
                { 
                   //services.AddHttpClient(); 
                   services.AddHostedService<Worker>(); 
                });
تا اینجا هنوز هم برنامه، شبیه به یک برنامه‌ی کنسول دات نت Core قابل اجرا و دیباگ است. اما اگر خواستید آن‌را به صورت یک سرویس ویندوز نیز نصب کنید، تنها کافی است از دستور زیر استفاده کنید:
 cs create WorkerServiceDemo binPath=C:\Path\To\WorkerServiceDemo.exe

البته برای لینوکس نیز می‌توان از UseSystemd استفاده کرد که نیاز به نصب بسته‌ی Microsoft.Extensions.Hosting.Systemd را دارد:
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSystemd()
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<Worker>();
        });
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت دوم - معرفی کامپوننت‌ها
در قسمت قبل، پیشنیازهای کار با AngularJS 2.0 مرور و دریافت شدند. اگر مطالب آن‌را پیگیری کرده باشید، هم اکنون باید در پوشه‌ی node_modules واقع در ریشه‌ی پروژه، تمام اسکریپت‌های لازم جهت شروع به کار با AngularJS 2.0 موجود باشند.


تعاریف مداخل فایل index.html یک سایت AngularJS 2.0

پروژه‌ای که در اینجا در حال استفاده است یک برنامه‌ی ASP.NET MVC 5.x است؛ اما الزامی هم به استفاده‌ی از آن وجود ندارد. یا یک فایل index.html را به ریشه‌ی پروژه اضافه کنید و یا فایل Views\Shared\_Layout.cshtml را به نحو ذیل تغییر دهید:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
 
    <link href="~/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
    <link href="~/app/app.component.css" rel="stylesheet"/>
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
 
    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
    <script src="~/node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
    <script src="~/node_modules/es6-shim/es6-shim.min.js"></script>
    <script src="~/node_modules/systemjs/dist/system-polyfills.js"></script>
 
    <script src="~/node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="~/node_modules/systemjs/dist/system.src.js"></script>
    <script src="~/node_modules/rxjs/bundles/Rx.js"></script>
    <script src="~/node_modules/angular2/bundles/angular2.dev.js"></script>
 
    <!-- Required for http -->
    <script src="~/node_modules/angular2/bundles/http.dev.js"></script>
 
    <!-- Required for routing -->
    <script src="~/node_modules/angular2/bundles/router.dev.js"></script> 
 
    <!-- 2. Configure SystemJS -->
    <script>
        System.config({
            packages: {
                app: {
                    format: 'register',
                    defaultExtension: 'js'
                }
            }
        });
        System.import('app/main')
              .then(null, console.error.bind(console));
    </script>
</head>
<body>
    <div>
        @RenderBody()
        <pm-app>Loading App...</pm-app>
    </div>
 
    @RenderSection("Scripts", required: false)
</body>
</html>
در اینجا ابتدا تعاریف مداخل بوت استرپ و css‌های سفارشی برنامه را مشاهده می‌کنید.
سپس کتابخانه‌های جاوا اسکریپتی مورد نیاز جهت کار با AngularJS 2.0 به ترتیبی که ذکر شده‌، باید تعریف شوند.
ذکر /~ در ابتدای آدرس‌ها، مختص به ASP.NET MVC است. اگر از آن استفاده نمی‌کنید، نیازی به ذکر آن هم نیست.
در ادامه تعاریف System.JS ذکر شده‌است. System.JS کار بارگذاری ماژول‌های برنامه را به عهده دارد. به این ترتیب دیگر نیازی نیست تا به ازای هر قسمت جدید برنامه، مدخلی را در اینجا اضافه کرد و کار بارگذاری آن‌ها خودکار خواهد بود. فرمت register ایی که در اینجا ذکر شده‌است، تا ماژول‌های استاندارد با فرمت ES 6 را نیز پشتیبانی می‌کند. همچنین با ذکر و تنظیم پسوند پیش فرض به js، دیگر نیازی نخواهد بود تا در حین تعریف importها در قسمت‌های مختلف برنامه، پسوند فایل‌ها را به صورت صریح ذکر کرد. مبحث improtها مرتبط است به مفاهیم ماژول‌ها در ES 6 و همچنین TypeScript.
سطر System.import کار بارگذاری اولین ماژول برنامه را از پوشه‌ی app قرار گرفته در ریشه‌ی سایت انجام می‌دهد. این ماژول main نام دارد.


نوشتن اولین کامپوننت AngularJS 2.0

برنامه‌های AngularJS 2.0 متشکل هستند از تعدادی کامپوننت و سرویس:


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


- یک قالب یا Template: با استفاده از HTML تعریف می‌شود و کار تشکیل View و نحوه‌ی رندر کامپوننت را مشخص می‌کند. در این Viewها با استفاده از امکانات binding و directives موجود در AngularJS 2.0 کار دسترسی به داده‌ها صورت می‌گیرد.
یک کلاس: کار این کلاس که توسط TypeScript تهیه می‌شود، فراهم آوردن کدهای مرتبط با قالب است. برای مثال این کلاس حاوی تعدادی خاصیت خواهد بود که از اطلاعات آن‌ها در View مرتبط استفاده می‌شود. همچنین این کلاس می‌تواند حاوی متدهای مورد نیاز در View نیز باشد؛ برای مثال متدی که کار نمایش یا مخفی سازی یک تصویر را با کلیک بر روی دکمه‌ای انجام می‌دهد.
- متادیتا: متادیتا (یا decorator در اینجا) به AngularJS 2.0 اعلام می‌کند که این کلاس تعریف شده، صرفا یک کلاس ساده نیست و باید به آن به صورت یک کامپوننت نگاه شود.

در ذیل، کدهای یک کامپوننت نمونه‌ی AngularJS 2.0 را مشاهده می‌کنید:
import { Component } from 'angular2/core';
 
@Component({
    selector: 'pm-app',
    template:`
    <div><h1>{{pageTitle}}</h1>
        <div>My First Component</div>
    </div>
    `
})
export class AppComponent {
    pageTitle: string = "DNT AngularJS 2.0 APP";
}
در انتهای کدها، یک کلاس را مشاهده می‌کنید که کار تعریف خواص و متدهای مورد نیاز توسط View را انجام می‌دهد.
بلافاصله در بالای این کلاس، متد decorator ایی را به نام Component مشاهده می‌کنید. این متادیتا است که به AngularJS 2.0 اعلام می‌کند، کلاس AppComponent تعریف شده، یک کامپوننت است و نه تنها یک کلاس ساده.
در متد Component تعریف شده، قالب یا template نحوه‌ی رندر این کامپوننت را مشاهده می‌کنید.
در ابتدای این ماژول نیز کار import تعاریف مرتبط با متد ویژه‌ی Component، از هسته‌ی AngularJS 2.0 انجام شده‌است تا کامپایلر TypeScript بتواند این فایل ts را کامپایل کند.


مروری بر نحوه‌ی تعریف class در TypeScript

مرور جامع کلاس‌ها در TypeScript را در مطلب «مبانی TypeScript؛ کلاس‌ها» می‌توانید مطالعه کنید. در اینجا جهت یادآوری، خلاصه‌ای از آن‌را که نیاز داریم، بررسی خواهیم کرد:
- جهت تعریف یک کلاس، ابتدا واژه‌ی کلیدی class به همراه نام کلاس ذکر می‌شوند.
- در AngularJS 2.0 مرسوم است که نام کلاس را به صورت نام ویژگی مدنظر به همراه پسوند Component ذکر کنیم؛ مانند AppComponent مثال فوق. این نام pascal case است و با حروف بزرگ شروع می‌شود.
- همچنین مرسوم است در برنامه‌های AngularJS 2.0، کامپوننت ریشه‌ی سایت نیز AppComponent نامیده شود.
- در مثال فوق، واژه‌ی کلیدی export را نیز پیش از واژه‌ی کلیدی class مشاهده می‌کنید. به این ترتیب این کلاس خارج از ماژولی که در آن تعریف می‌شود، قابل دسترسی خواهد بود. اکنون این کلاس و فایل، تبدیل به ماژولی خواهند شد که توسط module loader معرفی شده‌ی در ابتدای بحث یا همان System.JS به صورت خودکار بارگذاری می‌شود و دیگر نیازی به تعریف مدخل script متناظر با آن در فایل index.html نخواهد بود.
- در بدنه‌ی کلاس، کار تعریف متدها و خواص مورد نیاز View صورت می‌گیرند. برای نمونه در اینجا تنها یک خاصیت «عنوان صفحه» تعریف شده‌است. در جاوا اسکریپت مرسوم است که نام خواص را camel case شروع شده با حروف کوچک تعریف کنیم. سپس نوع این خاصیت به صورت رشته‌ای تعریف شده‌است و در آخر مقدار پیش فرض این خاصیت ذکر گردیده‌است.
البته باید دقت داشت که الزامی به ذکر نوع خاصیت، در TypeScript وجود ندارد. همینقدر که مقدار پیش فرض این خاصیت رشته‌ای است، بر اساس ویژگی به نام Type inference در TypeScript، نوع این خاصیت نیز رشته‌ای درنظر گرفته خواهد شد و دیگر نمی‌توان برای مثال یک عدد را به آن انتساب داد.
- سطح دسترسی خواص تعریف شده‌ی در یک کلاس TypeScript به صورت پیش فرض public است. بنابراین در اینجا نیازی به ذکر صریح آن نبوده‌است.


مروری بر متادیتا یا decorator یک کلاس در AngularJS 2.0

خوب، تا اینجا کلاس AppComponent تعریف و همچنین export شد تا توسط system.js به صورت خودکار بارگذاری شود. اما این کلاس به خودی خود صرفا یک کلاس TypeScript ایی است و توسط AngularJS شناسایی نمی‌شود. برای معرفی این کلاس به صورت یک کامپوننت، از یک تزئین کننده یا decorator ویژه به نام Component استفاده می‌شود که بلافاصله در بالای تعریف کلاس قرار می‌گیرد؛ چیزی شبیه به data annotations یا attributes در زبان #C.
یک decorator متدی است که اطلاعاتی اضافی را به یک کلاس، اعضاء و متدهای آن و یا حتی آرگومان‌های آن متدها، الصاق می‌کند. این ویژگی قرار است به صورت استاندارد در ES 2016 یا نگارش بعدی جاوا اسکریپت حضور داشته باشد و در حال حاضر توسط TypeScript پشتیبانی شده و در نهایت به کدهای ES 5 قابل اجرای در تمام مرورگرها ترجمه می‌شود.
یک decorator همیشه با @ شروع می‌شود و AngularJS 2.0 به همراه تعدادی decorator توکار است؛ مانند Component. از آنجائیکه decorator یک متد است، همیشه به همراه یک جفت پرانتز () ذکر می‌شود و در انتهای آن نیازی به ذکر سمی‌کالن نیست. در اینجا تزئین کننده‌ی Component یک شیء را می‌پذیر که به همراه تعدادی خاصیت است. به همین جهت پارامتر آن به صورت {} ذکر شده‌است.
خاصیت selector یک کامپوننت مشخص می‌کند که نام directive متناظر با این کامپوننت چیست:
 selector: 'pm-app',
 از این نام، به صورت یک المان جدید و سفارشی HTML جهت تعریف این کامپوننت استفاده خواهیم کرد. برای مثال اگر به کدهای ابتدای بحث دقت کنید، نام pm-app به صورت ذیل و به شکل یک تگ جدید HTML استفاده شده‌است:
    <div>
        @RenderBody()
        <pm-app>Loading App...</pm-app>
    </div>
همچنین یک کامپوننت همواره به همراه یک قالب است که نحوه‌ی رندر آن‌را مشخص می‌کند:
  template:`
 <div><h1>{{pageTitle}}</h1>
<div>My First Component</div>
 </div>
 `
 در اینجا از back tick مربوط به ES 6.0 که توسط TypeScript نیز پشتیبانی می‌شود، جهت تعریف یک رشته‌ی چندسطری جاوا اسکریپتی، استفاده شده‌است.
همچنین {{}} به معنای تعریف data binding است. به این ترتیب مقداری که قرار است به صورت تگ h1 رندر شود، از خاصیت pageTitle کلاس مزین شده‌ی توسط این ویژگی یا decorator تامین خواهد شد؛ یعنی مقدار پیش فرض خاصیت pageTitle در کلاس AppComponent.


import اطلاعات مورد نیاز جهت کامپایل یک فایل TypeScript

تا اینجا مفاهیم موجود در کلاس AppComponent را به همراه متادیتای آن بررسی کردیم. اما این متادیتای جدید کامپوننت، به صورت پیش فرض ناشناخته‌است:


همانطور که مشاهده می‌کنید، در اینجا ذیل کامپوننت، خط قرمزی جهت یادآوری عدم تعریف آن، کشیده شده‌است. در TypeScript و یا ES 6، پیش از استفاده از یک کلاس یا متد خارجی، نیاز است به module loader اعلام کنیم تا آن‌را باید از کجا تامین کند. اینکار توسط عبارت import انجام می‌شود که شبیه به عبارت using در زبان سی‌شارپ است. عبارت import جزئی از استاندارد ES 6 است و همچنین در TypeScript نیز پشتیبانی می‌شود. به این ترتیب امکان دسترسی به ویژگی‌های export شده‌ی از سایر ماژول‌ها را در ماژول فعلی (فایل فعلی در حال کار) خواهیم یافت. نمونه‌ی آن‌را با ذکر واژه‌ی کلیدی export پیش از کلاس AppComponent پیشتر ملاحظه کردید.
این ماژول‌های خارجی می‌توانند سایر ماژول‌ها و فایل‌های ts نوشته شده‌ی توسط خودمان و یا حتی اجزای AngularJS 2.0 باشند. طراحی AngularJS 2.0 نیز ماژولار است و از ماژول‌هایی مانند angular2/core، angular2/animation، angular2/http و angular2/router تشکیل شده‌است.
برای نمونه متادیتای کامپوننت، در ماژول angular2/core قرار دارد. به همین جهت نیاز است در ابتدای ماژول فعلی آن‌را import کرد:
import { Component } from 'angular2/core';
کار با ذکر واژه‌ی کلیدی import شروع می‌شود. سپس جزئی را که نیاز داریم داخل {} ذکر کرده و در آخر مسیر یافتن آن‌را مشخص می‌کنیم.


ساخت کامپوننت ریشه‌ی یک برنامه‌ی AngularJS 2.0

در ابتدای بحث که تعاریف مداخل مورد نیاز جهت اجرای یک برنامه‌ی AngularJS 2.0 ذکر شدند، عنوان شد که system.js به دنبال ماژول آغازین app/main می‌گردد.
بنابراین در ریشه‌ی پروژه، پوشه‌ی جدیدی را به نام app ایجاد کنید.
سپس یک فایل TypeScript جدید را به نام app.component.ts به این پوشه اضافه کنید. قالب این نوع فایل‌ها در add new item و با جستجو typescript در دسترس است و یا حتی یک فایل متنی ساده را هم با پسوند ts ایجاد کنید، کافی است.


نامگذاری این فایل‌ها هم مرسوم است به صورت ذکر نام ویژگی به همراه یک دات و سپس ذکر کامپوننت صورت گیرد. در اینجا چون قصد داریم کامپوننت ریشه‌ی برنامه را ایجاد کنیم، نام ویژگی آن app خواهد بود و نام کامل فایل به این ترتیب app.component.ts می‌شود.
سپس محتوای این فایل را به دقیقا معادل کدهای قسمت «نوشتن اولین کامپوننت AngularJS 2.0» ابتدای بحث تغییر دهید (همان import، متادیتا و کلاس AppComponent).

تا اینجا کامپوننت ریشه‌ی برنامه ایجاد شد. اما چگونه باید از آن استفاده کنیم و چگونه AngularJS 2.0 آن‌را شناسایی می‌کند؟ به این فرآیند آغازین شناسایی و بارگذاری ماژول‌ها، اصطلاحا bootstrapping می‌گویند. تنها صفحه‌ی واقعی موجود در یک برنامه‌ی تک صفحه‌ای وب، همان فایل index.html است و سایر صفحات و محتوای آن‌ها به محتوای این صفحه‌ی آغازین اضافه یا کم خواهند شد.
<div>
    @RenderBody()
    <pm-app>Loading App...</pm-app>
</div>
در اینجا برای نمایش اولین کامپوننت تهیه شده، نام تگ selector آن که توسط متادیتای کلاس AppComponent تعریف شد، در body فایل index.html به نحو فوق به صورت یک تگ سفارشی جدید اضافه می‌شود. به آن directive نیز می‌گویند.
خوب، اکنون module loader یا system.js چگونه این pm-app یا کامپوننت ریشه‌ی برنامه را شناسایی می‌کند؟
 System.import('app/main')
این شناسایی توسط سطر System.import تعریف شده‌ی در فایل index.html انجام می‌شود. در اینجا system.js، در پوشه‌ی app واقع در ریشه‌ی سایت، به دنبال ماژول راه اندازی به نام main می‌گردد. یعنی نیاز است فایل TypeScript جدیدی را به نام main.ts به ریشه‌ی پوشه‌ی app اضافه کنیم. محتوای این فایل ویژه‌ی بوت استرپ AngularJS 2.0 به صورت ذیل است:
/// <reference path="../node_modules/angular2/typings/browser.d.ts" />
 
import { bootstrap } from "angular2/platform/browser";
 
// Our main component
import { AppComponent } from "./app.component";
 
bootstrap(AppComponent);
این فایل ویژه را نیز مانند کلاس AppComponent که پیشتر بررسی کردیم، نیاز است از انتها به ابتدا بررسی کرد.
در انتهای این فایل متد bootstrap مربوط به AngularJS 2.0 را مشاهده می‌کنید. کار آن بارگذاری اولین ماژول و کامپوننت برنامه یا همان AppComponent است.
در ادامه نیاز است AppComponent را به این ماژول معرفی کرد؛ در غیراینصورت کامپایل نخواهد شد. برای این منظور سطر import این کلاس را از فایل app.component، مشاهده می‌کنید. در اینجا نیازی به ذکر پسوند ts. فایل app.component نیست.
سپس نیاز است محل تعریف متد بوت استرپ را نیز مشخص کنیم. این متد در ماژول angular2/platform/browser قرار دارد که به عنوان اولین import این فایل ذکر شده‌است.
سطر اول، مربوط است به تعریف فایل‌های d.ts. مربوط به TypeScript جهت شناسایی نوع‌های مرتبط با AngularJS 2.0. اگر اینکار صورت نگیرد، خطاهای ذیل را در حین کامپایل فایل‌های TypeScript دریافت خواهید کرد:
 node_modules\angular2\src\core\application_ref.d.ts(171,81): error TS2304: Build: Cannot find name 'Promise'.
node_modules\angular2\src\core\change_detection\differs\default_keyvalue_differ.d.ts(23,15): error TS2304: Build: Cannot find name 'Map'.
تهیه فایل main.ts تنها یکبار صورت می‌گیرد و دیگر با آن کاری نخواهیم داشت.

تا اینجا پوشه‌ی app واقع در ریشه‌ی سایت، یک چنین شکلی را پیدا می‌کند:



و اکنون اگر برنامه را اجرا کنیم (فشردن دکمه‌ی F5)، خروجی آن در مرورگر به صورت ذیل خواهد بود:

با توجه به اینکه در حال کار با یک برنامه‌ی جاوا اسکریپتی هستیم، باز نگه داشتن developer tools مرورگر، جهت مشاهده‌ی خطاهای احتمالی ضروری است.

در اینجا اگر خطایی وجود داشته باشد، یا اطلاعات اضافی مدنظر باشد، در console لاگ خواهند شد. برای مثال در اینجا عنوان شده‌است که برنامه در حالت توسعه در حال اجرا است. بهتر است برای ارائه‌ی نهایی، متد enableProdMode را در فایل index.html فراخوانی کنید.

همچنین در اینجا می‌توان HTML نهایی تزریق شده‌ی به صفحه را بهتر مشاهده کرد:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part2.zip
برای اجرای آن، ابتدا به فایل project.json مراجعه کرده و یکبار آن‌را ذخیره کنید تا وابستگی‌های اسکریپتی پروژه از اینترنت دریافت شوند (این موارد در قسمت قبل مرور شدند). سپس یکبار هم پروژه را Build کنید تا تمام فایل‌های ts آن کامپایل شوند و در آخر، اجرای نهایی پروژه.


خلاصه‌ی بحث

یک برنامه‌ی AngularJS 2.0 متشکل است از تعدادی کامپوننت. بنابراین کلاسی را ایجاد خواهیم کرد تا کدهای پشتیبانی کننده‌ی View این کامپوننت را تولید کند. سپس این کلاس را با متادیتایی مزین کرده و توسط آن تگ سفارشی ویژه‌ی این کامپوننت و تگ‌های HTML تشکیل دهنده‌ی این کامپوننت را به AngularJS 2.0 معرفی می‌کنیم. در اینجا در صورت نیاز وابستگی‌های تعریف این متادیتا را توسط واژه‌ی کلیدی import دریافت می‌کنیم. نام این کلاس بهتر است Pascal case بوده و با حروف بزرگ شروع شود و همچنین به صورت نام ویژگی ختم شده‌ی به کلمه‌ی Component باشد. در اینجا حتما نیاز است این کلاس export شود تا توسط module loader قابل استفاده و بارگذاری گردد. اگر View این کامپوننت نیاز به دریافت اطلاعاتی دارد، این اطلاعات به صورت خواصی در کلاس کامپوننت تعریف می‌شوند. این خواص تعریف شده‌ی با سطح دسترسی عمومی، مرسوم است به صورت camel case تعریف شوند و حروف اول آن‌ها کوچک باشد.
نظرات مطالب
سرریز اعداد و checked در #C
روش دیگر کامپایل کردن کد با پارامتر زیر است
/checked
در کل کارآیی این روش حدود سه بار کندتر است از روش معمولی بدون checked و اگر مشکل یافت شده بهتر است از بازه صحیح اعداد استفاده شود.

مطالب
شروع به کار با EF Core 1.0 - قسمت 1 - برپایی تنظیمات اولیه
در ادامه‌ی سری «ارتقاء به ASP.NET Core 1.0» اگر بخواهیم مباحث اعتبارسنجی کاربران و ASP.NET Identity مخصوص آن‌را بررسی کنیم، نیاز است ابتدا مباحث Entity framework Core 1.0 را بررسی کنیم. به همین جهت در طی چند قسمت مباحث پایه‌ای کار با EF Core 1.0 را در ASP.NET Core 1.0، بررسی خواهیم کرد. بنابراین پیشنیاز ضروری این مباحث، مطالعه‌ی سری «ارتقاء به ASP.NET Core 1.0» است و در آن از مباحثی مانند چگونگی کار با فایل‌های کانفیگ جدید، تزریق وابستگی‌ها و سرویس‌ها، فعال سازی سرویس Logging، فعال سازی صفحات مخصوص توسعه دهنده‌ها و ... در ASP.NET Core 1.0 استفاده خواهد شد.


EF Core چیست؟

EF Core یک ORM یا object-relational mapper چندسکویی است که امکان کار با بانک‌های اطلاعاتی مختلف را از طریق اشیاء دات نتی میسر می‌کند. توسط آن قسمت عمده‌ی کدهای مستقیم کار با بانک‌های اطلاعاتی حذف شده و تبدیل به کدهای دات نتی می‌شوند. مزیت این لایه‌ی Abstraction اضافی (لایه‌ای بر روی کدهای مستقیم لایه ADO.NET زیرین)، امکان تعویض بانک اطلاعاتی مورد استفاده، تنها با تغییر کدهای آغازین برنامه‌است؛ بدون نیاز به تغییری در سایر قسمت‌های برنامه. همچنین کار با اشیاء دات نتی و LINQ، مزایایی مانند تحت نظر قرار گرفتن کدها توسط کامپایلر و برخورداری از ابزارهای Refactoring پیشرفته را میسر می‌کنند. به علاوه SQL خودکار تولیدی توسط آن نیز همواره پارامتری بوده و مشکلات حملات تزریق SQL در این حالت تقریبا به صفر می‌رسند (اگر مستقیما SQL نویسی نکنید و صرفا از LINQ استفاده کنید). مزیت دیگر همواره پارامتری بودن کوئری‌ها، رفتار بسیاری از بانک‌های اطلاعاتی با آن‌ها همانند رویه‌های ذخیره شده است که به عدم تولید Query plan‌های مجزایی به ازای هر کوئری رسیده منجر می‌شود که در نهایت سبب بالا رفتن سرعت اجرای کوئری‌ها و مصرف حافظه‌ی کمتری در سمت سرور بانک اطلاعاتی می‌گردد.


تفاوت EF Core با نگارش‌های دیگر Entity framework در چیست؟

سورس باز بودن
EF از نگارش‌های آخر آن بود که سورس باز شد؛ اما EF Core از زمان نگارش‌های پیش نمایش آن به صورت سورس باز در GitHub قابل دسترسی است.

چند سکویی بودن
EF Core برخلاف EF 6.x (آخرین نگارش مبتنی بر Full Framework آن)، نه تنها چندسکویی است و قابلیت اجرای بر روی Mac و لینوکس را نیز دارا است، به علاوه امکان استفاده‌ی از آن در انواع و اقسام برنامه‌های دات نتی مانند UWP یا Universal Windows Platform و Windows phone که پیشتر با EF 6.x میسر نبود، وجود دارد. لیست این نوع سکوها و برنامه‌های مختلف به شرح زیر است:
 • All .NET application (Console, ASP.NET 4, WinForms, WPF)
 • Mac and Linux applications (Mono)
 • UWP (Universal Windows Platform)
 • ASP.NET Core applications
 • Can use EF Core in Windows phone and Windows store app

افزایش تعداد بانک‌های اطلاعاتی پشتیبانی شده
در EF Full یا EF 6.x، هدف اصلی، تنها کار با بانک‌های اطلاعاتی رابطه‌‌ای بود و همچنین مایکروسافت صرفا نگارش‌های مختلف SQL Server را به صورت رسمی پشتیبانی می‌کرد و برای سایر بانک‌های اطلاعاتی دیگر باید از پروایدرهای ثالث استفاده کرد.
در EF Core علاوه بر افزایش تعداد پروایدرهای رسمی بانک‌های اطلاعاتی رابطه‌ای، پشتیبانی از بانک‌های اطلاعاتی NoSQL هم اضافه شده‌است؛ به همراه پروایدر ویژه‌‌ای به نام In Memory جهت انجام ساده‌تر Unit testing. کاری که با نگارش‌های پیشین EF به سادگی و از روز اول پشتیبانی نمی‌شد.

حذف و یا عدم پیاده سازی تعدادی از قابلیت‌های EF 6.x
اگر موارد فوق جزو مهم‌ترین مزایای کار با EF Core باشند، باید درنظر داشت که به علت حذف و یا تقلیل یافتن یک سری از ویژگی‌ها در NET Core.، مانند Reflection (جهت پشتیبانی از دات نت در سکوهای مختلف کاری و خصوصا پشتیبانی از حالتی که کامپایلر مخصوص برنامه‌های UWP نیاز دارد تمام نوع‌ها را همانند زبان‌های C و ++C، در زمان کامپایل بداند)، یک سری از قابلیت‌های EF 6.x مانند Lazy loading هنوز در EF Core پشتیبانی نمی‌شوند. لیست کامل و به روز شده‌ی این موارد را در اینجا می‌توانید مطالعه کنید.
بنابراین امکان انتقال برنامه‌های EF 6.x به EF Core 1.0 عموما وجود نداشته و نیاز به بازنویسی کامل دارند. هرچند بسیاری از مفاهیم آن با EF Code First یکی است.


برپایی تنظیمات اولیه‌ی EF Core 1.0 در یک برنامه‌ی ASP.NET Core 1.0

برای نصب تنظیمات اولیه‌ی EF Core 1.0 در یک برنامه‌ی ASP.NET Core 1.0، جهت کار با مشتقات SQL Server (و SQL LocalDB) نیاز است سه بسته‌ی ذیل را نصب کرد (از طریق منوی Tools -> NuGet Package Manager -> Package Manager Console):
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM> Install-Package Microsoft.EntityFrameworkCore.Tools -Pre
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design
البته در این قسمت صرفا از بسته‌ی اول که جهت اتصال به SQL Server است استفاده می‌کنیم. بسته‌های دیگر را در قسمت‌های بعد، برای به روز رسانی اسکیمای بانک اطلاعاتی (مباحث Migrations) و مباحث scaffolding استفاده خواهیم کرد.
پس از اجرای سه دستور فوق، تغییرات مداخل فایل project.json برنامه به صورت ذیل خواهند بود:
{
    "dependencies": {
       // same as before
        "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
        "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
        "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0"
    }
}
این مداخلی که توسط نیوگت اضافه شده‌اند، نیاز به اصلاح دارند؛ به این صورت:
{
    "dependencies": {
       // same as before
        "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
        "Microsoft.EntityFrameworkCore.Tools": {
            "version": "1.0.0-preview2-final",
            "type": "build"
        },
        "Microsoft.EntityFrameworkCore.SqlServer.Design": {
            "version": "1.0.0",
            "type": "build"
        }
    },

    "tools": {
       // same as before
        "Microsoft.EntityFrameworkCore.Tools": {
            "version": "1.0.0-preview2-final",
            "imports": [
                "portable-net45+win8"
            ]
        }   
   }
}
نیاز است در قسمت dependencies مشخص کنیم که ابزارهای اضافه شده مخصوص build هستند و نه اجرای برنامه. همچنین قسمت tools را باید با Microsoft.EntityFrameworkCore.Tools مقدار دهی کرد تا بتوان از این ابزار در خط فرمان، جهت اجرای فرامین migrations استفاده کرد.
بنابراین از همین ابتدای کار، بدون مراجعه‌ی به Package Manager Console، چهار تغییر فوق را به فایل project.json اعمال کرده و آن‌را ذخیره کنید؛ تا کار به روز رسانی و نصب بسته‌ها، به صورت خودکار و همچنین صحیحی انجام شود.


فعال سازی صفحات مخصوص توسعه دهنده‌های EF Core 1.0

در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها» با تعدادی از اینگونه صفحات آشنا شدیم. برای EF Core نیز بسته‌ی مخصوصی به نام Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore وجود دارد که امکان فعال سازی صفحه‌ی نمایش خطاهای بانک اطلاعاتی را میسر می‌کند. بنابراین ابتدا به فایل project.json مراجعه کرده و این بسته را اضافه کنید:
{
    "dependencies": {
       // same as before
        "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0"
    }
}
سپس می‌توان متد جدید UseDatabaseErrorPage را در متد Configure کلاس آغازین برنامه، فراخوانی کرد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
      app.UseDatabaseErrorPage();
   }
با فعال سازی این صفحه، اگر در حین توسعه‌ی برنامه و اتصال به بانک اطلاعاتی، خطایی رخ دهد، بجای مشاهده‌ی یک صفحه‌ی خطای عمومی (اگر UseDeveloperExceptionPage را فعال کرده باشید)، اینبار ریز جزئیات بیشتری را به همراه توصیه‌هایی از طرف تیم EF مشاهده خواهید کرد.


تعریف اولین Context برنامه و مشخص سازی رشته‌ی اتصالی آن


در این تصویر، زیر ساخت نگاشت‌های EF Core را مشاهده می‌کنید. در سمت چپ، ظرفی را داریم به نام DB Context که در برگیرنده‌ی Db Setها است. در سمت راست که بیانگر ساختار کلی یک بانک اطلاعاتی است، معادل این‌ها را مشاهده می‌کنیم. هر Db Set به یک جدول بانک اطلاعاتی نگاشت خواهد شد و متشکل است از کلاسی به همراه یک سری خواص که این‌ها نیز به فیلدها و ستون‌های آن جدول در سمت بانک اطلاعاتی نگاشت می‌شوند.
بنابراین برای شروع کار، پوشه‌ای را به نام Entities به پروژه اضافه کرده و سپس کلاس ذیل را به آن اضافه می‌کنیم:
namespace Core1RtmEmptyTest.Entities
{
    public class Person
    {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}
کلاس Person بیانگر ساختار جدول اشخاص بانک اطلاعاتی است. برای اینکه این کلاس را تبدیل و نگاشت به یک جدول کنیم، نیاز است آن‌را به صورت یک DbSet در معرض دید EF Core قرار دهیم و اینکار در کلاسی که از DbContex مشتق می‌شود، صورت خواهد گرفت:
using Microsoft.EntityFrameworkCore;

namespace Core1RtmEmptyTest.Entities
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
        }

        public DbSet<Person> Persons { get; set; }
    }
}
بنابراین در ادامه کلاس جدید ApplicationDbContext را که از کلاس پایه DbContext مشتق می‌شود تعریف کرده و سپس کلاس Person را به صورت یک DbSet در معرض دید EF Core قرار می‌دهیم.
سازنده‌ی این کلاس نیز به نحو خاصی تعریف شده‌است. اگر به سورس‌های EF Core مراجعه کنیم، کلاس پایه‌ی DbContext دارای دو سازنده‌ی با و بدون پارامتر است:
protected DbContext()
   : this((DbContextOptions) new DbContextOptions<DbContext>())
{
}

public DbContext([NotNull] DbContextOptions options)
{
  // …
}
اگر از سازنده‌ی بدون پارامتر استفاده کنیم و برای مثال در کلاس ApplicationDbContext فوق، به طور کامل سازنده‌ی تعریف شده را حذف کنیم، باید به نحو ذیل تنظیمات بانک اطلاعاتی را مشخص کنیم:
using Microsoft.EntityFrameworkCore;

namespace Core1RtmEmptyTest.Entities
{
    public class ApplicationDbContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"... connection string ...");
        }
    }
}
در این حالت باید متد OnConfiguring را override و یا بازنویسی کنیم، تا بتوان از اول مشخص کرد که قرار است از پروایدر SQL Server استفاده کنیم و ثانیا رشته‌ی اتصالی به آن چیست.
اما چون در یک برنامه‌ی ASP.NET Core، کار ثبت سرویس مربوط به EF Core، در کلاس آغازین برنامه انجام می‌شود و در آنجا به سادگی می‌توان به خاصیت Configuration برنامه دسترسی یافت و توسط آن رشته‌ی اتصالی را دریافت کرد، مرسوم است از سازنده‌ی با پارامتر DbContext به نحوی که در ابتدا عنوان شد، استفاده شود.
بنابراین در ادامه، پس از مطالعه‌ی مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» به فایل appsettings.json مراجعه کرده و تنظیمات رشته‌ی اتصالی برنامه را به صورت ذیل در آن مشخص می‌کنیم:
{
    "ConnectionStrings": {
        "ApplicationDbContextConnection": "Data Source=(local);Initial Catalog=TestDbCore2016;Integrated Security = true"
    }
}
باید دقت داشت که نام این مداخل کاملا اختیاری هستند و در نهایت باید در کلاس آغازین برنامه به صورت صریحی مشخص شوند.
در اینجا به وهله‌ی پیش فرض SQL Server اشاره شده‌است؛ از حالت اعتبارسنجی ویندوزی SQL Server استفاده می‌شود و بانک اطلاعاتی جدیدی به نام TestDbCore2016 در آن مشخص گردیده‌است.

پس از تعریف رشته‌ی اتصالی، متد OnConfiguring را از کلاس ApplicationDbContext حذف کرده و از همان نگارش دارای سازنده‌ی با پارامتر آن استفاده می‌کنیم. برای اینکار به کلاس آغازین برنامه مراجعه کرده و توسط متد AddDbContext این Context را به سرویس‌های ASP.NET Core معرفی می‌کنیم:
    public class Startup
    {
        public IConfigurationRoot Configuration { set; get; }

        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                                .SetBasePath(env.ContentRootPath)
                                .AddJsonFile("appsettings.json", reloadOnChange: true, optional: false)
                                .AddJsonFile($"appsettings.{env}.json", optional: true);
            Configuration = builder.Build();
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseSqlServer(Configuration["ConnectionStrings:ApplicationDbContextConnection"]);
            });
در اینجا جهت یادآوری مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» نحوه‌ی وهله سازی خاصیت Configuration که در متد UseSqlServer مورد استفاده قرار گرفته‌است، نیز ذکر شده‌است.
بنابراین قسمت options.UseSqlServer را یا در اینجا مقدار دهی می‌کنید و یا از طریق بازنویسی متد OnConfiguring کلاس Context برنامه.


یک نکته: امکان تزریق IConfigurationRoot به کلاس Context برنامه وجود دارد

با توجه به اینکه Context برنامه را به صورت یک سرویس به ASP.NET Core معرفی کردیم، امکان تزریق وابستگی‌ها نیز در آن وجود دارد. یعنی بجای روش فوق، می‌توان IConfigurationRoot را به سازنده‌ی کلاس Context برنامه نیز تزریق کرد:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace Core1RtmEmptyTest.Entities
{
    public class ApplicationDbContext : DbContext
    {
        private readonly IConfigurationRoot _configuration;

        public ApplicationDbContext(IConfigurationRoot configuration)
        {
            _configuration = configuration;
        }

        //public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        //{
        //}

        public DbSet<Person> Persons { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(_configuration["ConnectionStrings:ApplicationDbContextConnection"]);
        }
    }
}
با توجه به اینکه IConfigurationRoot در کلاس ConfigureServices به صورت Singleton، به مجموعه‌ی سرویس‌های برنامه معرفی شده‌است، از آن در تمام کلاس‌های برنامه که تحت نظر سیستم تزریق وابستگی‌های توکار ASP.NET Core هستند، می‌توان استفاده کرد.
در این حالت متد ConfigureServices کلاس آغازین برنامه، چنین شکلی را پیدا می‌کند و ساده می‌شود:
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>();


یک نکته: امکان تزریق ApplicationDbContext به تمام کلاس‌های برنامه وجود دارد

همینقدر که ApplicationDbContext را به عنوان سرویسی در ConfigureServices تعریف کردیم، امکان تزریق آن در اجزای مختلف یک برنامه‌ی ASP.NET Core نیز وجود دارد:
using System.Linq;
using Core1RtmEmptyTest.Entities;
using Microsoft.AspNetCore.Mvc;

namespace Core1RtmEmptyTest.Controllers
{
    public class TestDBController : Controller
    {
        private readonly ApplicationDbContext _ctx;

        public TestDBController(ApplicationDbContext ctx)
        {
            _ctx = ctx;
        }

        public IActionResult Index()
        {
            var name = _ctx.Persons.First().FirstName;
            return Json(new { firstName = name });
        }
    }
}
در اینجا نحوه‌ی تزریق DB Context برنامه را به یک کنترلر مشاهده می‌کنید. البته هرچند تزریق یک کلاس مشخص به این شکل، تزریق وابستگی‌ها نام ندارد و هنوز این کنترلر دقیقا وابسته‌است به پیاده سازی خاص کلاس ApplicationDbContext، اما ... در کل امکان آن هست.

در این حالت پس از اجرای برنامه، خطای ذیل را مشاهده خواهیم کرد:


علت اینجا است که هنوز این بانک اطلاعاتی ایجاد نشده‌است و همچنین ساختار جداول را به آن منتقل نکرده‌ایم که این موارد را در قسمت‌های بعدی مرور خواهیم کرد.
نظرات مطالب
بهبود کارآیی حلقه‌های foreach در دات نت 7
یک نکته‌ی تکمیلی: آشنایی با مفهوم «C# Lowering»


در این مطلب، جهت بررسی درک علت یکسان بودن کارآیی حلقه‌هایی که از دیدگاه ما یکی نیستند، از قابلیت نمایش #Low-level C استفاده شد که نام اصلی آن «C# Lowering» است. Lowering به معنای ترجمه‌ی امکانات سطح بالای یک زبان به امکانات سطح پایین آن است. یعنی حاصل عملیات صورت گرفته نیز باز به همان زبان اولیه است که نمونه‌ی آن، تبدیل یک حلقه‌ی foreach سطح بالا به نمونه‌ی سطح پایینی است که توسط NET Runtime. بهتر درک شده و ساده‌تر اجرا می‌شود.

مزایای Lowering
- بهبود کارآیی برنامه: برای مثال یکی از کارهایی که در این بین عموما انجام می‌شود «Loop unrolling» است. یعنی یک حلقه به چندین حلقه‌ی کوچکتر تقسیم می‌شود تا سربار instructions کنترلی حلقه کاهش پیدا کنند.
- طراحی ساده‌تر زبان: اینکار به تیم طراحی زبان امکان نوشتن کدهای اضافه‌تری را می‌دهد که کار برنامه نویس‌ها را کمتر می‌کند. برای مثال یک record واقعا چیزی نیست بجز یک کلاس پیاده سازی کننده‌ی IEquatable به صورت خودکار و در پشت صحنه.

Lowering چه زمانی رخ می‌دهد؟
Lowering جزئی از عملیات صورت گرفته‌ی در حین کامپایل است. زمانیکه دستور dotnet build را صادر می‌کنیم، ابتدا semantics & syntax analysis صورت می‌گیرد تا اگر برای مثال خطای دستوری وجود دارد، مشخص شود. سپس کدها به CIL یا Common intermediate language تبدیل می‌شوند. در حین این قسمت است که عملیات lowering نیز انجام می‌شود.
اگر علاقمند به مشاهده‌ی این کد #C ثانویه‌ی تولید شده‌ی توسط کامپایلر هستید، می‌توان از ابزار https://sharplab.io نیز استفاده کرد. برای مثال در سمت چپ آن کدهای زیر را قرار دهید:
using System;
using System.Collections.Generic;

var list = new List<int> { 1, 2 };

foreach(var item in list)
    Console.Write(item);
سپس در سمت راست آن، گزینه‌ی #Results C را انتخاب کنید تا بتوانید نمونه‌ی معادل تبدیل شده‌ی توسط کامپایلر را مشاهده نمائید.
مطالب
بسته بندی نرم افزار الکترون
بعد از اینکه برنامه الکترون آماده شد، لازم است آن را به فایل‌های اجرایی پلتفرم‌های مختلف تبدیل کنیم. برای اینکار بسته معروف Electron-packager را مورد استفاده قرار می‌دهیم. برای نصب آن به شکل زیر اقدام کنید:
npm install electron-packager --save-dev
بعد از اینکه نصب شد، در فایل package.json در قسمت scripts، خصوصیت جدیدتری را وارد می‌کنیم:
"build":"electron-packager . myapp --platform=all --arch=all --overwrite"
این دستور شامل حداقل‌های آرگومان‌ها می‌باشد. در تشریح این دستور باید گفت اولین آرگومان، نام دایرکتوری است که برنامه شما در آن نوشته شده است و حاوی فایل package.json است و با قرار دادن علامت "." دایرکتوری جاری را معرفی کرده‌ایم. بعد از آن نام برنامه و فایل اجرایی برنامه است. بعد از آن فلگ‌ها آغاز می‌شوند که اولین فلگ مشخص میکند خروجی خود را برای چه پلتفرمی نیاز دارید و شامل مقادیر زیر می‌شود؛ یا اینکه عبارت all را برای در نظر گرفتن همه مقادیر وارد کنید.
darwin
سیستم عامل مکینتاش
linux
 سیستم عامل لینوکس
win32
 سیستم عامل ویندوز

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

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

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

حال با دستور زیر در nodejs، عملیات بسته بندی را آغاز می‌کنیم:

npm run build
بعد از اجرای این دستور برای اولین بار، ممکن است برای هر پلتفرم، کتابخانه‌های مربوطه را دانلود کند تا بر اساس آن، عمل بسته بندی را انجام دهد. سپس در دایرکتوری پروژه، دایرکتوری‌های جدیدی را با نام‌های مشخصی خواهید یافت.
یکی از دیگر فلگ‌ها که کاربردی می‌باشد، برای نادیده گرفتن ورورد یک سری پکیج‌ها به بسته نهایی است که به طور پیش فرض جلوی ورود بسته‌های eletron-prebuilt و electron-packager را میگیرد. ولی اگر دوست دارید تا بسته‌های دیگری را نیز به این لیست اضافه کنید، دستو زیر را به کار ببرید:
--ignore=node_modules/<package_name>
یا
--ignore=node_modules/electron_[0-9]*

فلگ‌های پر استفاده دیگر این بسته:
 aap-version  نسخه برنامه
 app-copyright  متنی برای قانون کپی رایت
 asar موقعی که برنامه‌ای را بسته بندی می‌کنید، در دایرکتوری Resources/App، هنوز سورس برنامه وجود دارد که فایل اجرایی شما بدون آن قادر به ادامه فعالیت نیست. ولی اگر بخواهیم این سورس را در اختیار شخصی قرار ندهیم، باید از ویژگی asar استفاده کنیم. با استفاده از این فلگ، فایلی با نام app.asar جای این دایرکتوری ایجاد خواهد شد و دیگر نیازی نیست تا سورس برنامه همراه آن باشد.
 icon در صورتیکه قصد استفاده از آیکنی بجز آیکون الکترون را دارید.
 out به طور پیش فرض برنامه نهایی در دایرکتوری کاری پروژه اضافه می‌شود. در صورتیکه قصد دارید آن‌را در دایرکتوری بجز دایرکتوری کاری قرار دهید، از این ویژگی استفاده کنید.
 version-string این خصوصیت برای نسخه بندی برنامه است که فقط برای ویندوز کاربرد دارد و شامل خصوصیاتی چون نام محصول، نام سازنده، توصیف برنامه و ... می‌باشد:
--version-string.ProductName="Product"

                   Properties supported:
                   - CompanyName
                   - FileDescription
                   - OriginalFilename
                   - ProductName
                   - InternalName

prune
استفاده از این فلگ باعث می‌شود کلیه بسته‌های معرفی شده در dev-dependency به بسته نهایی اضافه نشوند

دستور بسته بندی بالا را نیز می‌توان به طور خلاصه‌تر نیز نوشت :

electron-packager . --all
این دستور با در نظر گرفتن همه پلتفرم‌ها و معماری آن‌ها، برای دایرکتوری جاری اجرا شده و نام برنامه و دیگر اطلاعات را از طریق فایل package.json به دست می‌آورد.
مطالب
آشنایی با NHibernate - قسمت ششم

آشنایی با Automapping در فریم ورک Fluent NHibernate

اگر قسمت‌های قبل را دنبال کرده باشید، احتمالا به پروسه طولانی ساخت نگاشت‌ها توجه کرده‌اید. با کمک فریم ورک Fluent NHibernate می‌توان پروسه نگاشت domain model خود را به data model متناظر آن به صورت خودکار نیز انجام داد و قسمت عمده‌ای از کار به این صورت حذف خواهد شد. (این مورد یکی از تفاوت‌های مهم NHibernate با نمونه‌های مشابهی است که مایکروسافت تا تاریخ نگارش این مقاله ارائه داده است. برای مثال در نگار‌ش‌های فعلی LINQ to SQL یا Entity framework ، اول دیتابیس مطرح است و بعد ساخت کد از روی آن، در حالیکه در اینجا ابتدا کد و طراحی سیستم مطرح است و بعد نگاشت آن به سیستم داده‌ای و دیتابیس)

امروز قصد داریم یک سیستم ساده ثبت خبر را از صفر با NHibernate پیاده سازی کنیم و همچنین مروری داشته باشیم بر قسمت‌های قبلی.

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

ابتدا یک پروژه کنسول جدید را به نام NHSample2 آغاز کنید. سپس ارجاعاتی را به اسمبلی‌های زیر به آن اضافه نمائید:
FluentNHibernate.dll
NHibernate.dll
NHibernate.ByteCode.Castle.dll
NHibernate.Linq.dll
و ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم

سپس پوشه‌ای را به نام Domain به این پروژه اضافه نمائید (کلیک راست روی نام پروژه در VS.Net و سپس مراجعه به منوی Add->New folder). در این پوشه تعاریف موجودیت‌های برنامه را قرار خواهیم داد. سه کلاس جدید Category ، User و News را در این پوشه ایجاد نمائید. محتویات این سه کلاس به شرح زیر هستند:

namespace NHSample2.Domain
{
public class User
{
public virtual int Id { get; set; }
public virtual string UserName { get; set; }
public virtual string Password { get; set; }
}
}


namespace NHSample2.Domain
{
public class Category
{
public virtual int Id { get; set; }
public virtual string CategoryName { get; set; }
}
}


using System;

namespace NHSample2.Domain
{
public class News
{
public virtual Guid Id { get; set; }
public virtual string Subject { get; set; }
public virtual string NewsText { get; set; }
public virtual DateTime DateEntered { get; set; }
public virtual Category Category { get; set; }
public virtual User User { get; set; }
}
}
همانطور که در قسمت‌های قبل نیز ذکر شد، تمام خواص پابلیک کلاس‌های Domain ما به صورت virtual تعریف شده‌اند تا lazy loading را در NHibernate فعال سازیم. در حالت lazy loading ، اطلاعات تنها زمانیکه به آن‌ها نیاز باشد بارگذاری خواهند شد. این مورد در حالتیکه نیاز به نمایش اطلاعات تنها یک شیء وجود داشته باشد بسیار مطلوب می‌باشد، یا هنگام ثبت و به روز رسانی اطلاعات نیز یکی از بهترین روش‌ها است. اما زمانیکه با لیستی از اطلاعات سروکار داشته باشیم باعث کاهش افت کارآیی خواهد شد زیرا برای مثال نمایش آن‌ها سبب خواهد شد که 100 ها کوئری دیگر جهت دریافت اطلاعات هر رکورد در حال نمایش اجرا شود (مفهوم دسترسی به اطلاعات تنها در صورت نیاز به آن‌ها). Lazy loading و eager loading (همانند مثال‌های قبلی) هر دو در NHibernate به سادگی قابل تنظیم هستند (برای مثال LINQ to SQL به صورت پیش فرض همواره lazy load است و تا این تاریخ راه استانداردی برای امکان تغییر و تنظیم این مورد پیش بینی نشده است).

اکنون کلاس جدید Config را به برنامه اضافه نمائید:

using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;

namespace NHSample2
{
class Config
{
public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
{
var cfg = dbType.ConfigureProperties(new Configuration());

new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly).Configure(cfg);

return cfg;
}

public static void GenerateDbScript(Configuration config, string filePath)
{
bool script = true;//فقط اسکریپت دیتابیس تولید گردد
bool export = false;//نیازی نیست بر روی دیتابیس هم اجرا شود
new SchemaExport(config).SetOutputFile(filePath).Create(script, export);
}

public static void BuildDbSchema(Configuration config)
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool drop = false;//آیا اطلاعات موجود دراپ شوند
new SchemaExport(config).Execute(script, export, drop);
}

public static void CreateSQL2008DbPlusScript(string connectionString, string filePath)
{
Configuration cfg =
GenerateMapping(
MsSqlConfiguration
.MsSql2008
.ConnectionString(connectionString)
.ShowSql()
);
GenerateDbScript(cfg, filePath);
BuildDbSchema(cfg);
}

public static ISessionFactory CreateSessionFactory(IPersistenceConfigurer dbType)
{
return
Fluently.Configure().Database(dbType)
.Mappings(m => m.AutoMappings
.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly))
)
.BuildSessionFactory();
}
}
}

در متد GenerateMapping از قابلیت Automapping موجود در فریم ورک Fluent Nhibernate استفاده شده است (بدون نوشتن حتی یک سطر جهت تعریف این نگاشت‌ها). این متد نوع دیتابیس مورد نظر را جهت ساخت تنظیمات خود دریافت می‌کند. سپس با کمک کلاس AutoPersistenceModel این فریم ورک، به صورت خودکار از اسمبلی برنامه نگاشت‌های لازم را به کلاس‌های موجود در پوشه Domain ما اضافه می‌کند (مرسوم است که این پوشه در یک پروژه Class library مجزا تعریف شود که در این برنامه جهت سهولت کار در خود برنامه قرار گرفته است). قسمت Where ذکر شده به این جهت معرفی گردیده است تا Fluent Nhibernate برای تمامی کلاس‌های موجود در اسمبلی جاری، سعی در تعریف نگاشت‌های لازم نکند. این نگاشت‌ها تنها به کلاس‌های موجود در پوشه دومین ما محدود شده‌اند.
سه متد بعدی آن، جهت ایجاد اسکریپت دیتابیس از روی این نگاشت‌های تعریف شده و سپس اجرای این اسکریپت بر روی دیتابیس جاری معرفی شده، تهیه شده‌اند. برای مثال CreateSQL2008DbPlusScript یک مثال ساده از استفاده دو متد قبلی جهت ایجاد اسکریپت و دیتابیس متناظر اس کیوال سرور 2008 بر اساس نگاشت‌های برنامه است.
با متد CreateSessionFactory در قسمت‌های قبل آشنا شده‌اید. تنها تفاوت آن در این قسمت، استفاده از کلاس AutoPersistenceModel جهت تولید خودکار نگاشت‌ها است.

در ادامه دیتابیس متناظر با موجودیت‌های برنامه را ایجاد خواهیم کرد:

using System;

namespace NHSample2
{
class Program
{
static void Main(string[] args)
{
Config.CreateSQL2008DbPlusScript(
"Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true",
"db.sql");

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

پس از اجرای برنامه، ابتدا فایل اسکریپت دیتابیس به نام db.sql در پوشه اجرایی برنامه تشکیل خواهد شد و سپس این اسکریپت به صورت خودکار بر روی دیتابیس معرفی شده اجرا می‌گردد. دیتابیس دیاگرام حاصل را در شکل زیر می‌توانید ملاحظه نمائید:



همچنین اسکریپت تولید شده آن، صرفنظر از عبارات drop اولیه، به صورت زیر است:

create table [Category] (
Id INT IDENTITY NOT NULL,
CategoryName NVARCHAR(255) null,
primary key (Id)
)

create table [User] (
Id INT IDENTITY NOT NULL,
UserName NVARCHAR(255) null,
Password NVARCHAR(255) null,
primary key (Id)
)

create table [News] (
Id UNIQUEIDENTIFIER not null,
Subject NVARCHAR(255) null,
NewsText NVARCHAR(255) null,
DateEntered DATETIME null,
Category_id INT null,
User_id INT null,
primary key (Id)
)

alter table [News]
add constraint FKE660F9E1C9CF79
foreign key (Category_id)
references [Category]

alter table [News]
add constraint FKE660F95C1A3C92
foreign key (User_id)

references [User]

اکنون یک سری گروه خبری، کاربر و خبر را به دیتابیس خواهیم افزود:

using System;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHSample2.Domain;

namespace NHSample2
{
class Program
{
static void Main(string[] args)
{
using (ISessionFactory sessionFactory = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{
using (ISession session = sessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
//با توجه به کلیدهای خارجی تعریف شده ابتدا باید گروه‌ها را اضافه کرد
Category ca = new Category() { CategoryName = "Sport" };
session.Save(ca);
Category ca2 = new Category() { CategoryName = "IT" };
session.Save(ca2);
Category ca3 = new Category() { CategoryName = "Business" };
session.Save(ca3);

//سپس یک کاربر را به دیتابیس اضافه می‌کنیم
User u = new User() { Password = "123$5@1", UserName = "VahidNasiri" };
session.Save(u);

//اکنون می‌توان یک خبر جدید را ثبت کرد

News news = new News()
{
Category = ca,
User = u,
DateEntered = DateTime.Now,
Id = Guid.NewGuid(),
NewsText = "متن خبر جدید",
Subject = "عنوانی دلخواه"
};
session.Save(news);

transaction.Commit(); //پایان تراکنش
}
}
}

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
جهت بررسی انجام عملیات ثبت هم می‌توان به دیتابیس مراجعه کرد، برای مثال:



و یا می‌توان از LINQ استفاده کرد:
برای مثال کاربر VahidNasiri تعریف شده را یافته، اطلاعات آن‌را نمایش دهید؛ سپس نام او را به Vahid ویرایش کرده و دیتابیس را به روز کنید.

برای اینکه کوئری‌های LINQ ما شبیه به LINQ to SQL شوند، کلاس NewsContext را به صورت ذیل تشکیل می‌دهیم. این کلاس از کلاس پایه NHibernateContext مشتق شده و سپس به ازای تمام موجودیت‌های برنامه، یک متد از نوع IOrderedQueryable را تشکیل خواهیم داد.

using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample2.Domain;

namespace NHSample2
{
class NewsContext : NHibernateContext
{
public NewsContext(ISession session)
: base(session)
{ }

public IOrderedQueryable<News> News
{
get { return Session.Linq<News>(); }
}

public IOrderedQueryable<Category> Categories
{
get { return Session.Linq<Category>(); }
}

public IOrderedQueryable<User> Users
{
get { return Session.Linq<User>(); }
}
}
}
اکنون جهت یافتن کاربر و به روز رسانی اطلاعات او در دیتابیس خواهیم داشت:

using System;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using System.Linq;
using NHSample2.Domain;

namespace NHSample2
{
class Program
{
static void Main(string[] args)
{
using (ISessionFactory sessionFactory = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{
using (ISession session = sessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
using (NewsContext db = new NewsContext(session))
{
var query = from x in db.Users
where x.UserName == "VahidNasiri"
select x;

//اگر چیزی یافت شد
if (query.Any())
{
User vahid = query.First();
//نمایش اطلاعات کاربر
Console.WriteLine("Id: {0}, UserName: {0}", vahid.Id, vahid.UserName);
//به روز رسانی نام کاربر
vahid.UserName = "Vahid";
session.Update(vahid);

transaction.Commit(); //پایان تراکنش
}
}
}
}
}

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
مباحث تکمیلی AutoMapping

اگر به اسکریپت دیتابیس تولید شده دقت کرده باشید، عملیات AutoMapping یک سری پیش فرض‌هایی را اعمال کرده است. برای مثال فیلد Id را از نوع identity و به صورت کلید تعریف کرده، یا رشته‌ها را به صورت nvarchar با طول 255 ایجاد نموده است. امکان سفارشی سازی این موارد نیز وجود دارد.

مثال:

using FluentNHibernate.Conventions.Helpers;

public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
{
var cfg = dbType.ConfigureProperties(new Configuration());

new AutoPersistenceModel()
.Conventions.Add()
.Where(x => x.Namespace.EndsWith("Domain"))
.Conventions.Add(
PrimaryKey.Name.Is(x => "ID"),
DefaultLazy.Always(),
ForeignKey.EndsWith("ID"),
Table.Is(t => "tbl" + t.EntityType.Name)
)
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly)
.Configure(cfg);

return cfg;
}

تابع GenerateMapping معرفی شده را اینجا با قسمت Conventions.Add تکمیل کرده‌ایم. به این صورت دقیقا مشخص شده است که فیلدهایی با نام ID باید primary key در نظر گرفته شوند، همواره lazy loading صورت گیرد و نام کلید خارجی به ID ختم شود. همچنین نام جداول با tbl شروع گردد.
روش دیگری نیز برای معرفی این قرار دادها و پیش فرض‌ها وجود دارد. فرض کنید می‌خواهیم طول رشته پیش فرض را از 255 به 500 تغییر دهیم. برای اینکار باید اینترفیس IPropertyConvention را پیاده سازی کرد:

using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;

namespace NHSample2.Conventions
{
class MyStringLengthConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
instance.Length(500);
}
}
}
سپس نحوه‌ی معرفی آن به صورت زیر خواهد بود:

public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
{
var cfg = dbType.ConfigureProperties(new Configuration());

new AutoPersistenceModel()
.Conventions.Add()
.Where(x => x.Namespace.EndsWith("Domain"))
.Conventions.Add<MyStringLengthConvention>()
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly)
.Configure(cfg);

return cfg;
}

نکته:
اگر برای یافتن اطلاعات بیشتر در این مورد در وب جستجو کنید، اکثر مثال‌هایی را که مشاهده خواهید کرد بر اساس نگارش بتای fluent NHibernate هستند و هیچکدام با نگارش نهایی این فریم ورک کار نمی‌کنند. در نگارش رسمی نهایی ارائه شده، تغییرات بسیاری صورت گرفته که آن‌ها را در این آدرس می‌توان مشاهده کرد.

دریافت سورس برنامه قسمت ششم


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

مطالب
قابل ویرایش کننده‌ی فوق العاده x-editable ؛ قسمت دوم
در قسمت قبلی با نحوه‌ی اجرا و ویژگی‌های فنی و خصوصیات کدنویسی x-editable آشنا شدیم. غیر از این خصوصیات، خصوصیات دیگری هم هستند که فقط مختص نوع کنترلی هست که در قسمت type مشخص کرده‌اید.

کنترلهای زیر جهت ورود اطلاعات در ویرایشگر پشتیبانی می‌شوند:
  • text
  • textarea
  • select
  • date
  • datetime
  • dateui
  • combodate
  • html5types
  • checklist
  • wysihtml5
  • typeahead
  • typeaheadjs
  • select2 
text
 clear دکمه‌ای جهت حذف محتوای کادر متنی است. مقدار پیش فرض آن true است.
 escape  برای دفاع در برابر کدهای مخرب html به کار میرود و کاراکترهای مدنظر را در صورت true بودن غیرفعال می‌کند. البته اگر از خاصیت display استفاده کنید این گزینه تاثیرش را از دست خواهد داد.
 inputclass یک کلاس css را به کادر متنی اعمال می‌کند.
placeholder
مقدار داده شده را در صورتی که کادر متنی خالی باشد، نشان می‌دهد.
 tpl به معنی یک قالب. شما می‌توانید کد html تگ input خود را وارد کنید؛ ولی توصیه نمی‌شود.


 TextArea

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

select

خاصیت‌های escape,input,class و tpl را دارد به‌علاوه خاصیت‌های زیر:

 prepend  همانند گزینه پایینی است ولی قبل از آن داده‌های خود را اضافه می‌کند.
 source از آنجا که یک لیست، لیستی از آیتم‌ها را دارد و کاربر یکی از آن‌ها را انتخاب می‌کند، این بخش، منبع آیتم‌ها را معرفی می‌کند. این خاصیت چهار نوع داده می‌پذیرد: آرایه یا شیء‌ایی از مقادیر. تابعی که بعد از انجام هر عملی، اطلاعات به آن پاس می‌شوند و یا از نوع رشته که این رشته یک آدرس سمت سرور است که با درخواست از آن آدرس، اطلاعات را دریافت می‌کند.
 sourceCache
 اگه خاصیت بالا با آدرسی پر شده باشد که از سمت سرور بخواند، در دفعات بعدی مقدار دریافتی را از کش خواهد خواند.
 sourceError  یک پیام خطا هنگام بارگزاری اطلاعات
 sourceOptions  در صورتیکه قصد اضافه کردن پارامتری را به درخواست ایجکسی دارید. یک شیء از پارامترها را به آن نسبت می‌دهیم و برای رونویسی پارامترها از یک تابع استفاده می‌کنیم که نحوه‌ی تغییرات را قبلا در جدول شماره یک دیده‌اید.
 
date
خاصیت‌های مشترک قبلی : tpl,input,class,escape و clear است.
 datepicker  پیکربندی تقویم را بر عهده دارد. برای اطلاعات بیشتر در مورد پیکربندی تقویم به این لینک مراجعه فرمایید.
{ weekStart: 0, startView: 0, minViewMode: 0, autoclose: false }
 format قالب بندی فرمت تاریخ جهت ارسال به سرور\ حالت پیش فرض yyyy-mm-dd
مقادیری که میتوان به کار برد: yy   yyyy mm   m  dd   d
 viewformat  این فرمت هنگام نمایش به کار می‌آید و در صورتیکه مقدار عنصر در این قالب نباشد، آن را تبدیل می‌کند.


 datetime در بوت استراپ

کاملا مشترک با مورد قبلی.


dateUI

مختص JqueryUI است و کاملا مشترک با مورد قبلی.


combodate

موارد مشترک قبلی را دارد ولی به جای خاصیت datepicker از combodate استفاده می‌شود که پیکربندی آن در این لینک قرار دارد.


نوع‌های HTML 5

شامل موارد زیر است:

  • password
  • email
  • url
  • tel
  • number
  • range
  • time 
html5 شامل عناصر زیادی است که ویژگی‌های جالبی را مد نظر دارند؛ ولی ممکن است بعضی المان‌ها در بعضی مرورگرها کارآیی مناسبی نداشته باشند که در این صفحه سازگاری مرورگرها با این نوع المان‌ها ذکر شده است.
خاصیت‌های ذکر شده در مورد نوع text، در مورد آن‌ها نیز صدق می‌کند.

checklist
همانند نوع select است؛ فقط خاصیت separator را دارد که کارش جدا کردن مقادیر است و مقدار پیش فرض آن علامت ',' است.


wysihtml5
سورس و دمو ی این نوع ادیتور که بر پایه‌ی بوت استرپ بنا شده است و زحمت اضافه کردن کتابخانه‌ها به صفحه، بر عهده شماست.
مداخل زیر را به طور دستی به صفحه اضافه کنید:
<link href="js/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css" rel="stylesheet" type="text/css"></link>  
<script src="js/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0.min.js"></script>  
<script src="js/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js"></script>
و همچنین اسکریپت x-editable برای کار با این عنصر را هم اضافه کنید:
<script src="js/inputs-ext/wysihtml5/wysihtml5.js"></script>
این فایل در بسته‌ای که دانلود کرده‌اید موجود است. شامل خاصیت‌های escape,inputclass,placeholder,tpl است و خاصیت wysihtml5 شامل تنظیمات و پیکربندی ادیتور است که پیکریندی آن را می‌توانید در اینجا مطالعه بفرمایید.

typeahead
این گزینه فقط مختص بوت استرپ 2 است و یک کنترل autocomplete به شمار می‌آید. منبع داده‌های آن از طریق خاصیت source به دو صورت آرایه و object تامین می‌گردد.
['text1', 'text2', 'text3' ...]

//or

[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]
شامل خاصیت‌های clear,escape,prepend,source,sourceOptions,sourceError,sourceCache,inputclass,tpl است و شامل خاصیت typeahead جهت پیکربندی آن می‌شود.

typeaheadjs
همانند قبلی است و بر اساس twitterBootstrap است و شامل همان خصوصیات قبلی است. تنها خصوصیت typeahead آن است که باید از این پیکربندی استفاده کنید.

Select2
این المان بر اساس این کتابخانه  سورس باز ایجاد می‌شود. و مستندات آن شامل جزئیات و پیکربندی آن می‌شود. برای معرفی آن فایل‌های زیر را به صفحه معرفی کنید.
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>  
<script src="select2/select2.js"></script>
برای دریافت استایل بوت استرپی آن این فایل را صدا بزنید:
<link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
نکته: در حال حاضر خاصیت autotext روی این المان جواب نمی‌دهد و می‌توانید از خاصیت data-value به جای آن استفاده کنید.

شامل خاصیت‌های inputclass , escape , placeolder , source , tpl می‌باشد و از select2 برای دریافت پیکربندی‌های کنترل استفاده می‌کند و علامت جدا کننده آن توسط viewseperator صورت می‌گیرد.


قالبی نو برای ویرایشگر

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

fn.editableform.template$
 مقدار پیش فرض آن که حتما باید شامل تگ فرم و کلاس‌های مدنظر باشد:
    <form>
        <div>
             <div><div></div><div></div></div>
             <div></div>
        </div> 
    </form>
در صورتی که قصد تغییر کلاس‌های آن را دارید باید کلاس‌های زیر را رونویسی کنید:
  • control-group
  • editable-input
  • editable-buttons
  • editable-error-block

fn.editableform.buttons$ 
    <button type="submit">ok</button>
    <button type="button">cancel</button>
کلاس‌های editable-sumit و editable-cancel به طور خودکار به کلاس editable-buttons تزریق می‌شوند.
و نهایتا جهت تغییر loading

fn.editableform.loading$  
<div></div>

گاهی اوقات نیاز است که خصوصیات این ویرایشگر را در شرایط متغیر صفحه کنترل کنیم، برای مثال گاهی پیش می‌آید که بخواهید در یک شرایط خاص ویرایشگر یک المان خاص را غیرفعال کنید. کد زیر مثال این تغییرات است.
$('#favsite').editable('option', 'disabled', false);

متدها و رویدادها


متدهایی که روی آن قابل اجراست:
editable
ویرایشگر را بر اساس مقادیر اولیه روی عنصر مشخص شده فعال می‌کند.
() activate  فوکوس را به input ویرایشگر باز می‌گرداند.
() destory  حذف ویژگی ویرایش از روی عنصر
() disable  غیرفعال کردن ویرایشگر
() enable  فعال سازی آن
 ()getvalue
باعث بازگردانی مقدار جاری همه عناصر توسط شیء جفت کلید مقدار می‌شود و عناصری که شامل متن یا مقداری نیستند، از آن حذف می‌شوند. در صورتیکه قصد دارید مقدار تنها یک عنصر قابل دریافت باشد، با خاصیت isSingle آن را true کنید.
    $('#username, #fullname').editable('getValue');
    //result:
    {
    username: "superuser",
    fullname: "John"
    }
    //isSingle = true
    $('#username').editable('getValue', true);
    //result "superuser"
 ()hide  مخفی کردن تگ فرم ویرایشگر
(option(key,value
 تغییر خصوصیات یک عنصر که در بالا هم نمونه کد آن را دیدیم.
(setvalue(value,convertStr  ست کردن مقدار جدید کنترل و پارامتر دوم وضعیت تبدیل این مقدار به فرمت داخلی است که برای آن تعریف شده است مثل date
() show  نمایش ویرایشگر
( submit(options  در صورتی که خاصیت ارسال خودکار به سمت سرور را غیر فعال کرده باشید، با این گزینه می‌توانید همه اطلاعات و تغییرات را ارسال کنید. برای ایجاد فرم بر اساس ویرایشگرها و ارسال اطلاعات با کلیک بر روی دکمه submit کاربرد دارد. یک مثال در این زمینه .
پارامترهای options به شرح زیر هستند:
url
data
ajaxoptions
(error(obj
(success(obj,config

از نسخه 1.5.1 میتوان این گزینه را به راحتی روی یک المان خاص هم صدا زد:
$('#username').editable('submit')
() toggle  کدی که صدا زده می‌شود بین دو وضعیت show و hide سوئیچ می‌کند.
() toggleDisabled  تغییر وضعیت بین دو حالت enable و disable
() validate  انجام اعتبارسنجی بر روی همه کنترل ها.
    $('#username, #fullname').editable('validate');
    // possible result:
    {
      username: "username is required",
      fullname: "fullname should be minimum 3 letters length"
    }


رویدادها

 hidden این رویداد زمانی رخ می‌دهد که ویرایشگر دیگر قابل مشاهده نیست و شامل دو پارامتر event و reason است. reason دلیل اینکه چرا ویرایشگر از دید خارج شده است را با یکی از گزینه‌های زیر مشخص می‌کند.
save
cancel
onblur
nochange
manual

    $('#username').on('hidden', function(e, reason) {
        if(reason === 'save' || reason === 'cancel') {
            //auto-open next editable
            $(this).closest('tr').next().find('.editable').editable('show');
        } 
    });
init
موقعی صدا زده میشود که متد editable روی عنصر صدا زده می‌شود و به یاد داشته باشید که این رویداد باید قبل از آن ست شده باشد.
    $('#username').on('init', function(e, editable) {
        alert('initialized ' + editable.options.name);
    });
    $('#username').editable();
save
 موقعی که مقدار جدید، با موفقیت تایید می‌شود. دو پارامتر event و params را باز می‌گرداند که params شامل دو خصوصیت newValue و response است که به ترتیب مقدار جدید و اطلاعات برگشت داده شده از درخواست آژاکس است.
    $('#username').on('save', function(e, params) {
        alert('Saved value: ' + params.newValue);
    });
shown
موقعیکه ویرایشگر نمایش می‌یابد و فرم با موفقیت رندر شده است. برای اشیایی چون select باید صبر کنید تا مقادیر آن‌ها بارگذاری شوند.
    $('#username').on('shown', function(e, editable) {
        editable.input.$input.val('overwriting value of input..');
    });
 

حل مشکل این ابزار در کندو

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


برای حل این مشکل فایل kendo.common-xxx را باز کنید. xxx بر اساس قالبی که برای کندو انتخاب کرده‌اید، می‌تواند متفاوت باشد. در مثال‌های کندو عموما این xxx به نام default شناخته می‌شود یا برای مثال من، bootstrap بود.
بعد از اینکه باز کردید، به دنبال چنین استایلی بگردید:
div.k-treeview{
border-width: 0px;
background: transparent none repeat scroll 0px center;
overflow: auto;
white-space: nowrap;
}
خط زیر را از آن حذف کنید تا مشکل حل شود.
overflow: auto;

نکته بعدی اینکه وقتی ویرایشگر در حالت popup قرار می‌گیرد، مقدار خاصیت title نمایش می‌یابد که عموما با مضامینی چون "کلمه جدید را وارد نمایید" و ... پر می‌شود که به طور پیش فرض سمت چپ قرار گرفته است. کد زیر را در صفحه وارد کنید تا متن در سمت راست قرار بگیرد:
  .popover-title {
        text-align: right;
    }

مطالب
شروع به کار با EF Core 1.0 - قسمت 13 - بررسی سیستم ردیابی تغییرات
هر Context در EF Core، دارای خاصیتی است به نام ChangeTracker که وظیفه‌ی آن ردیابی تغییراتی است که نیاز است به بانک اطلاعاتی منعکس شوند. برای مثال زمانیکه توسط یک کوئری، شیءایی را باز می‌گردانید و سپس مقدار یکی از خواص آن‌را تغییر داده و متد SaveChanges را فراخوانی می‌کنید، این ChangeTracker است که به EF اعلام می‌کند، کوئری Update ایی را که قرار است تولید کنی، فقط نیاز است یک خاصیت را به روز رسانی کند؛ آن هم تنها با این مقدار تغییر یافته.

روش‌های مختلف اطلاع رسانی به سیستم ردیابی تغییرات

متد DbSet.Add کار اطلاع رسانی تبدیل وهله‌‌های ثبت شده را به کوئری‌های Insert رکوردهای جدید، انجام می‌دهد:
using (var db = new BloggingContext())
{
   var blog = new Blog { Url = "http://sample.com" };
   db.Blogs.Add(blog);
   db.SaveChanges();
}

سیستم ردیابی اطلاعات، اگر تغییراتی را در خواص اشیاء تحت نظر خود مشاهده کند، سبب تولید کوئری‌های Update می‌گردد. یک چنین اشیایی تحت نظر Context هستند:
الف) اشیایی که در طول عمر Context از دیتابیس کوئری گرفته شده‌اند.
ب) اشیایی که در طول عمر Context به آن اضافه شده‌اند (حالت قبل).
using (var db = new BloggingContext())
{
  var blog = db.Blogs.First();
  blog.Url = "http://sample.com/blog";
  db.SaveChanges();
}

و متد DbSet.Remove کار اطلاع رسانی تبدیل وهله‌های حذف شده را به کوئری‌های Delete معادل، انجام می‌دهد:
using (var db = new BloggingContext())
{
  var blog = db.Blogs.First();
  db.Blogs.Remove(blog);
  db.SaveChanges();
}
اگر شیء حذف شده پیشتر توسط متد DbSet.Add اضافه شده باشد، تنها این شیء از Context حذف می‌شود و کوئری در مورد آن تولید نخواهد شد.

به علاوه امکان ترکیب متدهای Add، Remove و همچنین به روز رسانی اشیاء در طی یک Context و با فراخوانی یک SaveChanges در انتهای کار نیز وجود دارد. از این جهت که یک Context، الگوی واحد کار را پیاده سازی می‌کند و بیانگر یک تراکنش است. در این حالت ترکیبی، یا کل تراکنش با موفقیت به پایان می‌رسد و یا در صورت بروز مشکلی، هیچکدام از تغییرات درخواستی، اعمال نخواهند شد.


عملیات ردیابی، بر روی هر نوع Projections صورت نمی‌گیرد

اگر توسط LINQ Projections، نتیجه‌ی نهایی کوئری را تغییر دادید، فقط در زمانی سیستم ردیابی بر روی آن فعال خواهد بود که projection نهایی حاوی اصل موجودیت مدنظر باشد. برای مثال در کوئری ذیل چون در Projection صورت گرفته‌ی در متد Select، هنوز در خاصیت Blog، به اصل موجودیت Blog اشاره می‌شود، نتیجه‌ی این کوئری نیز تحت نظر سیستم ردیابی خواهد بود:
using (var context = new BloggingContext())
{
   var blog = context.Blogs
      .Select(b =>
            new
            {
               Blog = b,
               Posts = b.Posts.Count()
            });
 }
اما در کوئری ذیل، خیر:
using (var context = new BloggingContext())
{
   var blog = context.Blogs
            .Select(b =>
                 new
                 {
                   Id = b.BlogId,
                   Url = b.Url
                 });
 }
در اینجا در Projection انجام شده، نتیجه‌ی نهایی، به هیچکدام از موجودیت‌های ممکن اشاره نمی‌کند. بنابراین نتیجه‌ی آن تحت نظر سیستم ردیابی قرار نمی‌گیرد.


لغو سیستم ردیابی تغییرات، در زمانیکه به آن نیازی نیست

سیستم ردیابی تغییرات بر اساس مفاهیم AOP و تولید پروکسی‌های آن کار می‌کند. این پروکسی‌ها، اشیایی شفاف هستند که اشیاء شما را احاطه می‌کنند و هر تغییری را که اعمال می‌کنید، ابتدا از این غشاء رد شده و در سیستم ردیابی EF ثبت می‌شوند. سپس به وهله‌ی اصلی شیء موجود اعمال خواهند شد.
بدیهی است تولید این پروکسی‌ها، دارای سربار است و اگر هدف شما صرفا کوئری گرفتن از اطلاعات، جهت نمایش آن‌ها است، نیازی به تولید خودکار این پروکسی‌ها را ندارید و این مساله سبب کاهش مصرف حافظه‌ی برنامه و بالا رفتن سرعت آن می‌شود.
در قسمت قبل عنوان شد که «یک چنین اشیایی تحت نظر Context هستند: الف) اشیایی که در طول عمر Context از دیتابیس کوئری گرفته شده‌اند.»
اگر می‌خواهید این حالت پیش فرض را لغو کنید، از متد AsNoTracking استفاده نمائید:
using (var context = new BloggingContext())
{
  var blogs = context.Blogs.AsNoTracking().ToList();
}
یک چنین کوئری‌هایی برای سناریوهای فقط خواندنی (گزارشگیری‌ها) مناسب هستند و بدیهی است هرگونه تغییری در لیست blogs حاصل، توسط context جاری ردیابی نشده و در نهایت به بانک اطلاعاتی (در صورت فراخوانی SaveChanges) اعمال نمی‌گردد.

اگر می‌خواهید متد AsNoTracking را به صورت خودکار به تمام کوئری‌های یک context خاص اعمال کنید، روش کار و تنظیم آن به صورت زیر است:
using (var context = new BloggingContext())
{
    context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;


نکات به روز رسانی ارجاعات موجودیت‌ها

دو حالت زیر را درنظر بگیرید که در اولی، blog از بانک اطلاعاتی واکشی شده‌است و post به صورت مستقیم وهله سازی شده‌است:
using (var context = new BloggingContext())
{
  var blog = context.Blogs.First();
  var post = new Post { Title = "Intro to EF Core" };

  blog.Posts.Add(post);
  context.SaveChanges();
}
و در دومی blog به صورت مستقیم وهله سازی گردیده‌است و post از بانک اطلاعاتی واکشی شده‌است:
using (var context = new BloggingContext())
{
  var blog = new Blog { Url = "http://blogs.msdn.com/visualstudio" };
  var post = context.Posts.First();

  blog.Posts.Add(post);
  context.SaveChanges();
}
در حالت اول، Post، ابتدا به بانک اطلاعاتی اضافه شده و سپس این مطلب جدید به لیست ارجاعات blog اضافه می‌شود (Post جدیدی اضافه شده و اولین Blog، جهت درج آن به روز رسانی می‌شود).
در حالت دوم، ابتدا blog در بانک اطلاعاتی ثبت می‌شود (چون برخلاف حالت اول، تحت نظر context نیست) و سپس این post (که تحت نظر context است) به مجموعه مطالب آن اضافه می‌شود (بلاگ جدیدی اضافه شده و ارجاع مطلب موجودی به آن اضافه می‌شود).


وارد کردن یک موجودیت به سیستم ردیابی اطلاعات

در مثال قبل مشاهده کردیم که اگر موجودیتی تحت نظر context نباشد (برای مثال توسط یک کوئری به context وارد نشده باشد)، در حین ذخیره سازی ارجاعات، با آن به صورت یک وهله‌ی جدید رفتار شده و حتما در بانک اطلاعاتی به صورت یک رکورد جدید ذخیره می‌شود؛ حتی اگر Id آن‌را دستی تنظیم کرده باشید که ندید گرفته خواهد شد.
اگر Id و سایر اطلاعات شیءایی را دارید، نیازی نیست تا حتما توسط یک کوئری ابتدا آن‌را از بانک اطلاعاتی دریافت و سپس به صورت خودکار وارد سیستم ردیابی کنید؛ متد Attach نیز یک چنین کاری را انجام می‌دهد:
 var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" };
 context.Blog.Attach(blog);
 context.SaveChanges();
در اینجا هرچند شیء Blog از بانک اطلاعاتی واکشی نشده‌است، اما چون توسط متد Attach به DbSet اضافه شده‌است، اکنون جزئی از اشیاء تحت نظر به حساب می‌آید؛ اما با یک شرط. حالت اولیه‌ی این شیء به EntityState.Unchanged تنظیم شده‌است. یعنی زمانیکه SaveChanges فراخوانی می‌شود، عملیات خاصی صورت نخواهد گرفت و هیچ اطلاعاتی در بانک اطلاعاتی درج نمی‌گردد.
علاوه بر متد Attach، متد AttachRange نیز برای افزودن لیستی از موجودیت‌ها در حالت EntityState.Unchanged، پیش بینی شده‌است.

روش دیگر انجام اینکار به صورت ذیل است:
در اینجا ابتدا یک وهله‌ی جدید از Blog ایجاد شده‌است و سپس توسط متد Entry به Context وارد شده و همچنین حالت آن به صورت صریح، به تغییر یافته، مشخص گردیده‌است:
 var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" };
 context.Entry(blog).State = EntityState.Modified ;
 context.SaveChanges();
و یا می‌توان این عملیات را به صورت زیر ساده کرد:
 var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" };
 context.Update(blog);
 context.SaveChanges();
در اینجا متد جدید Update، همان کار Attach و سپس تنظیم حالت را به EntityState.Modified انجام می‌دهد.
به علاوه متد UpdateRange نیز برای افزودن لیستی از موجودیت‌ها در حالت EntityState.Modified، پیش بینی شده‌است.

یک نکته: متدهای Attach و Update، هم بر روی یک DbSet و هم بر روی Context، قابل اجرا هستند. اگر بر روی Context اجرا شدند، نوع موجودیت دریافتی به نوع DbSet متناظر به صورت خودکار نگاشت شده و استفاده می‌شود (context.Set<T>().Attach(entity)). یعنی در حقیقت بین این دو حالت تفاوتی نیست و امکان فراخوانی این متدها بر روی Context، صرفا جهت سهولت کار درنظر گرفته شده‌است.


تفاوت رفتار context.Entry در EF Core با EF 6.x

متد  context.Entry در EF 6.x هم وجود دارد. اما در EF core سبب تغییر وضعیت گراف متصل به یک شیء نمی‌شود و ضعیت روابط آن‌را به روز رسانی نمی‌کند (برخلاف EF 6.x). اگر در EF Core نیاز به یک چنین به روز رسانی گراف مانندی را داشتید، باید از متد جدید context.ChangeTracker.TrackGraph به نحو ذیل استفاده نمائید:
 context.ChangeTracker.TrackGraph(blog, e => e.Entry.State = EntityState.Added);


کوئری گرفتن از سیستم ردیابی اطلاعات

این سناریوها را درنظر بگیرید:
 - می‌خواهم سیستمی شبیه به تریگرهای اس کیوال سرور را با EF داشته باشم.
 - می‌خواهم اطلاعات تمام رکوردهای ثبت شده، حذف شده و به روز رسانی شده را لاگ کنم.
 - می‌خواهم پس از ثبت رکوردی در هر جای برنامه، شبیه به مباحث SQL Server Service Broker و SqlDependency بلافاصله مطلع شده و توسط SignalR اطلاع رسانی کنم.

و در حالت کلی می‌خواهم پیش و یا پس از ثبت اطلاعات، بتوانم به تغییرات صورت گرفته دسترسی داشته باشم و عملیاتی را بر روی آن‌ها انجام دهم. تمام این موارد و سناریوها را با کوئری گرفتن از سیستم ردیابی اطلاعات EF می‌توان پیاده سازی کرد.
برای نمونه در مطلب قبل و قسمت «طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط»، یک کلاس پایه را که مقادیر پیش فرض خود را از SQL Server دریافت می‌کند، طراحی کردیم. در اینجا می‌خواهیم با استفاده از سیستم ردیابی EF، طراحی این کلاس پایه را عمومی کرده و سازگار با تمام بانک‌های اطلاعاتی موجود کنیم.
جهت یادآوری، کلاس پایه موجودیت‌ها، یک چنین شکلی را داشته:
public class BaseEntity
{
   public int Id { set; get; }
   public DateTime? DateAdded { set; get; }
   public DateTime? DateUpdated { set; get; }
}
و پس از آن، هر موجودیت برنامه به این شکل خلاصه شده و نشانه گذاری می‌شود:
public class Person : BaseEntity
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
}
اکنون به کلاس Context برنامه مراجعه کرده و متد SaveChanges آن‌را بازنویسی می‌کنیم:
    public class ApplicationDbContext : DbContext
    {
        // same as before 

        public override int SaveChanges()
        {
            this.ChangeTracker.DetectChanges();

            var modifiedEntries = this.ChangeTracker
                                      .Entries<BaseEntity>()
                                      .Where(x => x.State == EntityState.Modified);
            foreach (var modifiedEntry in modifiedEntries)
            {
                modifiedEntry.Entity.DateUpdated = DateTime.UtcNow;
            }
 
            var addedEntries = this.ChangeTracker
                                      .Entries<BaseEntity>()
                                      .Where(x => x.State == EntityState.Added);
            foreach (var addedEntry in addedEntries)
            {
                addedEntry.Entity.DateAdded = DateTime.UtcNow;
            }
 
            return base.SaveChanges();
        }
    }
این متد SaveChanges، نقطه‌ی مشترک تمام تغییرات برنامه است. به همین دلیل است که اینجا را می‌توان جهت اعمالی، پیش و پس از فراخوانی متد اصلی base.SaveChanges که کار نهایی درج تغییرات را به بانک اطلاعاتی انجام می‌دهد، مورد استفاده قرار داد.
در اینجا کار با کوئری گرفتن از خاصیت ChangeTracker شروع می‌شود. سپس باید مشخص کنیم چه نوع موجودیت‌هایی را مدنظر داریم. چون تمام موجودیت‌های ما از کلاس پایه‌ی BaseEntity مشتق می‌شوند، بنابراین کوئری گرفتن بر روی این نوع، به معنای دسترسی به تمام موجودیت‌های برنامه نیز هست. سپس در اینجا اگر حالتی EntityState.Modified بود، فقط مقدار خاصیت DateUpdated را به صورت خودکار مقدار دهی می‌کنیم و اگر حالتی EntityState.Added بود، تنها مقدار خاصیت DateAdded را به روز رسانی خواهیم کرد.
در یک چنین حالتی دیگر نیازی نیست تا مقادیر این خواص را در حین ثبت اطلاعات برنامه به صورت دستی مشخص کنیم.

یک نکته: اگر به ابتدای متد بازنویسی شده دقت کنید، فراخوانی متد this.ChangeTracker.DetectChanges در آن انجام شده‌است. علت اینجا است که این فراخوانی به صورت خودکار توسط متد base.SaveChanges انجام می‌شود، اما چون این مرحله را تا انتهای متد بازنویسی شده، به تاخیر انداخته‌ایم، نیاز است خودمان به صورت دستی سبب محاسبه‌ی مجدد تغییرات صورت گرفته شویم.

نکته‌ای در مورد بهبود کیفیت کدهای متد SaveChanges: استفاده‌ی Change Tracker به این صورت با بازنویسی متد SaveChanges بسیار مرسوم است. اما پس از مدتی به متد SaveChanges ایی خواهید رسید که کنترل آن از دست خارج می‌شود. به همین جهت برای EF 6.x پروژه‌هایی مانند EFHooks طراحی شده‌اند تا کپسوله سازی بهتری را بتوان ارائه داد. انتقال کدهای آن به EF Core کار مشکلی نیست و اصل آن، بازنویسی HookedDbContext آن است که نحوه‌ی مدیریت شکیل‌تر کوئری گرفتن از ChangeTracker را بیان می‌کند.


خواص سایه‌ای یا Shadow properties

EF Core به همراه مفهوم کاملا جدیدی است به نام خواص سایه‌ای. این نوع خواص در سمت کدهای ما و در کلاس‌های موجودیت‌های برنامه وجود خارجی نداشته، اما در سمت جداول بانک اطلاعاتی وجود دارند و اکنون امکان کوئری گرفتن و کار کردن با آن‌ها در EF Core میسر شده‌است.
برای تعریف آن‌ها، بجای افزودن خاصیتی به کلاس‌های برنامه، کار از متد OnModelCreating به نحو ذیل شروع می‌شود:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>().Property<DateTime>("DateAdded");
در اینجا یک خاصیت جدید به نام DateAdded، از نوع DateTime که در کلاس Blog وجود خارجی ندارد، تعریف شده‌است. به این خاصیت، shadow property می‌گویند (سایه‌ای است از ستونی از جدول بلاگ).
سپس برای کار کردن و کوئری گرفتن از آن می‌توان از متد جدید EF.Property، به نحو ذیل استفاده کرد:
 var blogs = context.Blogs.OrderBy(b => EF.Property<DateTime>(b, "DateAdded"));
همچنین برای مقدار دهی آن تنها می‌توان توسط سیستم Change Tracker اقدام نمود:
 context.Entry(myBlog).Property("DateAdded").CurrentValue = DateTime.Now;
و یا در همان قطعه کد بازنویسی متد SaveChanges فوق، نحوه‌ی دسترسی به اینگونه خواص، به صورت زیر می‌باشد:
foreach (var addedEntry in addedEntries)
{
  addedEntry.Property("DateAdded").CurrentValue = DateTime.UtcNow;
}
مهم‌ترین دلیل وجودی این خواص، پیاده سازی روابطی مانند many-to-many، در نگارش‌های بعدی EF Core هستند. در حقیقت جدول واسطی که در اینجا به صورت خودکار تشکیل می‌شود، به همراه خواصی است که تاکنون امکان دسترسی به آن‌ها در کدهای EF وجود نداشت؛ اما Shadow properties این امر را میسر می‌کنند (فیلدهایی که در سمت بانک اطلاعاتی وجود دارند، اما در کدهای کلاس‌های ما، خیر).