مطالب
Angular CLI - قسمت چهارم - تنظیمات مسیریابی
«انجام تنظیمات مسیریابی پیش فرض پروژه جدید توسط Angular CLI» را در قسمت دوم این سری بررسی کردیم. در ادامه با قابلیت‌های بیشتری از امکانات تنظیمات مسیریابی موجود در Angular CLI، آشنا خواهیم شد.

یک مثال: در ادامه یک پروژه‌ی جدید مبتنی بر Angular CLI را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
> ng new angular-routing --routing
همانطور که در قسمت دوم نیز ذکر شد، پرچم routing در اینجا، سبب ایجاد فایل app-routing.module.ts نیز خواهد گردید:


 و تنظیمات مرتبط با آن به صورت خودکار به قسمت imports فایل app.module.ts اضافه می‌شوند و آماده‌ی استفاده هستند.
همچنین اگر به فایل src\app\app.component.html مراجعه کنیم، router-outlet نیز به آن افزوده شده‌است و مدیریت نمایش مسیریابی‌ها در این قسمت انجام خواهد شد.

در ادامه‌ی این مثال، دو کامپوننت جدید را به نام‌های dashboard و customer ایجاد می‌کنیم:
>ng g c dashboard
>ng g c customer


هدف این است که مسیریابی‌هایی را جهت کار و نمایش این کامپوننت‌ها ایجاد کنیم. به همین جهت به فایل src\app\app-routing.module.ts مراجعه کرده و تغییرات ذیل را اعمال کنید:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { DashboardComponent } from './dashboard/dashboard.component';
import { CustomerComponent } from './customer/customer.component';

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'dashboard' },
  { path: 'dashboard', component: DashboardComponent },
  { path: 'customer', component: CustomerComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا ابتدا کامپوننت‌های جدید، import شده و سپس یک مسیریابی پیش فرض به کامپوننت dashboard و دو مسیریابی جدید دیگر به کامپوننت‌های dashboard و customer ایجاد شده‌اند.
البته باید دقت داشت که چون پیشتر با اجرای دستورات ng g c، این کامپوننت‌ها به صورت خودکار به تعاریف فایل app.module.ts اضافه شده‌اند، امکان استفاده‌ی از آن‌ها در اینجا میسر است:
@NgModule({
  declarations: [
    AppComponent,
    DashboardComponent,
    CustomerComponent
  ],

پس از تعریف مسیریابی‌ها، به فایل src\app\app.component.html مراجعه کرده و لینک‌هایی را به این مسیریابی‌های جدید ایجاد می‌کنیم:
<h1>
  {{title}}
</h1>
<nav>
  <ul>
    <li><a href="" [routerLink]="['/dashboard']">Dashboard</a></li>
    <li><a href="" [routerLink]="['/customer']">Customer</a></li>
  </ul>
</nav>
<router-outlet></router-outlet>

اکنون اگر دستور کامپایل و گشودن برنامه را در مرورگر پیش فرض سیستم صادر کنیم:
> ng serve -o
یک چنین تصویری حاصل خواهد شد:


با توجه به تنظیمات مسیریابی پیش فرض انجام شده، ابتدا مسیر http://localhost:4200/dashboard بارگذاری شده‌است.


ایجاد ماژول‌های جدید به همراه تنظیمات مسیریابی آن‌ها

در قسمت قبل با نحوه‌ی ایجاد ماژول‌های جدید توسط Angular CLI آشنا شدیم:
> ng g module admin
این فرمان، فایل admin.module.ts را تولید می‌کند. در اینجا می‌توان پرچم مسیریابی را نیز ذکر کرد (برای اینکار یک پنجره‌ی خط فرمان دیگر را باز کنید و اجازه دهید تا پنجره‌ی خط فرمان ng serve -o باز باقی بماند و مدام مشغول بررسی تغییرات و کامپایل پشت صحنه‌ی کار باشد):
> ng g m admin --routing
در این حالت دو فایل admin.module.ts و همچنین admin-routing.module.ts تولید می‌شوند.


سپس داخل این ماژول یک کامپوننت جدید را به نام admin ایجاد می‌کنیم:
> ng g c admin
در اینجا چون این کامپوننت، هم نام پوشه‌ی admin است، داخل همان پوشه ایجاد خواهد شد.
برای مثال اگر نیاز به ایجاد کامپوننت دیگری به نام emails داخل این پوشه بود، باید به نحو ذیل عمل کرد:
> ng g c admin/email
installing component
  create src\app\admin\email\email.component.css
  create src\app\admin\email\email.component.html
  create src\app\admin\email\email.component.spec.ts
  create src\app\admin\email\email.component.ts
  update src\app\admin\admin.module.ts
در این حالت پوشه‌ی جدید email داخل پوشه‌ی admin ایجاد شده و فایل‌های کامپوننت جدید email به آن اضافه می‌شوند. همچنین اگر دقت کنید، اینبار سطر update آخری، فایل admin.module.ts را به روز رسانی کرده‌است و در قسمت declarations آن، دو کامپوننت ایجاد شده را تعریف کرده‌است:
@NgModule({
  imports: [
    CommonModule,
    AdminRoutingModule
  ],
  declarations: [AdminComponent, EmailComponent]
})
export class AdminModule { }

تا اینجا ماژول جدید admin را ایجاد کرده‌ایم؛ اما برنامه‌ی اصلی از آن اطلاعی ندارد. به همین جهت به فایل src\app\app.module.ts مراجعه کرده و این ماژول جدید را به آن معرفی می‌کنیم:
import { AdminModule } from './admin/admin.module';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
 
    AdminModule,

    AppRoutingModule
  ],
ابتدا کلاس این ماژول import شده و سپس آن‌را پیش از AppRoutingModule تعریف می‌کنیم.

در ادامه برای تعریف مسیریابی‌های این ماژول جدید، به فایل src\app\admin\admin-routing.module.ts آن مراجعه کرده و ثابت routes آن‌را مقدار دهی می‌کنیم:
import { AdminComponent } from './admin.component';
import { EmailComponent } from './email/email.component';

const routes: Routes = [
    { 
      path: 'admin', 
      component: AdminComponent,
      children:[
        { path:'', component:EmailComponent },
        { path:'email', component:EmailComponent }
      ]
    }
];
در اینجا مسیریابی admin، دارای فرزند email نیز می‌باشد و پیش فرض آن نیز به email تنظیم شده‌است.
سپس به فایل app\admin\admin.component.html نیز مراجعه کرده و router-outlet آن‌را به آن اضافه می‌کنیم:
<p>
  admin works!
</p>
<router-outlet></router-outlet>
تا اینجا هرچند لینک جدیدی را به ناحیه‌ی ادمین تعریف نکرده‌ایم، اما مسیریابی تعریف شده‌ی آن کار می‌کند:


یک نکته: امکان تولید route guards نیز توسط Angular CLI برای محافظت از مسیریابی خاصی وجود دارد. برای این منظور می‌توان دستور ذیل را صادر کرد:
>ng g guard auth
که سبب تولید فایل auth.guard.ts می‌شود.
مطالب
نمایش خودکار مقدار یکDropDownList با کمک jQuery

نیاز بود هنگام انتخاب یک آیتم دراپ داون لیست در کل برنامه و تمامی دراپ داون‌های آن، مقدار آن‌ها نیز به صورت یک برچسب در کنار آن نمایش داده شود.
برای مثال در لیست زیر:

<asp:DropDownList ID="ddlActive" runat="server">
<asp:ListItem Value="Active">فعال</asp:ListItem>
<asp:ListItem Value="Inactive">غیرفعال</asp:ListItem>
</asp:DropDownList>
اگر آیتم فعال انتخاب شد، مقدار active نیز کنار آن نمایش داده شود و الی آخر.

راه حل اول:
در تمام صفحات به ازای تک تک دراپ داون‌ها یک label اضافه کنیم و همچنین کدهای تمام قسمت‌های برنامه را نیز اصلاح کنیم تا این مورد را لحاظ کند.

راه دوم:
یک کنترل دراپ داون سفارشی را با خاصیت مورد نظر (همراه بودن با یک لیبل) ایجاد کرده و سپس تمام فرم‌ها را باید اصلاح کرد تا از این کنترل جدید استفاده کنند.

راه سوم:
استفاده از jQuery برای اعمال این مهم به کل برنامه بدون نیاز به تغییرات اساسی در آن (و همچنین سازگاری با تمام مرورگرها):

//فقط در این محدوده
$("#mainFormReq select").change(function() {
var currentId = $(this).attr("id"); //آی دی شیء جاری
var val = $(this).val(); //مقدار
var text = $('#' + currentId + ' option:selected').text(); //متن
$("#lbl" + currentId).remove(); //اگر نمونه‌ی قبلی موجود است حذف شود
if (val && (val.length > 0) && (text != val)) {
//اگر متن و مقدار یکی نیست نمایش داده شود
$(this).after('<label id="lbl' + currentId + '">' + val + '</label>');
}
});
توضیحات:
در یک محدوده مشخص شده با ID مساوی mainFormReq (مثلا استفاده از master page ها و نسبت دادن این ID به content آن)، به دنبال تمام select های موجود در آن ناحیه می‌گردیم (اگر mainFormReq حذف شود، این جستجو در کل صفحه صورت خواهد گرفت) و تغییرات آن‌ها را تحت نظر قرار خواهیم داد.
سپس آی دی این کنترل انتخابی را دریافت می‌کنیم (از این ID برای تولید ID برچسب مورد نظر استفاده خواهیم کرد).
در ادامه مقدارهای text و value گزینه انتخابی دریافت می‌شوند (+).
سپس بررسی خواهیم کرد که آیا برچسبی با ID مشخص شده ما وجود دارد (در صورت انتخاب آیتم‌های دیگر، نباید برچسبی غیر منحصربفرد و تکراری در صفحه ایجاد کرد)
در ادامه اگر این مقدار null نبود و همچنین مقدار text و value هم یکی نبودند (اگر یکی بودند لزوم وجود این برچسب بی معنا است)، با استفاده از متد after کتابخانه jQuery یک برچسب را تولید و مقدار مورد نظر را پس از محل نمایش دراپ داون خود، نمایش خواهیم داد.

بهبود کد:
صورت مساله: اکنون نیاز است بجز ناحیه mainFormReq، به سه ناحیه دیگر نیز این تغییرات اعمال گردد. آیا باید همین مقدار کد را سه بار دیگر copy/paste کرد؟
روش صحیح انجام اینکار در jQuery ، نوشتن یک افزونه بر اساس کدهای فوق است که روش انجام آن به صورت زیر می‌باشد (+):

//<![CDATA[
(function($) {
$.fn.dropdownlabel = function() {
return this.change(function() {
var obj = $(this);
var currentId = obj.attr("id"); //آی دی شیء جاری
var val = obj.val(); //مقدار
var text = $('#' + currentId + ' option:selected').text(); //متن
$("#lbl" + currentId).remove(); //اگر نمونه‌ی قبلی موجود است حذف شود
if (val && (val.length > 0) && (text != val)) {
//اگر متن و مقدار یکی نیست نمایش داده شود
obj.after('<label id="lbl' + currentId + '">' + val + '</label>');
}
});
};
})(jQuery);
//]]>
و در نهایت نحوه استفاده از آن (فایلی به نام jquery.dropdownlabel.js ) به صورت زیر خواهد بود:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestDropdownlabel.aspx.cs"
Inherits="testWebForms87.TestDropdownlabel" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

<script src="jquery.min.js" type="text/javascript"></script>
<script src="jquery.dropdownlabel.js" type="text/javascript"></script>

<script type="text/javascript">
$(document).ready(function() {
$("#mainFormReq select").dropdownlabel();
});
</script>

</head>
<body>
<form id="form1" runat="server">
<div id="mainFormReq">
<asp:DropDownList ID="ddlActive" runat="server">
<asp:ListItem Value=""></asp:ListItem>
<asp:ListItem Value="Active">فعال</asp:ListItem>
<asp:ListItem Value="Inactive">غیرفعال</asp:ListItem>
</asp:DropDownList>
</div>
</form>
</body>
</html>

نظرات مطالب
وادار کردن EF Code first به ساخت بانک اطلاعاتی پیش از شروع به کار برنامه
- مطلب جاری برای حالت «AutomaticMigrationsEnabled = true» است که در آن نیازی به اعمال دستی مهاجرت‌ها نیست (در این حالت متد Up و Down ایی وجود ندارد) و همه چیز پس از آن توسط MigrateDatabaseToLatestVersion خودکار است. اگر از مهاجرت‌های «دستی» استفاده می‌کنید، نیازی به این مطلب ندارید. هر زمانیکه دستور Update-Database اجرا می‌شود (یعنی حالت مهاجرت «دستی»)، اجرای متد Seed هم جزئی از آن است.
- اگر رشته‌ی اتصالی به صورت دستی تنظیم می‌شود، MigrateDatabaseToLatestVersion نیاز به اصلاح دارد: « استفاده از چندین بانک اطلاعاتی به صورت همزمان در EF Code First »
نظرات مطالب
EF Code First #4
- این‌ها ساده هستند و قابل اجرا بر روی دیتابیس ریموت (فقط باید ConnectionString را صریحا ذکر کنید):
Update-Database -StartUpProjectName "...name..." -ConnectionString "...data..." -ConnectionProviderName "System.Data.SqlClient"
- و بله. «ویژگی مهاجرت خودکار» را در برنامه فعال کنید و لذت ببرید. نیازی به هیچ دستور پاور شلی ندارد. هر زمان که مدل‌های شما تغییر کرد، به صورت خودکار ساختار بانک اطلاعاتی را در اولین اجرای بعدی برنامه، به روز می‌کند.
- «بازسازی جدول MigrationHistory با کد نویسی در EF Code first»
نظرات مطالب
Blazor 5x - قسمت ششم - مبانی Blazor - بخش 3 - چرخه‌های حیات کامپوننت‌ها
یک نکته‌ی تکمیلی: روش جلوگیری از رندر مجدد کامپوننت‌ها
یکی از مواردی که در Blazor کارآیی را کاهش میدهد، رندر مجدد کامپوننتها بعد از فراخوانی رویدادی میباشد. به صورت پیش‌فرض، و به‌طور خودکار پس از فراخوانی یک رویداد، بلافاصله StateHasChanged کامپوننت فراخوانی می‌شود. در برخی موارد، ممکن است اجرای StateHasChanged غیرضروری باشد. بخصوص برای صفحاتی که تعداد زیادی المنت در آن قرار داشته باشد. این رندر مجدد باعث کاهش سرعت بروزرسانی UI میشود. جهت جلوگیری از اجرای خودکار StateHasChanged میتوانیم اینترفیس   IHandleEvent  را پیاده سازی کنیم که بصورت سراسری بر تمام کامپوننتهای یک صفحه تاثیر بگذارد.
@implements IHandleEvent
@code {
        Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg) => callback.InvokeAsync(arg);
}
 در اینگونه موارد میتوانیم UI را بصورت دستی با فراخوانی StateHasChanged   به‌روز رسانی کنیم.
مطالب
Lazy Loading در AngularJS
وقتی پروژه انگیولاری‌تان کمی گسترش پیدا کند، تعداد زیادی فایل شامل کنترلرها، سرویس‌ها، دایرکتیوها و ... خواهید داشت. واضح است که همه این اجزا همراه با هم مورد نیاز نیستند و برای افزایش سرعت بارگذاری سایت و صرفه جویی در مصرف پهنای باند بهتر است هرکدام از آن‌ها را در هنگام نیاز بارگذاری کنیم. این یعنی همان lazy loading خودمان!
در AngularJS امکانی برای lazy loading فایل‌ها پیشبینی نشده است، پس باید از ابزارهای دیگری که این امکان را فراهم می‌کنند استفاده کرد. من در ادامه از Script.js برای این کار استفاده خواهم کرد، ولی شما می‌توانید از هر کتابخانه دیگری استفاده کنید.
اما مسئله دیگری که پیش از lazy loading  فایل‌ها باید تکلیفش را معلوم کنیم، این است که چطور می‌توانیم اجزایی را به ماژولی که قبلا راه‌اندازی (bootstrap) شده اضافه کنیم. اگر بخواهیم برای مثال کنترلری را در یک فایل مجزا تعریف کنیم، باید آن را به شکلی در ماژول برنامه‌مان ثبت کنیم. فرض کنید این کار را به این ترتیب انجام دهیم: 
angular.module('app').controller('SomeLazyController', function($scope)
{
    $scope.key = '...';
});
در این صورت اگر این کنترلر را در قسمتی از برنامه به صورت ng-controller='SomeLazyController' استفاده کنیم با این خطا مواجه خواهیم شد:
Error: Argument ‘SomeLazyController’ is not a function, got undefined
برای این کار (افزودن اجزایی به ماژولی که قبلا راه اندازی شده) می‌توانیم بجای استفاده از API‌های ماژول، از provider های AngularJS استفاده کنیم. به این ترتیب برای ثبت یک کنترلر باید از  controllerProvider$، برای ثبت یک directive از  compileProvider$، برای ثبت فیلترها از filterProvider$ و برای ثبت سایر اجزا در ماژول از provide$ استفاده کنیم:
// Registering a controller after app bootstrap
$controllerProvider.register('SomeLazyController', function($scope)
{
   $scope.key = '...'; 
});
 
// Registering a directive after app bootstrap
$compileProvider.directive('SomeLazyDirective', function()
{
    return {
        restrict: 'A',
        templateUrl: 'templates/some-lazy-directive.html'
    }
})
 
// etc
اما نکته‌ای که درباره providerها وجود دارد این است که آن‌ها تنها در روال config یک ماژول در دسترس هستند. بنا بر این برای دسترسی به آن‌ها پس از اجرای این روال، ارجاعی به آنها را باید نگهداری کنیم: 
(function () {
    app = angular.module("app", []);

    app.config([
        '$controllerProvider',
        '$compileProvider',
        '$filterProvider',
        '$provide',

    function ($controllerProvider, $compileProvider, $filterProvider, $provide) {
        //برای رجیستر کردن غیر همروند اجزای انگیولاری در آینده
        app.lazy =
        {
            controller: $controllerProvider.register,
            directive: $compileProvider.directive,
            filter: $filterProvider.register,
            factory: $provide.factory,
            service: $provide.service
        };
    }]);
})();
اکنون SomeLazyController  را به این ترتیب می‌توانیم ثبت کنیم: 
angular.module('app').lazy.controller('SomeLazyController', function($scope)
{
    $scope.key = '...';
});
نکته دیگر این است که کجا باید lazy loadign را انجام دهیم. به نظر می‌رسد مناسب‌ترین محل برای انجام این کار خصوصیت resolve مسیریابی است. در  این مطلب و این مطلب resolve در route$ و UI-Router معرفی شده است:
$stateProvider
            .state('state1', {
                url: '/state1',
                template: '<div>{{st1Ctrl.msg}}</div>',
                controller: 'state1Controller as st1Ctrl',
                resolve: {
                    fileDeps: ['$q', '$rootScope', function ($q, $rootScope) {
                        var deferred = $q.defer();
                        var deps = [
                            'app/messageService.js',
                            'app/state1Controller.js'];
                        $script(deps, function () {
                            $rootScope.$apply(function () {
                                deferred.resolve();
                            });
                        });
                        return deferred.promise;
                    }]
                }
            })
            .state('state2', {
                url: '/state2',
                template: '<div>{{st2Ctrl.msg}}</div>',
                controller: 'state2Controller as st2Ctrl',
                resolve: {
                    fileDeps: ['$q', '$rootScope', function ($q, $rootScope) {
                        var deferred = $q.defer();
                        var deps = [
                            'app/messageService.js',
                            'app/state2Controller.js'];
                        $script(deps, function () {
                            $rootScope.$apply(function () {
                                deferred.resolve();
                            });
                        });
                        return deferred.promise;
                    }]
                }
            });
    }]);
کنترلر state1Controller که در فایلی با همین نام پیاده‌سازی شده است تنها در مسیر state1/ مورد نیاز است، و state2Controller تنها در مسیر state2/ لازم است بارگذاری شود. هردوی این کنترلرها به messageService وابستگی دارند که در messageService.js پیاده سازی شده است (همانطور که در این مطلب   اشاره شده می‌توانیم یک حالت انتزاعی به عنوان پدر دو حالت موجود تعریف کرده و وابستگی مشترک را به آن منتقل کنیم).
 برای بارگذاری فایل‌های مورد نیاز در ابتدای کار و راه اندازی اولیه برنامه هم می‌توان به این ترتیب عمل کرد:  
<script type="text/javascript">
        // ----Script.js----
        !function (a, b, c) { function t(a, c) { var e = b.createElement("script"), f = j; e.onload = e.onerror = e[o] = function () { e[m] && !/^c|loade/.test(e[m]) || f || (e.onload = e[o] = null, f = 1, c()) }, e.async = 1, e.src = a, d.insertBefore(e, d.firstChild) } function q(a, b) { p(a, function (a) { return !b(a) }) } var d = b.getElementsByTagName("head")[0], e = {}, f = {}, g = {}, h = {}, i = "string", j = !1, k = "push", l = "DOMContentLoaded", m = "readyState", n = "addEventListener", o = "onreadystatechange", p = function (a, b) { for (var c = 0, d = a.length; c < d; ++c) if (!b(a[c])) return j; return 1 }; !b[m] && b[n] && (b[n](l, function r() { b.removeEventListener(l, r, j), b[m] = "complete" }, j), b[m] = "loading"); var s = function (a, b, d) { function o() { if (!--m) { e[l] = 1, j && j(); for (var a in g) p(a.split("|"), n) && !q(g[a], n) && (g[a] = []) } } function n(a) { return a.call ? a() : e[a] } a = a[k] ? a : [a]; var i = b && b.call, j = i ? b : d, l = i ? a.join("") : b, m = a.length; c(function () { q(a, function (a) { h[a] ? (l && (f[l] = 1), o()) : (h[a] = 1, l && (f[l] = 1), t(s.path ? s.path + a + ".js" : a, o)) }) }, 0); return s }; s.get = t, s.ready = function (a, b, c) { a = a[k] ? a : [a]; var d = []; !q(a, function (a) { e[a] || d[k](a) }) && p(a, function (a) { return e[a] }) ? b() : !function (a) { g[a] = g[a] || [], g[a][k](b), c && c(d) }(a.join("|")); return s }; var u = a.$script; s.noConflict = function () { a.$script = u; return this }, typeof module != "undefined" && module.exports ? module.exports = s : a.$script = s }(this, document, setTimeout)

        $script('Scripts/angular.js', function () {
            $script('Scripts/angular-ui-router.js', function () {
                $script('app/app.js', function () {
                    angular.bootstrap(document, ['app']);
                });
            });
        });
    </script>
توجه داشته باشید که لازم نیست بارگذاری فایل‌ها حتما یکی پس از دیگری باشد. ترتیب بارگذاری فایل‌ها تنها در آن‌هایی که وابستگی به هم دارند باید رعایت شود. همچنین، می‌توانید همه فایل‌های مورد نیاز در این مرحله را Bundle کنید. 
از اینجا می‌توانید پروژه بسیار ساده‌ای که در آن lazy loading پیاده شده است را دانلود کرده و مطالب توضیح داده شده را مشاهده کنید. 
مطالب
آشنایی با TransactionScope
TransactionScope روشی برای پیاده سازی تراکنش در .Net است که برای اولین بار در دات نت 2 معرفی شده است. روش پیاده سازی آن بسیار ساده است و همین سادگی و راحتی کار با اون باعث شده است که خیلی از برنامه نویس‌ها رو متمایل به خودش کنه.  در ادامه به روش استفاده و مزایا و معایب این روش برای پیاده سازی تراکنش‌ها می‌پردازیم.
این روش دارای تمام خواص یک تراکنش است(اصطلاحا به این خواص  ACID Properties گفته میشود)
1-Atomic : به این معناست که تمام دستورات بین بلاک (دستورات SQL و سایر عملیات) باید به صورت عملیات اتمی  کار کنند. یعنی یا تمام عملیات موفقیت آمیز است یا همه با شکست روبرو می‌شوند.
2 - Consistent: به این معناست که اگر تراکنش موفقیت آمیز بود پایگاه داده  باید در شروع تراکنش بعدی تغییرات لازم رو انجام داده باشد و در غیر این صورت پایگاه داده باید به حالت قبل از شروع تراکنش برگردد.
3- Isolated: اگر چند تا تراکنش هم زمان شروع شوند اجرای هیچ کدوم از اون‌ها نباید بر اجرای بقیه تاثیر بزاره.
4- Durable: یعنی تغییرات حاصل شده بعد از اتمام تراکنش باید دائمی باشند.

روش کار به این صورت است تمام کارهایی که قصد داریم در طی یک تراکنش انجام شوند باید در یک بلاک قرار بگیرند و تا زمانی که متد  Complete فراخوانی شود. در این بلاک شما هر عملیاتی رو که به عنوان جزئی از تراکنش می‌دونید قرار بدید. در صورتی که کنترل اجرا به فراخوانی دستور Complete برسه تمام موارد قبل از این دستور Commit  می‌شوند در غیر این صورت RollBack.
به مثال زیر دقت کنید.
ابتدا به پروژه مربوطه باید اسمبلی System.Transaction رو اضافه کنید.
using ( TransactionScope scope = new TransactionScope() )
  {
       //Statement1
       //Statement2
       //Statement3
         scope.Complete();
    }
تمام دستوراتی که در این بلاک نوشته شوند بعد از فراخوانی دستور scope.Complete اصطلاحا Commit می‌شوند. اگر به هر دلیلی فراخوانی دستورات به scope.Complete  نرسد عمل RollBack انجام می‌شود. در نتیجه برای این که عمل RollBack رو انجام دهید بهتره که قبل از دستور  Complete یک Exception رو پرتاب کنید که باعث فراخوانی Dispose می‌شود. کد زیر
using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope() )
            {
                if ( result == 0 )
                {
                    throw new ApplicationException();
                }
                scope.Complete();
            }
نکته حائز اهمیت این است که اگر در هنگام اجرای برنامه به این روش به خطای
MSDTC on server {} is unavailable
برخوردید باید سرویس MSDTC رو Start کنید.برای این کار باید سرویس Distributed Transaction Coordinator رو از لیست سرویس‌های ویندوز پیدا کنید و بر روی اون راست کلیک کرده و دکمه Start رو بزنید.
نکته 1: میزان Timeout در این تراکنش‌ها چه قدر است؟
برای بدست آوردن مقدار Timeout در این گونه تراکنش‌ها می‌توانید از کلاس TransactionManager استفاده کنید. به صورت زیر :

var defaultTimeout = TransactionManager.DefaultTimeout
var maxTimeout = TransactionManager.MaximumTimeout
مقدار پیش فرض برای DefaultTimeout یک دقیقه است و برای MaximumTimeout ده دقیقه است. البته خاصیت‌های بالا به صورت فقط خواندنی هستند و نمی‌تونید از این راه مقدار Timeout هر تراکنش را افزایش یا کاهش دهیم. برای این کار بهتره از روش زیر استفاده کنیم.
TransactionOptions option = new TransactionOptions(); 
 option.Timeout = TimeSpan.MaxValue;

  using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required ,option) ) { scope.Complete(); }
توضیح درباره انواع TransactionScopeOption
1 - Required  : یعنی نیاز به تراکنش وجود دارد. در صورتی که تراکنش در یک تراکنش دیگر شروع شود نیاز به ساختن تراکنش جدید نیست و از همان تراکنش قبلی برای این کار استفاده می‌شود.
2 - RequiresNew: در هر صورت برای محدوده یک تراکنش تولید می‌شود.
3- Suppress : به عنوان محدوه تراکنش در نظر گرفته نمی‌شود.
using(TransactionScope scope1 = new TransactionScope())
{
     try
     {          
          using(TransactionScope scope2 = new  TransactionScope(TransactionScopeOption.Suppress))
          {
               //به دلیل استفاده از Suppress این محدوده خارج از تراکنش محسوب می‌شود
          }
          //شروع محدوده تراکنش
   }
     catch
     {}
   //Rest of scope1
}
مزایا استفاده از این روش
1-این روش از تراکنش‌های توزیع شده پشتیبانی می‌کند . یعنی می‌تونید از چند تا منبع داده استفاده کنید یا می‌تونید از یک تراکنش چند تا Connection به یک منبع داده باز کنید.(استفاده از چند تاconnection در طی یک تراکنش)
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
    string strCmd = "SQL to Execute";
    conn = new SqlClient.SqlConnection("Connection to DB1");
    conn.Open()
    objCmd = new SqlClient.SqlCommand(strCmd, conn);
    objCmd.ExecuteNonQuery();
    string strCmd2 = "SQL to Execute";
    conn2 = new SqlClient.SqlConnection("Connection to DB2");
    conn2.Open()
    objCmd2 = new SqlClient.SqlCommand(strCmd2, conn2);
    objCmd2.ExecuteNonQuery();
}
2- پیاده سازی این روش  واقعا راحت است .
3- با DataProvider‌های متفاوت نظیر Oracle و OleDb و ODBC سازگار است.
4- از تراکنش‌های تو در تو به خوبی پشتیبانی میکنه(Nested Transaction)
using(TransactionScope scope1 = new TransactionScope()) 
{ 
     using(TransactionScope scope2 = new  TransactionScope(TransactionScopeOption.Required)) 
     {
     ...
     } 

     using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew)) 
     {
     ...
     } 

     using(TransactionScope scope4 = new   TransactionScope(TransactionScopeOption.Suppress)) 
    {
     ...
    } 
}
5- به خوبی توسط سرویس‌های WCF پشتیبانی می‌شود و برای سیستم‌های SOA مبتنی بر WCF مناسب است.معایب :
*استفاده از این روش در سیستم هایی که تعداد کاربران آنلاین آن زیاد است و هم چنین تعداد تراکنش‌های موجود نیز در سطح سیستم خیلی زیاد باشه مناسب نیست.
*تراکنش‌های استفاده شده از این روش کند هستند.(مخصوصا که تراکنش در سطح دیتابیس با تعداد و حجم داده زیاد باشه)
امکان تغییر IsolationLevel در طی انجام یک تراکنش امکان پذیر نیست.
(به شخصه مواد * رو در سطح یک پروژه با شرایط کاربران  و حجم داده زیاد تست کردم و نتیجه مطلوب حاصل نشد)

موفق باشید.
مطالب
استفاده از کتابخانه جی کوئری در الکترون
از آنجا که الکترون از مفاهیم وب در دسکتاپ به خوبی پشتیبانی می‌کند، پس به راحتی می‌توان از کتابخانه‌های تحت وب و جاوااسکرپیتی چون جی کوئری و آنگولار و ... استفاده کرد. پروژه‌ای داریم که در آن، حین باز شدن صفحه، به کاربر پیام خوش آمد گویی نشان داده می‌شود:
<!DOCTYPE html>
<html>
  <head>

    <script src="./jquery.min.js"></script>
 <meta charset="utf-8">
    <title></title>
    <script>
$(document).ready(()=>
{
   alert("Welcome");
});
    </script>
  </head>
  <body>
  </body>
</html>
برنامه را اجرا می‌کنیم و در کمال تعجب می‌بینیم که پیامی نمایش داده نمی‌شود. برای اینکه بتوانیم اشکال آن را پیدا کنیم بهتر است ابزارهای سودمند توسعه و دیباگینگ کرومیوم را فراخوانی کنیم. برای باز کردن این پنجره، بعد از ایجاد شیء پنجره (فرضا نام متغیر win باشد) عبارت زیر را می‌نویسیم:
  win.openDevTools();
برنامه را بار دیگر اجرا کنید. در سمت راست برنامه یک پنجره جدید باز می‌شود تا بتوانید از طریق آن به دیباگینگ Render Process بپردازید. اگر اینبار به پنجره کنسول نگاهی بیندازید متوجه می‌شوید که خطای داده شده به دلیل عدم شناخت $ بوده است؛ در صورتی که همه چیز به طور صحیح قرار گرفته است و در یک صفحه وب عادی خیلی راحت اجرا می‌شود، پس مشکل از کجاست؟


module و module.exports
در اکثر کتابخانه‌های جاوااسکریپتی شما با عبارت require زیاد مواجه شده‌اید و این امکان از طریق شیءایی به نام module.exports امکان پذیر شده است. شما با کدی مشابه زیر:
exports.sayHelloInEnglish=()=>
{
  console.log("hello");
}
exports.sayHelloInPersian=()=>
{
  console.log('salam');
}
می‌توانید کتابخانه خود را به این شکل در الکترون صدا بزنید:
const test=require("./test.js");
test.sayHelloInPersian();
test.sayHelloInEnglish();
همانطور که می‌بینید شیء module.exports از این طریق می‌تواند در دسترس دیگران قرار بگیرد. حالا جی کوئری و هر کتابخانه مشابهی که کدی شبیه به کد زیر را داشته باشد می‌تواند به چنین مشکلی دچار شود:
if ( typeof module === "object" && typeof module.exports === "object" ) {
  // set jQuery in `module`
} else {
  // set jQuery in `window`
}

کد بالا بررسی می‌کند که آیا  module.export وجود دارد یا خیر. اگر وجود داشته باشد، در اختیار آن قرار می‌گیرد و اگر نداشته باشد در اختیار شیء window قرار می‌گیرد. پس کاری که جی کوئری اینجا انجام می‌دهد این است که توابع آن در اختیار شیء window نیست و در اختیار exports است. به همین علت ما باید شیء جی کوئری را از طریق آن دریافت کنیم. پس کد زیر را قبل از کدهای جی کوئری می‌نویسیم:
<script src="./jquery.min.js"></script>
<script>
window.$=window.jQuery=module.exports;
</script>
یا
window.$ = window.jQuery = require('./jquery.min.js');
حالا دیگر جی کوئری به راحتی اجرا خواهد شد.
مطالب
استثنای Sequence contains no elements در حین استفاده از LINQ

در ابتدا مثال‌های زیر را در نظر بگیرید:

using System;
using System.Collections.Generic;
using System.Linq;

namespace testWinForms87
{
public class Data
{
public int id { get; set; }
public string name { get; set; }
}

class CLinqTests
{
public static int TestGetListMin1()
{
var lst = new List<Data>
{
new Data{ id=1, name="id1"},
new Data{ id=2, name="id2"},
new Data{ id=3, name="name3"}
};

return (from c in lst
where c.name.Contains("id")
select c.id).Min();
}

public static int TestGetListMin2()
{
var lst = new List<Data>();

return (from c in lst
where c.name.Contains("id")
select c.id).Min();
}
}
}
در متد TestGetListMin1 قصد داریم کوچکترین آی دی رکوردهایی را که نام آن‌ها حاوی id است، از لیست تشکیل شده از کلاس Data بدست آوریم (همانطور که مشخص است سه رکورد از نوع Data در لیست lst ما قرار گرفته‌اند).
محاسبات آن کار می‌کند و مشکلی هم ندارد. اما همیشه در دنیای واقعی همه چیز قرار نیست به این خوبی پیش برود. ممکن است همانند متد TestGetListMin2 ، لیست ما خالی باشد (برای مثال از دیتابیس، رکوردی مطابق شرایط کوئری‌های قبلی بازگشت داده نشده باشد). در این حالت هنگام فراخوانی متد Min ، استثنای Sequence contains no elements رخ خواهد داد و همانطور که در مباحث defensive programming عنوان شد، وظیفه‌ی ما این نیست که خودرو را به دیوار کوبیده (یا منتظر شویم تا کوبیده شود) و سپس به فکر چاره بیفتیم که خوب، عجب! مشکلی رخ داده است!
اکنون چه باید کرد؟ حداقل یک مرحله بررسی اینکه آیا کوئری ما حاوی رکوردی می‌باشد یا خیر باید به این متد اضافه شود (به صورت زیر):

public static int TestGetListMin3()
{
var lst = new List<Data>();
var query = from c in lst
where c.name.Contains("id")
select c.id;

if (query.Any())
return query.Min();
else
return -1;
}
البته می‌شد اگر هیچ رکوردی بازگشت داده نمی‌شد، یک استثنای سفارشی را ایجاد کرد، اما به شخصه ترجیح می‌دهم عدد منهای یک را بر گردانم (چون می‌دانم رکوردهای من عدد مثبت هستند و اگر حاصل منفی شد نیازی به ادامه‌ی پروسه نیست).

شبیه به این مورد در هنگام استفاده از تابع Single مربوط به LINQ نیز ممکن است رخ دهد (تولید استثنای ذکر شده) اما در اینجا مایکروسافت تابع SingleOrDefault را نیز پیش بینی کرده است. در این حالت اگر کوئری ما رکوردی را برنگرداند، SingleOrDefault مقدار نال را برگشت داده و استثنایی رخ نخواهد داد (نمونه‌ی دیگر آن متدهای First و FirstOrDefault هستند).
در مورد متدهای Min و Max ، متدهای MinOrDefault یا MaxOrDefault در دات نت فریم ورک وجود ندارند. می‌توان این نقیصه را با استفاده از extension methods برطرف کرد.

using System;
using System.Collections.Generic;
using System.Linq;

public static class LinqExtensions
{
public static T MinOrDefault<T>(this IEnumerable<T> source, T defaultValue)
{
if (source.Any<T>())
return source.Min<T>();

return defaultValue;
}

public static T MaxOrDefault<T>(this IEnumerable<T> source, T defaultValue)
{
if (source.Any<T>())
return source.Max<T>();

return defaultValue;
}
}
اکنون با استفاده از extension methods فوق، کد ما به صورت زیر تغییر خواهد کرد:

public static int TestGetListMin4()
{
var lst = new List<Data>();
return (from c in lst
where c.name.Contains("id")
select c.id).MinOrDefault(-1);
}

مطالب
تهیه خروجی XML از یک بانک اطلاعاتی، توسط EF Code first
نگارش کامل SQL Server امکان تهیه خروجی XML از یک بانک اطلاعاتی را دارد. اما اگر بخواهیم از سایر بانک‌های اطلاعاتی که چنین توابع توکاری ندارند، استفاده کنیم چطور؟ برای تهیه خروجی XML توسط Entity framework و مستقل از نوع بانک اطلاعاتی در حال استفاده، حداقل دو روش وجود دارد:

الف) استفاده از امکانات Serialization توکار دات نت

using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace DNTViewer.Common.Toolkit
{
    public static class Serializer
    {
        public static string Serialize<T>(T type)
        {
            var serializer = new XmlSerializer(type.GetType());
            using (var stream = new MemoryStream())
            {
                serializer.Serialize(stream, type);
                stream.Seek(0, SeekOrigin.Begin);
                using (var reader = new StreamReader(stream))
                {
                    return reader.ReadToEnd();
                }
            }
        }
    }
}
در اینجا برای نمونه، لیستی از اشیاء مدنظر خود را تهیه کرده و به متد Serialize فوق ارسال کنید. نتیجه کار، تهیه معادل XML آن است.
امکانات سفارشی سازی محدودی نیز برای XmlSerializer درنظر گرفته شده است؛ برای نمونه قرار دادن ویژگی‌هایی مانند XmlIgnore بالای خواصی که نیازی به حضور آن‌ها در خروجی نهایی XML نمی‌باشد.


ب) استفاده از امکانات LINQ to XML دات نت

روش فوق بدون مشکل کار می‌کند، اما اگر بخواهیم قسمت Reflection خودکار ثانویه آن‌را (برای نمونه جهت استخراج مقادیر از لیست دریافتی) حذف کنیم، می‌توان از LINQ to XML استفاده کرد که قابلیت سفارشی سازی بیشتری را نیز در اختیار ما قرار می‌دهد (کاری که در سایت جاری برای تهیه خروجی XML از بانک اطلاعاتی آن انجام می‌شود).

        private string createXmlFile(string dir)
        {
            var xLinq = new XElement("ArrayOfPost",
                        _blogPosts
                            .AsNoTracking()
                            .Include(x => x.Comments)
                            .Include(x => x.User)
                            .Include(x => x.Tags)
                            .OrderBy(x => x.Id)
                            .ToList()
                            .Select(x => new XElement("Post", postXElement(x)))
                            );

            var xmlFile = Path.Combine(dir, "dot-net-tips-database.xml");
            xLinq.Save(xmlFile);
            return xmlFile;
        }

        private static XElement[] postXElement(BlogPost x)
        {
            return new XElement[]
            {
                new XElement("Id", x.Id),
                new XElement("Title", x.Title),
                new XElement("Body", x.Body),
                new XElement("CreatedOn", x.CreatedOn),
                tagElement(x),
                new XElement("User",
                                new XElement("Id", x.UserId.Value),
                                new XElement("FriendlyName", x.User.FriendlyName))
            }.Where(item => item != null).ToArray();
        }

        private static XElement tagElement(BlogPost x)
        {
            var tags = x.Tags.Any() ?
                            x.Tags.Select(y =>
                                        new XElement("Tag",
                                                new XElement("Id", y.Id),
                                                new XElement("Name", y.Name)))
                                  .ToArray() : null;
            if (tags == null)
                return null;

            return new XElement("Tags", tags);
        }
خلاصه‌ای از نحوه تبدیل اطلاعات لیستی از مطالب را به معادل XML آن در کدهای فوق مشاهده می‌کنید. یک سری نکات ریز نیز باید در اینجا رعایت شوند:
1) کار با یک new XElement که دارای متد Save با فرمت XML نیز هست، شروع می‌شود. مقدار آن‌را مساوی یک کوئری از بانک اطلاعاتی قرار می‌دهیم. این کوئری چون قرار است تنها اطلاعاتی را از بانک اطلاعاتی دریافت کند و نیازی به تغییر در آن‌ها نیست، با استفاده از متد AsNoTracking، حالت فقط خواندنی پیدا کرده است.
2) اطلاعاتی را که نیاز است در فایل نهایی XML وجود داشته باشند، تنها کافی است در قسمت Select این کوئری با فرمت new XElement‌های تو در تو قرار دهیم. به این ترتیب قسمت Relection خودکار XmlSerializer روش مطرح شده در ابتدای بحث دیگر وجود نداشته و عملیات نهایی بسیار سریعتر خواهد بود.
3) چون در این حالت، کار انجام شده دستی است، باید نام‌های گره‌های صحیحی را انتخاب کنیم تا اگر قرار است توسط همان XmlSerializer مجددا کار serializer.Deserialize صورت گیرد، عملیات با شکست مواجه نشود. بهترین کار برای کم شدن سعی و خطاها، تهیه یک لیست اطلاعات آزمایشی و سپس ارسال آن به روش ابتدای بحث است. سپس می‌توان با بررسی خروجی آن مثلا دریافت که روش serializer.Deserialize به صورت پیش فرض به دنبال ریشه‌ای به نام ArrayOfPost برای دریافت لیستی از مطالب می‌گردد و نه Posts یا هر نام دیگری.
4) در کوئری LINQ to Entites نوشته شده، پیش از Select، یک ToList قرار دارد. متاسفانه EF اجازه استفاده مستقیم از Select هایی از نوع XElement را نمی‌دهد و باید ابتدا اطلاعات را تبدیل به LINQ to Objects کرد.
5) در حین تهیه XElement‌ها اگر قرار است عنصری نال باشد، باید آن‌را در خروجی نهایی ذکر نکرد. به این ترتیب serializer.Deserialize بدون نیاز به تنظیمات اضافه‌تری بدون مشکل کار خواهد کرد. در غیراینصورت باید وارد مباحثی مانند تعریف یک فضای نام جدید برای خروجی XML به نام XSI رفت و سپس به کمک ویژگی‌ها، xsi:nil را به true مقدار دهی کرد. اما همانطور که در متد postXElement ملاحظه می‌کنید، برای وارد نشدن به مبحث فضای نام xsi، مواردی که null بوده‌اند، اصلا در آرایه نهایی ظاهر نمی‌شوند و نهایتا در خروجی، حضور نخواهند داشت. به این ترتیب متد ذیل، بدون مشکل و بدون نیاز به تنظیمات اضافه‌تری قادر است فایل XML نهایی را تبدیل به معادل اشیاء دات نتی آن کند.

using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace DNTViewer.Common.Toolkit
{
    public static class Serializer
    {
        public static T DeserializePath<T>(string xmlAddress)
        {
            using (var xmlReader = new XmlTextReader(xmlAddress))
                {
                    var serializer = new XmlSerializer(typeof(T));
                    return (T)serializer.Deserialize(xmlReader);
                }
        }
    }
}