مطالب
آموزش WAF
دز طراحی پروژه‌های مقیاس بزرگ و البته به صورت ماژولار همیشه ساختار پروژه اهمیت به سزایی دارد. متاسفانه این مورد خیلی در طراحی پروژه‌ها در نظر گرفته نمی‌شود و اغلب اوقات شاهد آن هستیم که یک پروژه بسیار بزرگ دقیقا به همان صورت پروژهای کوچک و کم اهمیت‌تر مدیریت و پیاده سازی می‌شود که این مورد هم مربوط به پروژه‌های تحت وب و هم پروژه‌های تحت ویندوز و WPF است. برای مدیریت پروژه‌های WPF و Silverlight در این پست به اختصار درباره PRISM بحث شد. مزایا و معایب آن  بررسی و در طی این پست ها(^ و ^) مثال هایی را پیاده سازی کردیم. اما در این پست مفتخرم شما را با یکی دیگر از کتابخانه‌های مربوط به پیاده سازی مدل MVVM آشنا کنم. کتابخانه ای متن باز، بسیار سبک با کارایی بالا.
اما نکته ای که ذکر آن خالی از لطف نیست این است که قبلا از این کتابخانه در یک پروژه بزرگ  و ماژولار WPF  استفاده کردم و نتیجه مطلوب نیز حاصل شد.
معرفی:
WPF Application Framework یا به اختصار WAF کتابخانه کم حجم سبک و البته با کارایی عالی برای طراحی پروژه‌های ماژولار WPF در مقیاس بزرگ طراحی شده است که مدل پیاده سازی ان بر مبنای مدل MVVM و MVC است. شاید برایتان جالب باشد که این کتابخانه دقیقا مدل MVC را با مدل MVVM ترکیب کرده در نتیجه مفاهیم آن بسیار شبیه به پروژه‌های تحت وب MVC است. همانطور که از نام آن پیداست این کتابخانه صرفا برای پروژه‌های WPF طراحی شده، در نتیجه در پروژه‌های Silverlight نمی‌توان از آن استفاده کرد.
ساختار کلی آن به شکل زیر می‌باشد:

همانطور که مشاهده می‌کنید پروژه‌های مبتنی بر این کتابخانه همانند سایر کتابخانه‌های MVVM از سه بخش تشکیل شده اند. بخش اول با عنوان Shell یا Presentation معرف فایل‌های Xaml پروژه است، بخش دوم یا Application معرف ViewModel و Controller و البته IView  می‌باشد. بخش Domain نیز در برگیرنده مدل‌های برنامه است.

معرفی برخی مفاهیم:

»Shell :  این کلاس معادل یک فایل Xaml است که حتما باید یک اینترفیس IView را پیاده سازی نماید.
»IView : معرف یک اینترفیس جهت برقراری ارتباط بین ViewModel و Shell
»ViewModel : در این جا ViewModel با مفهوم ViewModel در سایر کتابخانه‌های MVVM  کمی متفاوت است. در این کتابخانه ViewModel فقط شامل تعاریف است  و هیچ گونه پیاده سازی در اینجا صورت نمی‌گیرد. دقیقا معادل مفهوم ViewModel در پروژه‌های MVC تحت وب.
»Controller : پیاده سازی ViewModel و تعریف رفتارها در این قسمت انجام می‌گیرد.

اما در بسیاری از پروژها نیاز به پیاده سازی الگوی DataModel-View-ViewModel است که این کتابخانه با دراختیار داشتن برخی کلاس‌های پایه این مهم را برایمان میسر کرده است.

همانطور که می‌بینید در این حالت بر خلاف حالت قبلی ViewModel  و کنترلر‌های پروژه به جای ارتباط با مدل با مفهوم DataModel تغذیه می‌شوند که یک پیاده سازی سفارشی از مدل‌های پروژه است. هم چنین این کتابخانه یک سری Converter‌های سفارشی جهت تبدیل Model به DataModel و برعکس را ارائه می‌دهد.
سرویس‌های پیش فرض: که شامل DialogBox جهت نمایش پیغام‌ها و Save|Open File Dialog سفارشی نیز می‌باشد.
 »برای پیاده سازی Modularity از کتابخانه MEF استفاده شده است.
Command‌های سفارشی: پیاده سازی خاص از اینترفیس ICommand
»مفاهیم مربوط به Weak Event Pattern به صورت توکار در این کتابخانه تعبیه شده است.
»به صورت پیش فرض مباحث مربوط به اعتبارسنجی با استفاده از DataAnnotation   و IDataErrorInfo در این کتابخانه تعبیه شده است.
»ارائه Extension‌های مربوط به UnitTest نظیر Exceptions  و CanExecuteChangedEvent و PopertyChanged جهت سهولت در تهیه unit test

دانلود و نصب
با استفاده از nuget  و دستور زیر می‌توانید این کتابخانه را نصب نمایید:
Install-Package waf
هم چنین می‌توانید سورس آن به همراه فایل‌های باینری را از اینجا دریافت کنید. در پست بعدی یک نمونه از پیاده سازی مثال با این کتابخانه را بررسی خواهیم کرد.
نظرات مطالب
ذخیره سازی فایل‌ها در دیتابیس یا استفاده از فایل سیستم متداول؟
اما معایب استفاده از دیتابیس برای فایل ها رو ننوشتی که فکر میکنم در انتهای مقاله این مورد رو هم باید بررسی میکردی . مثلا فرض کن یک سرویس نوشتی که فایل های آهنگ و عکس های کاربرانشو میگیره و باید ذخیره کنه و چندین بار پخش کنه یا نمایش بده . مسلما نمایش هر عکس اگر بخواد از دیتابیس لود بشه، فشار زیادی را روی dbms میذاره .. بخصوص وقتی تعداد عکس ها و درخواست نمایش بالاست ..
مطالب
پیاده سازی پروژه‌های React با TypeScript - قسمت هفتم - تعیین نوع هوک useContext
پیشتر در مطلب «React 16x - قسمت 33 - React Hooks - بخش 4 - useContext Hook» با هوک useContext آشنا شدیم. در این قسمت می‌خواهیم نکات تایپ‌اسکریپتی آن‌را بررسی کنیم.


ایجاد UserContext

فرض کنید برای انتقال داده‌های کاربر وارد شده‌ی به سیستم، از روش انتقال props از بالاترین کامپوننت موجود در component tree به پایین‌ترین کامپوننت آن، نیاز است از Context استفاده کنیم. به همین جهت فایل جدید src\contexts\userContext.tsx را با محتوای زیر ایجاد می‌کنیم:
import React from "react";

export type User = {
  name: string;
  isActive: boolean;
  logOut: () => void;
};

export const UserContext = React.createContext<User>({});
export const useUser = () => React.useContext(UserContext);
متد createContext در اصل یک متد جنریک است که بر اساس نوع آرگومان جنریک آن، مقدار شیء تامین کننده‌ی مقدار آن مشخص می‌شود. برای مثال اگر نوع User را تعریف کرده و به آن انتساب دهیم، یک چنین امضایی را پیدا می‌کند:
function React.createContext<User>(defaultValue: User,
 calculateChangedBits?: ((prev: User, next: User) => number) | undefined): React.Context<User>
اما ... قطعه کد فوق با خطای تایپ‌اسکریپتی زیر کامپایل نمی‌شود:
Argument of type '{}' is not assignable to parameter of type 'User'.
Type '{}' is missing the following properties from type 'User': name, isActive, logOutts(2345)
عنوان می‌کند که شیء خالی که به createContext ارسال کرده‌ایم، از نوع User نیست. برای رفع این مشکل می‌توان از مفهومی به نام Partial استفاده کرد:
export const UserContext = React.createContext<Partial<User>>({});
به این ترتیب تمام خواص نوع User به صورت اختیاری معرفی می‌شوند و در این حالت انتساب یک شیء خالی اولیه به آن، مشکلی را ایجاد نخواهد کرد.

نکته 1: البته می‌توان آرگومان جنریک آن‌را ذکر نکرد و createContext را با یک شیء آغازین از نوع User مقدار دهی کرد. در این حالت با استفاده از Type Inference، نوع آن، همان User درنظر گرفته می‌شود و دیگر نیازی به ذکر صریح این نوع نخواهد بود.
نکته 2: اگر از شیء مقدار دهی شده‌ی آغازین استفاده کنید، دیگر حتی نیازی به ذکر export type User هم نیست. نوع createContext بر اساس نوع خواص شیء آغازین ارسالی به آن در سراسر برنامه مشخص شده و قابل دسترسی می‌شود؛ با intellisense کامل و type checking دقیق.
نکته 3: useUser تعریف شده، در حقیقت یک هوک سفارشی است که می‌توان بجای React.useContext از آن در سایر قسمت‌های برنامه استفاده کرد.
نکته 4: اگر علاقمند به استفاده‌ی از نوع Partial نیستید، می‌توان از union types هم در اینجا استفاده کرد. در این حالت می‌توان مقدار اولیه را undefined تعریف کرد:
export const UserContext = React.createContext<User | undefined>(undefined);


معرفی UserContext به بالاترین کامپوننت موجود در component tree

برای این منظور به فایل src\App.tsx مراجعه کرده و آن‌را با UserContext.Provider محصور می‌کنیم:
import { User, UserContext } from "./contexts/userContext";
// ...

function App() {
  const user: User = {
    name: "User 1",
    isActive: true,
    logOut: () => {
      console.log("LogOut.");
    },
  };

  return (
    <UserContext.Provider value={user}>
    // ... 
    </UserContext.Provider>
  );
}

export default App;
ابتدا نوع User و سپس UserContext  را import کرده‌ایم و سپس کل محتوای موجود در کامپوننت App را با UserContext.Provider که دارای مقدار user ابتدایی تعریف شده‌است، محصور می‌کنیم.


خواندن شیء Context در کامپوننتی دیگر

برای این منظور به کامپوننت src\components\Head.tsx که در قسمت‌های قبل ایجاد کردیم، مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
import { UserContext } from "../contexts/userContext";

// ...

export const Head = ({ title, isActive = true }: Props) => {
  const context = React.useContext(UserContext); // or useUser();
// ...
};
در اینجا با استفاده از هوک useContext به UserContext دریافتی دسترسی یافته و سپس می‌توان با اطلاعات شیء User کار کرد.
اولین مزیت کار با TypeScript در اینجا، وجود intellisense کامل به همراه context است:


و یا اگر از Object Destructuring استفاده کنیم، در صورت وجود یک اشتباه تایپی، بلافاصله در زمان کامپایل، خطایی را دریافت می‌کنیم:



یک نکته: اگر UserContext را با استفاده از union types تعریف کنیم:
export const UserContext = React.createContext<User | undefined>(undefined);
هنگام استفاده‌ی از آن، خطای عدم وجود خاصیت‌های آن‌را در حین Object Destructuring دریافت می‌کنیم:


علت اینجا است که با فعال بودن بررسی نوع‌های نال‌نپذیر، چون user context اکنون می‌تواند undefined هم باشد، یا باید از روش context?.name استفاده کرد و یا یک علامت تعجب را پس از useContext قرار داد:
const context = React.useContext(UserContext)!; // or useUser()!;
const { name } = context;
ذکر ! به این معنا است که می‌دانیم خروجی این متد، نال و یا undefined نیست (راهنمایی کردن کامپایلر TypeScript). چون پیشتر آن‌را در کامپوننت App، در حین تعریف Provider، مقدار دهی اولیه کرده‌ایم.
نظرات مطالب
یافتن خطاهای متداول کدهای جاوا اسکریپتی با غنی سازی تنظیمات کامپایلر TypeScript
یک نکته‌ی تکمیلی: ساده سازی فعالسازی گزینه‌های مختلف کامپایلر TypeScript

پس از به روز رسانی کامپایلر تایپ‌اسکریپت با دستور npm install -g typescript، اگر دستور tsc --init را در یک پوشه‌ی جدید اجرا کنید، سبب تولید یک فایل tsconfig.json از پیش تنظیم شده، بر اساس آخرین قابلیت‌های کامپایلر TypeScript می‌شود. برای مثال تنظیمات ذیل قسمتی از تنظیمات نگارش 2.7.1 است و برای نمونه strictPropertyInitialization به آن اضافه شده‌است:
    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */
اگر می‌خواهید به ازای هر به روز رسانی کامپایلر تایپ‌اسکریپت این تنظیمات را تغییر ندهید، فقط کافی است تنظیم strict": true" را قید کنید و مابقی را تنها در صورت نیاز به false کردن آن‌ها ذکر نمائید.
مطالب
آموزش TypeScript #3
در این پست به تشریح انواع داده در زبان TypeScript و ذکر مثال در این زمینه می‌پردازیم.
 
تعریف متغیر‌ها و انواع داده
در TypeScript هنگام تعریف متغیر‌ها باید نوع داده ای آن‌ها را مشخص کنیم. در TypeScript پنج نوع داده ای وجود دارد که در زیر با ذکر مثال تعریف شده اند. مفاهیم ماژول، کلاس و تابع در پست بعدی به تشریح توضیح داده خواهند شد.

 number : معادل نوع داده ای number در JavaScript است. برای ذخیره سازی اعداد صحیح و اعشاری استفاده می‌شود.
یک مثال:
class NumberTypeOfTypeScript { 
    MyFunction()
    {
        var p: number; 
        p = 1;
        var q = 2;
        var r = 3.33;
        alert("Value of P=" + p + "  Value of q=" + q + " Value of r=" + r);           
    }
}

window.onload = () =>{ 
    var value = new NumberTypeOfTypeScript();
    value.MyFunction();
}
حال باید یک فایل Html برای استفاده از این کلاس داشته باشیم. به صورت زیر:
<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="app.js"></script>
</head>
<body>
    <h1>Number Type in TypeScript</h1> 
    <div id="content"/>
</body>
</html>
بعد از اجرای پروزه خروجی به صورت زیر خواهد بود:

string : معادل نوع داده ای رشته ای است و برای ذخیره سازی مجموعه ای از کاراکتر‌ها از نوع UTF-16 استفاده می‌شود.

یک مثال:
class StringTypeOfTypeScript { 
    Myfunction() { 
      var  s: string;
      s="TypeScript"
      var empty = "";
      var abc = "abc";
      alert("Value of s="+ s+" Empty string="+ empty+" Value of abc ="+abc) ;     
    }
}
window.onload = () =>{ 
    var value = new StringTypeOfTypeScript();
    value.Myfunction();
}
کد کامپایل شده و تبدیل آن به JavaScript: 
var StringTypeOfTypeScript = (function () {
    function StringTypeOfTypeScript() { }
    StringTypeOfTypeScript.prototype.Myfunction = function () {
        var s;
        s = "TypeScript";
        var empty = "";
        var abc = "abc";
        alert("Value of s=" + s + " Empty string=" + empty + " Value of abc =" + abc);
    };
    return StringTypeOfTypeScript;
})();
window.onload = function () {
    var value = new StringTypeOfTypeScript();
    value.Myfunction();
};
خروجی به صورت زیر است:


boolean: برای ذخیره سازی مقادیر true یا false می‌باشد.
مثال:
class booleanTypeofTypeScript { 
    MyFunction() {
        var lie: bool;        
        lie = false;
        var a = 12;
        if (typeof (lie) == "boolean" && typeof (a) == "boolean") {
            alert("Both is boolean type");
        }

        if (typeof (lie) == "boolean" && typeof (a) != "boolean") {
            alert("lie is boolean type and a is not!") 
        }
        else { 
            alert("a is boolean type and lie is not!");
        }
    }     
}

window.onload =()=> { 
    var access = new booleanTypeofTypeScript();
    access.MyFunction();
 }
کد کامپایل شده و تبدیل آن به JavaScript:
var booleanTypeofTypeScript = (function () {
    function booleanTypeofTypeScript() { }
    booleanTypeofTypeScript.prototype.MyFunction = function () {
        var lie;
        lie = false;
        var a = 12;
        if(typeof (lie) == "boolean" && typeof (a) == "boolean") {
            alert("Both is boolean type");
        }
        if(typeof (lie) == "boolean" && typeof (a) != "boolean") {
            alert("lie is boolean type and a is not!");
        } else {
            alert("a is boolean type and lie is not!");
        }
    };
    return booleanTypeofTypeScript;
})();
window.onload = function () {
    var access = new booleanTypeofTypeScript();
    access.MyFunction();
};
null: همانند دات نت هنگامی که قصد داشته باشیم مقدار یک متغیر را null اختصاص دهیم از این کلمه کلیدی استفاده می‌کنیم.
مثال:
class NullTypeinTypeScript { 
    MyFunction() { 
        var p: number = null;
        var x = null;
        if (p== null) {
            alert("p has null value!");
        }
        else { alert("p has a value"); }
    }
}
window.onload = () =>{ 
    var value = new NullTypeinTypeScript();
    value.MyFunction();
}
کد کامپایل شده و تبدیل آن به JavaScript:
var NullTypeinTypeScript = (function () {
    function NullTypeinTypeScript() { }
    NullTypeinTypeScript.prototype.MyFunction = function () {
        var p = null;
        var x = null;
        if(p == null) {
            alert("p has null value!");
        } else {
            alert("p has a value");
        }
    };
    return NullTypeinTypeScript;
})();
window.onload = function () {
    var value = new NullTypeinTypeScript();
    value.MyFunction();
};

undefined:معادل نوع undefined در Javascript است. اگر به یک متغیر مقدار اختصاص ندهید مقدار آن undefined خواهد بود.
مثال:
class UndefinedTypeOfTypeScript { 
    Myfunction() { 
        var p: number;
        var x = undefined;
        if (p == undefined && x == undefined) {
            alert("p and x is undefined");
        }
        else { alert("p and c cannot undefined"); }
    }
}
window.onload = () =>{ 
    var value = new UndefinedTypeOfTypeScript();
    value.Myfunction();
}
کد کامپایل شده و تبدیل آن به JavaScript: 
var UndefinedTypeOfTypeScript = (function () {
    function UndefinedTypeOfTypeScript() { }
    UndefinedTypeOfTypeScript.prototype.Myfunction = function () {
        var p;
        var x = undefined;
        if(p == undefined && x == undefined) {
            alert("p and x is undefined");
        } else {
            alert("p and c cannot undefined");
        }
    };
    return UndefinedTypeOfTypeScript;
})();
window.onload = function () {
    var value = new UndefinedTypeOfTypeScript();
    value.Myfunction();
};
خروجی این مثال نیز به صورت زیر است:

ادامه دارد...
مطالب
یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017
در این مطلب مثالی را در مورد نحوه‌ی تنظیمات یک پروژه‌ی خالی ASP.NET Core، جهت استفاده‌ی از یک پروژه‌ی Angular CLI قرار گرفته‌ی در پوشه‌ی آن‌را بررسی خواهیم کرد.

پیشنیازها

 - مطالعه‌ی سری کار با Angular CLI خصوصا قسمت نصب و قسمت ساخت برنامه‌های آن، پیش از مطالعه‌ی این مطلب ضروری است.
 - همچنین فرض بر این است که سری ASP.NET Core را نیز یکبار مرور کرده‌اید و با نحوه‌ی برپایی یک برنامه‌ی MVC آن و ارائه‌ی فایل‌های استاتیک توسط یک پروژه‌ی ASP.NET Core آشنایی دارید.


ایجاد یک پروژه‌ی جدید ASP.NET Core در VS 2017

در ابتدا یک پروژه‌ی خالی ASP.NET Core را در VS 2017 ایجاد خواهیم کرد. برای این منظور:
 - ابتدا از طریق منوی File -> New -> Project (Ctrl+Shift+N) گزینه‌ی ایجاد یک ASP.NET Core Web application را انتخاب کنید.
 - در صفحه‌ی بعدی آن هم گزینه‌ی «empty template» را انتخاب نمائید.


تنظیمات یک برنامه‌ی ASP.NET Core خالی برای اجرای یک برنامه‌ی Angular CLI

برای اجرای یک برنامه‌ی مبتنی بر Angular CLI، نیاز است بر روی فایل csproj برنامه‌ی ASP.NET Core کلیک راست کرده و گزینه‌ی Edit آن‌را انتخاب کنید.
سپس محتوای این فایل را به نحو ذیل تکمیل نمائید:

الف) درخواست عدم کامپایل فایل‌های TypeScript
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
  </PropertyGroup>
چون نحوه‌ی کامپایل پروژه‌های Angular CLI صرفا مبتنی بر کامپایل مستقیم فایل‌های TypeScript آن نیست و در اینجا از یک گردش کاری توکار مبتنی بر webpack، به صورت خودکار استفاده می‌کند، کامپایل فایل‌های TypeScript توسط ویژوال استودیو، مفید نبوده و صرفا سبب دریافت گزارش‌های خطای بیشماری به همراه کند کردن پروسه‌ی Build آن خواهد شد. بنابراین با افزودن تنظیم TypeScriptCompileBlocked به true، از VS 2017 خواهیم خواست تا در این زمینه دخالت نکند.

ب) مشخص کردن پوشه‌هایی که باید الحاق و یا حذف شوند
  <ItemGroup>
    <Folder Include="Controllers\" />
    <Folder Include="wwwroot\" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="node_modules\**" />
    <Content Remove="node_modules\**" />
    <EmbeddedResource Remove="node_modules\**" />
    <None Remove="node_modules\**" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="src\**" />
    <Content Remove="src\**" />
    <EmbeddedResource Remove="src\**" />    
  </ItemGroup>
در اینجا پوشه‌های کنترلرها و wwwroot به پروژه الحاق شده‌اند. پوشه‌ی wwwroot جایی است که فایل‌هایی خروجی را Angular CLI ارائه خواهد کرد.
سپس دو پوشه‌ی node_modules و src واقع در ریشه‌ی پروژه را نیز به طور کامل از سیستم ساخت و توزیع VS 2017 حذف کرده‌ایم. پوشه‌ی node_modules وابستگی‌های Angular را به همراه دارد و src همان پوشه‌ی برنامه‌ی Angular ما خواهد بود.

ج) افزودن وابستگی‌های سمت سرور مورد نیاز
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
  </ItemGroup>
 
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />
  </ItemGroup>
 
  <ItemGroup>
    <!-- extends watching group to include *.js files -->
    <Watch Include="**\*.js" Exclude="node_modules\**\*;**\*.js.map;obj\**\*;bin\**\*" />
  </ItemGroup>
برای اجرای یک برنامه‌ی تک صفحه‌ای وب Angular، صرفا به وابستگی MVC و StaticFiles آن نیاز است.
در اینجا Watcher.Tools هم به همراه تنظیمات آن اضافه شده‌اند که در ادامه‌ی بحث به آن اشاره خواهد شد.


افزودن یک کنترلر Web API جدید

با توجه به اینکه دیگر در اینجا قرار نیست با فایل‌های cshtml و razor کار کنیم، کنترلرهای ما نیز از نوع Web API خواهند بود. البته در ASP.NET Core، کنترلرهای معمولی آن، توانایی ارائه‌ی Web API و همچنین فایل‌های Razor را دارند و از این لحاظ تفاوتی بین این دو نیست و یکپارچگی کاملی صورت گرفته‌است.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
 
namespace ASPNETCoreIntegrationWithAngularCLI.Controllers
{
  [Route("api/[controller]")]
  public class ValuesController : Controller
  {
    // GET: api/values
    [HttpGet]
    [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
    public IEnumerable<string> Get()
    {
      return new string[] { "Hello", "DNT" };
    }
  }
}
در اینجا کدهای یک کنترلر نمونه را جهت بازگشت یک خروجی JSON ساده مشاهده می‌کنید که در ادامه، در برنامه‌ی Angular CLI تهیه شده از آن استفاده خواهیم کرد.


تنظیمات فایل آغازین یک برنامه‌ی ASP.NET Core جهت ارائه‌ی برنامه‌های Angular

در ادامه به فایل Startup.cs برنامه‌ی خالی جاری، مراجعه کرده و آن‌را به نحو ذیل تغییر دهید:
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace ASPNETCoreIntegrationWithAngularCLI
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
 
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();
 
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
 
            app.Use(async (context, next) => {
                await next();
                if (context.Response.StatusCode == 404 &&
                    !Path.HasExtension(context.Request.Path.Value) &&
                    !context.Request.Path.Value.StartsWith("/api/", StringComparison.OrdinalIgnoreCase))
                {
                    context.Request.Path = "/index.html";
                    await next();
                }
            });
 
            app.UseMvcWithDefaultRoute();
            app.UseDefaultFiles();
            app.UseStaticFiles();
        }
    }
}
در اینجا برای ارائه‌ی کنترلر Web API، نیاز به ثبت سرویس‌های MVC است. همچنین ارائه‌ی فایل‌های پیش فرض و فایل‌های استاتیک (همان پوشه‌ی wwwroot برنامه) نیز فعال شده‌اند.
در قسمت app.Use آن، تنظیمات URL Rewriting مورد نیاز جهت کار با مسیریابی برنامه‌های Angular را مشاهده می‌کنید. برای نمونه اگر کاربری در ابتدای کار آدرس /products را درخواست کند، این درخواست به سمت سرور ارسال می‌شود و چون چنین صفحه‌ای در سمت سرور وجود ندارد، خطای 404 بازگشت داده می‌شود و کار به پردازش برنامه‌ی Angular نخواهد رسید. اینجا است که تنظیم میان‌افزار فوق، کار مدیریت خروجی‌های 404 را بر عهده گرفته و کاربر را به فایل index.html برنامه‌ی تک صفحه‌ای وب، هدایت می‌کند. به علاوه در اینجا اگر درخواست وارد شده، دارای پسوند باشد (یک فایل باشد) و یا با api/ شروع شود (اشاره کننده‌ی به کنترلرهای Web API برنامه)، از این پردازش و هدایت به صفحه‌ی index.html معاف خواهد شد.


ایجاد ساختار اولیه‌ی برنامه‌ی Angular CLI در داخل پروژه‌ی جاری

اکنون از طریق خط فرمان به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core‌، جائیکه فایل Startup.cs قرار دارد، وارد شده و دستور ذیل را اجرا کنید:
 >ng new ClientApp --routing --skip-install --skip-git --skip-commit
به این ترتیب پوشه‌ی جدید ClientApp، در داخل پوشه‌ی برنامه اضافه خواهد شد که در آن تنظیمات اولیه‌ی مسیریابی Angular نیز انجام شده‌است؛ از دریافت وابستگی‌های npm آن صرفنظر شده و همچنین کار تنظیمات git آن نیز صورت نگرفته‌است (تا از تنظیمات git پروژه‌ی اصلی استفاده شود).
پس از تولید ساختار برنامه‌ی Angular CLI، به پوشه‌ی آن وارد شده و تمام فایل‌های آن را Cut کنید. سپس به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core جاری، وارد شده و این فایل‌ها را در آنجا paste نمائید. به این ترتیب به حداکثر سازگاری ساختار پروژه‌های Angular CLI و VS 2017 خواهیم رسید. زیرا اکثر فایل‌های تنظیمات آن‌را می‌شناسد و قابلیت پردازش آن‌ها را دارد.
پس از این cut/paste، پوشه‌ی خالی ClientApp را نیز حذف کنید.


تنظیم محل خروجی نهایی Angular CLI به پوشه‌ی wwwroot

برای اینکه سیستم Build پروژه‌ی Angular CLI جاری، خروجی خود را در پوشه‌ی wwwroot قرار دهد، تنها کافی است فایل .angular-cli.json را گشوده و outDir آن‌را به wwwroot تنظیم کنیم:
"apps": [
    {
      "root": "src",
      "outDir": "wwwroot",
به این ترتیب پس از هر بار build آن، فایل‌های index.html و تمام فایل‌های js نهایی، در پوشه‌ی wwwroot که در فایل Startup.cs‌، کار عمومی کردن آن انجام شد، تولید می‌شوند.


فراخوانی کنترلر Web API برنامه در برنامه‌ی Angular CLI

در ادامه صرفا جهت آزمایش برنامه، فایل src\app\app.component.ts را گشوده و به نحو ذیل تکمیل کنید:
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http'; 
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  constructor(private _httpService: Http) { }
 
  apiValues: string[] = [];
 
  ngOnInit() {
    this._httpService.get('/api/values').subscribe(values => {
      this.apiValues = values.json() as string[];
    });
  }
}
در اینجا خروجی JSON کنترلر Web API برنامه دریافت شده و به آرایه‌ی apiValues انتساب داده می‌شود.

سپس این آرایه را در فایل قالب این کامپوننت (src\app\app.component.html) استفاده خواهیم کرد:
<h1>Application says:</h1>
<ul *ngFor="let value of apiValues">
  <li>{{value}}</li>
</ul>
<router-outlet></router-outlet>
در اینجا یک حلقه ایجاد شده و عناصر آرایه‌ی apiValues به صورت یک لیست نمایش داده می‌شوند.


نصب وابستگی‌های برنامه‌ی Angular CLI

در ابتدای ایجاد پوشه‌ی ClientApp، از پرچم skip-install استفاده شد، تا صرفا ساختار پروژه، جهت cut/paste آن با سرعت هر چه تمام‌تر، ایجاد شود. اکنون برای نصب وابستگی‌های آن یا می‌توان در solution explorer به گره dependencies مراجعه کرده و npm را انتخاب کرد. در ادامه با کلیک راست بر روی آن، گزینه‌ی restore packages ظاهر می‌شود. و یا می‌توان به روش متداول این نوع پروژه‌ها، از طریق خط فرمان به پوشه‌ی ریشه‌ی پروژه وارد شد و دستور npm install را صادر کرد. بهتر است اینکار را از طریق خط فرمان انجام دهید تا مطمئن شوید که از آخرین نگارش‌های این ابزار که بر روی سیستم نصب شده‌است، استفاده می‌کنید.


روش اول اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

تا اینجا اگر برنامه را از طریق VS 2017 اجرا کنید، خروجی را مشاهده نخواهید کرد. چون هنوز فایل index.html آن تولید نشده‌است.
بنابراین روش اول اجرای این نوع برنامه‌ها، شامل مراحل ذیل است:
الف) ساخت پروژه‌ی Angular CLI در حالت watch
 > ng build --watch
برای اجرای آن از طریق خط فرمان، به پوشه‌ی ریشه‌ی پروژه وارد شده و دستور فوق را وارد کنید. به این ترتیب کار build پروژه انجام شده و همچنین فایل‌های نهایی آن در پوشه‌ی wwwroot قرار می‌گیرند. به علاوه چون از پرچم watch استفاده شده‌است، با هر تغییری در پوشه‌ی src برنامه، این فایل‌ها به صورت خودکار به روز رسانی می‌شوند. بنابراین این پنجره‌ی خط فرمان را باید باز نگه داشت تا watcher آن بتواند کارش را به صورت مداوم انجام دهد.

ب) اجرای برنامه از طریق ویژوال استودیو
اکنون که کار ایجاد محتوای پوشه‌ی wwwroot برنامه انجام شده‌است، می‌توان برنامه را از طریق VS 2017 به روش متداولی اجرا کرد:


یک نکته: می‌توان قسمت الف را تبدیل به یک Post Build Event هم کرد. برای این منظور باید فایل csproj را به نحو ذیل تکمیل کرد:
<Target Name="AngularBuild" AfterTargets="Build">
    <Exec Command="ng build" />
</Target>
به این ترتیب با هربار Build پروژه در VS 2017، کار تولید مجدد محتوای پوشه‌ی wwwroot نیز انجام خواهد شد.
تنها مشکل روش Post Build Event، کند بودن آن است. زمانیکه از روش ng build --watch به صورت مستقل استفاده می‌شود، برای بار اول اجرا، اندکی زمان خواهد برد؛ اما اعمال تغییرات بعدی به آن بسیار سریع هستند. چون صرفا نیاز دارد این تغییرات اندک و تدریجی را کامپایل کند و نه کامپایل کل پروژه را از ابتدا.


روش دوم اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

روش دومی که در اینجا بررسی خواهد شد، مستقل است از قسمت «ب» روش اول که توضیح داده شد. برنامه‌های NET Core. نیز به همراه CLI خاص خودشان هستند و نیازی نیست تا حتما از VS 2017 برای اجرای آن‌ها استفاده کرد. به همین جهت وابستگی Microsoft.DotNet.Watcher.Tools را نیز در ابتدای کار به وابستگی‌های برنامه اضافه کردیم.

الف) در ادامه، VS 2017 را به طور کامل ببندید؛ چون نیازی به آن نیست. سپس دستور ذیل را در خط فرمان، در ریشه‌ی پروژه‌، صادر کنید:
> dotnet watch run
این دستور پروژه‌ی ASP.NET Core را کامپایل کرده و بر روی پورت 5000 ارائه می‌دهد:
>dotnet watch run
[90mwatch : [39mStarted
Hosting environment: Production
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
به علاوه پارامتر watch آن سبب خواهد شد تا هر تغییری که در کدهای پروژه‌ی ASP.NET Core صورت گیرند، بلافاصله کامپایل شده و قابل استفاده شوند.

ب) در اینجا چون برنامه بر روی پورت 5000 ارائه شده‌است، بهتر است دستور ng serve -o را صادر کرد تا بتوان به نحو ساده‌تری از سرور وب ASP.NET Core استفاده نمود. در این حالت برنامه‌ی Angular CLI بر روی پورت 4200 ارائه شده و بلافاصله در مرورگر نیز نمایش داده می‌شود.
مشکل! سرور وب ما بر روی پورت 5000 است و سرور آزمایشی Angular CLI بر روی پورت 4200. اکنون برنامه‌ی Angular ما، یک چنین درخواست‌هایی را به سمت سرور، جهت دریافت اطلاعات ارسال می‌کند: localhost:4200/api
برای رفع این مشکل می‌توان فایلی را به نام proxy.config.json با محتویات ذیل ایجاد کرد:
{
  "/api": {
    "target": "http://localhost:5000",
    "secure": false
  }
}
سپس دستور ng server صادر شده، اندکی متفاوت خواهد شد:
 >ng serve --proxy-config proxy.config.json -o
در اینجا به ng serve اعلام کرده‌ایم که تمامی درخواست‌های ارسالی به مسیر api/  (و یا همان localhost:4200/api جاری) را به سرور وب ASP.NET Core، بر روی پورت 5000 ارسال کن و نتیجه را در همینجا بازگشت بده. به این ترتیب مشکل عدم دسترسی به سرور وب، جهت تامین اطلاعات برطرف خواهد شد.
مزیت این روش، به روز رسانی خودکار مرورگر با انجام هر تغییری در کدهای قسمت Angular برنامه است.

نکته 1: بدیهی است می‌توان قسمت «ب» روش دوم را با قسمت «الف» روش اول نیز جایگزین کرد (ساخت پروژه‌ی Angular CLI در حالت watch). اینبار گشودن مرورگر بر روی پورت 5000 (و یا آدرس http://localhost:5000) را باید به صورت دستی انجام دهید. همچنین هربار تغییر در کدهای Angular، سبب refresh خودکار مرورگر نیز نمی‌شود که آن‌را نیز باید خودتان به صورت دستی انجام دهید (کلیک بر روی دکمه‌ی refresh پس از هر بار پایان کار ng build).
نکته 2: می‌توان قسمت «الف» روش دوم را حذف کرد (حذف dotnet run در حالت watch). یعنی می‌خواهیم هنوز هم ویژوال استودیو کار آغاز IIS Express را انجام دهد. به علاوه می‌خواهیم برنامه را توسط ng serve مشاهده کنیم (با همان پارامترهای قسمت «ب» روش دوم). در این حالت تنها موردی را که باید تغییر دهید، پورتی است که برای IIS Express تنظیم شده‌است. عدد این پورت را می‌توان در فایل Properties\launchSettings.json مشاهده کرد و سپس به تنظیمات فایل proxy.config.json اعمال نمود.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: ASPNETCoreIntegrationWithAngularCLI.zip
به همراه این کدها تعدادی فایل bat نیز وجود دارند که جهت ساده سازی عملیات یاد شده‌ی در این مطلب، می‌توان از آن‌ها استفاده کرد:
 - فایل restore.bat کار بازیابی و نصب وابستگی‌های پروژه‌ی دات نتی و همچنین Angular CLI را انجام می‌دهد.
 - دو فایل ng-build-dev.bat و ng-build-prod.bat بیانگر قسمت «الف» روش اول هستند. فایل dev مخصوص حالت توسعه است و فایل prod مخصوص ارائه‌ی نهایی.
 - دو فایل dotnet_run.bat و ng-serve-proxy.bat خلاصه کننده‌ی قسمت‌های «الف» و «ب» روش دوم هستند.
اشتراک‌ها
دوره کار با Lucene

Introduction to Lucene
Lucene Components Overview
Lucene Query (Search) Syntax Examples
Advanced Lucene Query Examples
Building a Search Index with Lucene
Integrating Lucene Search into an Application
Lucene Analysis Process Guide

دوره کار با Lucene
مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت پنجم
در قسمت قبل پیاده سازی change-tracking در سمت کلاینت توسط Web API را بررسی کردیم. در این قسمت نگاهی به حذف موجودیت‌های منفصل یا disconnected خواهیم داشت.


حذف موجودیت‌های منفصل

فرض کنید موجودیتی را از یک سرویس WCF دریافت کرده اید و می‌خواهید آن را برای حذف علامت گذاری کنید. مدل زیر را در نظر بگیرید.

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

  • در ویژوال استودیو پروژه جدیدی از نوع WCF Service Library بسازید و نام آن را به Recipe5 تغییر دهید.
  • روی پروژه کلیک راست کنید و گزینه Add New Item را انتخاب کنید. سپس گزینه‌های Data -> ADO.NET Entity Data Model را برگزینید.
  • از ویزارد ویژوال استودیو برای اضافه کردن یک مدل با جداول Invoice و Payment استفاده کنید. برای ساده نگه داشتن مثال جاری، فیلد پیمایشی Payments را از موجودیت Invoice حذف کرده ایم (برای این کار روی خاصیت پیمایشی Payments کلیک راست کنید و گزینه Delete From Model را انتخاب کنید.) روی خاصیت TimeStamp موجودیت Payment کلیک راست کنید و گزینه Properties را انتخاب کنید. سپس مقدار Concurrency Mode آن را به Fixed تغییر دهید. این کار باعث می‌شود که مقدار این فیلد برای کنترل همزمانی بررسی شود. بنابراین مقدار TimeStamp در عبارت WHERE تمام دستورات بروز رسانی و حذف درج خواهد شد.
  • فایل IService1.cs را باز کنید و تعریف سرویس را مانند لیست زیر تغییر دهید.
[ServiceContract]
public interface IService1
{
    [OperationContract]
    Payment InsertPayment();
    [OperationContract]
    void DeletePayment(Payment payment);
}
  • فایل Service1.cs را باز کنید و پیاده سازی سرویس را مانند لیست زیر تغییر دهید.
public class Service1 : IService1
{
    public Payment InsertPayment()
    {
        using (var context = new EFRecipesEntities())
        {
            // delete the previous test data
            context.Database.ExecuteSqlCommand("delete from [payments]");
            context.Database.ExecuteSqlCommand("delete from [invoices]");
            var payment = new Payment { Amount = 99.95M, Invoice =
                new Invoice { Description = "Auto Repair" } };
            context.Payments.Add(payment);
            context.SaveChanges();
            return payment;
        }
    }

    public void DeletePayment(Payment payment)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Entry(payment).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }
}
  • برای تست این سرویس به یک کلاینت نیاز داریم. یک پروژه جدید از نوع Console Application به راه حل جاری اضافه کنید و کد آن را مطابق لیست زیر تغییر دهید. فراموش نکنید که ارجاعی به سرویس هم اضافه کنید. روی پروژه کلاینت کلیک راست کرده و Add Service Reference را انتخاب نمایید. ممکن است پیش از آنکه بتوانید سرویس را ارجاع کنید، نیاز باشد پروژه سرویس را ابتدا اجرا کنید (کلیک راست روی پروژه سرویس و انتخاب گزینه Debug -> Start Instance).
class Program
{
    static void Main()
    {
        var client = new Service1Client();
        var payment = client.InsertPayment();
        client.DeletePayment(payment);
    }
}
اگر روی خط اول متد ()Main یک breakpoint قرار دهید می‌توانید مراحل ایجاد و حذف یک موجودیت Payment را دنبال کنید.


شرح مثال جاری

در مثال جاری برای بروز رسانی و حذف موجودیت‌های منفصل از الگویی رایج استفاده کرده ایم که در سرویس‌های WCF و Web API استفاده می‌شود.

در کلاینت با فراخوانی متد InsertPayment یک پرداخت جدید در دیتابیس ذخیره می‌کنیم. این متد، موجودیت Payment ایجاد شده را باز می‌گرداند. موجودیتی که به کلاینت باز می‌گردد از DbContext منفصل (disconnected) است، در واقع در چنین وضعیتی آبجکت context ممکن است در فضای پروسس دیگری قرار داشته باشد، یا حتی روی کامپیوتر دیگری باشد.

برای حذف موجودیت Payment از متد DeletePayment استفاده می‌کنیم. این متد به نوبه خود با فراخوانی متد Entry روی آبجکت context و پاس دادن موجودیت پرداخت بعنوان آرگومان، موجودیت را پیدا می‌کند. سپس وضعیت موجودیت را به EntityState.Deleted تغییر می‌دهیم که این کار آبجکت را برای حذف علامت گذاری می‌کند. فراخوانی‌های بعدی متد ()SaveChanges موجودیت را از دیتابیس حذف خواهد کرد.

آبجکت پرداختی که برای حذف به context الحاق کرده ایم تمام خاصیت هایش مقدار دهی شده اند، درست مانند هنگامی که این موجودیت به دیتابیس اضافه شده بود. اما از آنجا که از foreign key association استفاده می‌کنیم، تنها فیلدهای کلید موجودیت، خاصیت همزمانی (concurrency) و TimeStamp برای تولید عبارت where مناسب لازم هستند که نهایتا منجر به حذف موجودیت خواهد شد. تنها استثنا درباره این قاعده هنگامی است که موجودیت شما یک یا چند خاصیت از نوع پیچیده یا Complex Type داشته باشد. از آنجا که خاصیت‌های پیچیده، اجزای ساختاری یک موجودیت محسوب می‌شوند نمی‌توانند مقادیر null بپذیرند. یک راه حل ساده این است که هنگامی که EF مشغول ساختن عبارت SQL Delete لازم برای حذف موجودیت بر اساس کلید و خاصیت همزمانی آن است، وهله جدیدی از نوع داده پیچیده خود بسازید. اگر فیلدهای complex type را با مقادیر null رها کنید، فراخوانی متد ()SaveChanges با خطا مواجه خواهد شد.

اگر از یک independent association استفاده می‌کنید که در آن کثرت (multiplicity) موجودیت مربوطه یک، یا صفر به یک است، EF انتظار دارد که کلید‌های موجودیت‌ها بدرستی مقدار دهی شوند تا بتواند عبارت where مناسب را برای دستورات بروز رسانی و حذف تولید کند. اگر در مثال جاری از یک رابطه independent association بین موجودیت‌های Invoice و Payment استفاده می‌کردیم، لازم بود تا خاصیت پیمایشی Invoice را با وهله ای از صورت حساب مقدار دهی کنیم که خاصیت InvoiceId آن نیز بدرستی مقدار دهی شده باشد. در این صورت عبارت where نهایی شامل فیلدهای PaymentId, TimeStamp و InvoiceId خواهد بود.

نکته: هنگام پیاده سازی معماری‌های n-Tier با Entity Framework، استفاده از رویکرد Foreign Key Association برای موجودیت‌های مرتبط باید با ملاحظات جدی انجام شود. پیاده سازی رویکرد Independent Association مشکل است و می‌تواند کد شما را بسیار پیچیده کند. برای مطالعه بیشتر درباره این رویکردها و مزایا و معایب آنها به این لینک مراجعه کنید که توسط یکی از برنامه نویسان تیم EF نوشته شده است.

اگر موجودیت شما تعداد متعددی Independent Association دارد، مقدار دهی تمام آنها می‌تواند خسته کننده شود. رویکردی ساده‌تر این است که وهله مورد نظر را از دیتابیس دریافت کنید و آن را برای حذف علامت گذاری نمایید. این روش کد شما را ساده‌تر می‌کند، اما هنگامی که آبجکت را از دیتابیس دریافت می‌کنید EF کوئری جاری را بازنویسی می‌کند تا تمام روابط یک، یا صفر به یک بارگذاری شوند. مگر آنکه از گزینه NoTracking روی context خود استفاده کنید. اگر در مثال جاری رویکرد Independent Association را پیاده سازی کرده بودیم، هنگامی که موجودیت Payment را از دیتابیس دریافت می‌کنیم (قبل از علامت گذاری برای حذف) EF یک Object state entry برای موجودیت پرداخت و یک Relationship entry برای رابطه بین Payment و Invoice می‌ساخت. سپس وقتی که موجودیت پرداخت را برای حذف علامت گذاری می‌کنیم، EF رابطه بین پرداخت و صورت حساب را هم برای حذف علامت گذاری می‌کند. در اینجا عبارت where تولید شده مانند قبل، شامل فیلدهای PaymentId, TimeStamp و InvoiceId خواهد بود.

یک گزینه دیگر برای حذف موجودیت‌ها در Independent Associations این است که تمام موجودیت‌های مرتبط را مشخصا بارگذاری کنیم (eager loading) و کل Object graph را برای حذف به سرویس WCF یا Web API بفرستیم. در مثال جاری می‌توانستیم موجودیت صورتحساب مرتبط با موجودیت پرداخت را مشخصا بارگذاری کنیم. اگر می‌خواستیم موجودیت Payment را حذف کنیم، می‌توانستیم کل گراف را که شامل هر دو موجودیت می‌شود به سرویس ارسال کنیم. اما هنگام استفاده از چنین روشی باید بسیار دقت کنید، چرا که این رویکرد پهنای باند بیشتری مصرف می‌کند و زمان پردازش بیشتری هم برای مرتب سازی (serialization) صرف می‌کند. بنابراین هزینه این رویکرد نسبت به سادگی کدی که بدست می‌آید به مراتب بیشتر است.

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

ارزیابی در الگوریتم‌های دسته بندی 
برای سادگی معیارهای ارزیابی الگوریتم‌های دسته بندی، آنها را برای یک مسئله با دو دسته ارائه خواهیم نمود. در ابتدا با مفهوم ماتریس درهم ریختگی (Classification Matrix) آشنا می‌شویم. این ماتریس چگونگی عملکرد الگوریتم دسته بندی را با توجه به مجموعه داده ورودی به تفکیک انواع دسته‌های مساله دسته بندی، نمایش می‌دهد.

هر یک از عناصر ماتریس به شرح ذیل می‌باشد:
TN: بیانگر تعداد رکوردهایی است که دسته واقعی آنها منفی بوده و الگوریتم دسته بندی نیز دسته آنها را بدرستی منفی تشخیص داده است.
TP: بیانگر تعداد رکوردهایی است که دسته واقعی آنها مثبت بوده و الگوریتم دسته بندی نیز دسته آنها را بدرستی مثبت تشخیص داده است.
FP: بیانگر تعداد رکوردهایی است که دسته واقعی آنها منفی بوده و الگوریتم دسته بندی دسته آنها را به اشتباه مثبت تشخیص داده است.
FN: بیانگر تعداد رکوردهایی است که دسته واقعی آنها مثبت بوده و الگوریتم دسته بندی دسته آنها را به اشتباه منفی تشخیص داده است.

مهمترین معیار برای تعین کارایی یک الگوریتم دسته بندی دقت یا نرخ دسته بندی (Classification Accuracy - Rate) است که این معیار دقت کل یک دسته بند را محاسبه می‌کند. در واقع این معیار مشهورترین و عمومی‌ترین معیار محاسبه کارایی الگوریتم‌های دسته بندی است که نشان می‌دهد، دسته بند طراحی شده چند درصد از کل مجموعه رکوردهای آزمایشی را بدرستی دسته بندی کرده است.
دقت دسته بندی با استفاده از رابطه I بدست می‌آید که بیان می‌کند دو مقدار TP و TN مهمترین مقادیری هستند که در یک مسئله دودسته ای باید بیشینه شوند. (در مسائل چند دسته ای مقادیر قرار گرفته روی قطر اصلی این ماتریس - که در صورت کسر محاسبه CA قرار می‌گیرند - باید بیشینه باشند.)
معیار خطای دسته بندی (Error Rate) دقیقاً برعکس معیار دقت دسته بندی است که با استفاده از رابطه II بدست می‌آید. کمترین مقدار آن برابر صفر است زمانی که بهترین کارایی را داریم و بطور مشابه بیشترین مقدار آن برابر یک است زمانی که کمترین کارائی را داریم.
ذکر این نکته ضروری است که در مسائل واقعی، معیار دقت دسته بندی به هیچ عنوان معیار مناسبی برای ارزیابی کارایی الگوریتم‌های دسته بندی نمی‌باشد، به این دلیل که در رابطه دقت دسته بندی، ارزش رکوردهای دسته‌های مختلف یکسان در نظر گرفته می‌شوند. بنابراین در مسائلی که با دسته‌های نامتعادل سروکار داریم، به بیان دیگر در مسائلی که ارزش دسته ای در مقایسه با دسته دیگر متفاوت است، از معیارهای دیگری استفاده می‌شود.
همچنین در مسائل واقعی معیارهای دیگری نظیر DR و FAR که به ترتیب از روابط III و IV بدست می‌آیند، اهمیت ویژه ای دارند. این معیارها که توجه بیشتری به دسته بند مثبت نشان می‌دهند، توانایی دسته بند را در تشخیص دسته مثبت و بطور مشابه تاوان این توانایی تشخیص را تبیین می‌کنند. معیار DR نشان می‌دهد که دقت تشخیص دسته مثبت چه مقدار است و معیار FAR نرخ هشدار غلط را با توجه به دسته منفی بیان می‌کند.
 

معیار مهم دیگری که برای تعیین میزان کارایی یک دسته بند استفاده می‌شود معیار (AUC (Area Under Curve است.

AUC نشان دهنده سطح زیر نمودار (ROC (Receiver Operating Characteristic می‌باشد که هر چه مقدار این عدد مربوط به یک دسته بند بزرگتر باشد کارایی نهایی دسته بند مطلوب‌تر ارزیابی می‌شود. نمودار ROC روشی برای بررسی کارایی دسته بندها می‌باشد. در واقع منحنی‌های ROC منحنی‌های دو بعدی هستند که در آنها DR یا همان نرخ تشخیص صحیح دسته مثبت (True Positive Rate - TPR) روی محور Y و بطور مشابه FAR یا همان نرخ تشخیص غلط دسته منفی (False Positive Rate - FPR) روی محور X رسم می‌شوند. به بیان دیگر یک منحنی ROC مصالحه نسبی میان سودها و هزینه‌ها را نشان می‌دهد.

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

یک منحنی ROC اجازه مقایسه تصویری مجموعه ای از دسته بندی کننده‌ها را می‌دهد، همچنین نقاط متعددی در فضای ROC قابل توجه است. نقطه پایین سمت چپ (0,0) استراتژی را نشان می‌دهد که در یک دسته بند مثبت تولید نمی‌شود. استراتژی مخالف، که بدون شرط دسته بندهای مثبت تولید می‌کند، با نقطه بالا سمت راست (1,1) مشخص می‌شود. نقطه (0,1) دسته بندی کامل و بی عیب را نمایش می‌دهد. بطور کلی یک نقطه در فضای ROC بهتر از دیگری است اگر در شمال غربی‌تر این فضا قرار گرفته باشد. همچنین در نظر داشته باشید منحنی‌های ROC رفتار یک دسته بندی کننده را بدون توجه به توزیع دسته‌ها یا هزینه خطا نشان می‌دهند، بنابراین کارایی دسته بندی را از این عوامل جدا می‌کنند. فقط زمانی که یک دسته بند در کل فضای کارایی به وضوح بر دسته دیگری تسلط یابد، می‌توان گفت که بهتر از دیگری است. به همین دلیل معیار AUC که سطح زیر نمودار ROC را نشان می‌دهد می‌تواند نقش تعیین کننده ای در معرفی دسته بند برتر ایفا کند. برای درک بهتر نمودار ROC زیر را مشاهده کنید.
 

مقدار AUC برای یک دسته بند که بطور تصادفی، دسته نمونه مورد بررسی را تعیین می‌کند برابر 0.5 است. همچنین بیشترین مقدار این معیار برابر یک بوده و برای وضعیتی رخ می‌دهد که دسته بند ایده آل بوده و بتواند کلیه نمونه‌های مثبت را بدون هرگونه هشدار غلطی تشخیص دهد. معیار AUC برخلاف دیگر معیارهای تعیین کارایی دسته بندها مستقل از آستانه تصمیم گیری دسته بند می‌باشد. بنابراین این معیار نشان دهنده میزان قابل اعتماد بودن خروجی یک دسته بند مشخص به ازای مجموعه داده‌های متفاوت است که این مفهوم توسط سایر معیارهای ارزیابی کارایی دسته بندها قابل محاسبه نمی‌باشد. در برخی از مواقع سطح زیر منحنی‌های ROC مربوط به دو دسته بند با یکدیگر برابر است ولی ارزش آنها برای کاربردهای مختلف یکسان نیست که باید در نظر داشت در این گونه مسائل که ارزش دسته‌ها با یکدیگر برابر نیست، استفاده از معیار AUC مطلوب نمی‌باشد. به همین دلیل در این گونه مسائل استفاده از معیار دیگری به جزء هزینه (Cost Matrix) منطقی به نظر نمی‌رسد. در انتها باید توجه نمود در کنار معیارهای بررسی شده که همگی به نوعی دقت دسته بند را محاسبه می‌کردند، در دسته بندهای قابل تفسیر نظیر دسته بندهای مبتنی بر قانون و یا درخت تصمیم، پیچیدگی نهایی و قابل تفسیر بودن مدل یاد گرفته شده نیز از اهمیت بالایی برخوردار است. 

از روش‌های ارزیابی الگوریتم‌های دسته بندی (که در این الگوریتم روال کاری بدین صورت است که مدل دسته بندی توسط مجموعه داده آموزشی ساخته شده و بوسیله مجموعه داده آزمایشی مورد ارزیابی قرار می‌گیرد.) می‌توان به روش Holdout اشاره کرد که در این روش چگونگی نسبت تقسیم مجموعه داده‌ها (به دو مجموعه داده آموزشی و مجموعه داده آزمایشی) بستگی به تشخیص تحلیگر دارد که معمولاً دو سوم برای آموزش و یک سوم برای ارزیابی در نظر گرفته می‌شود. مهمترین مزیت این روش سادگی و سرعت بالای عملیات ارزیابی است ولیکن روش Holdout معایب زیادی دارد از جمله اینکه مجموعه داده‌های آموزشی و آزمایشی به یکدیگر وابسته خواهند شد، در واقع بخشی از مجموعه داده اولیه که برای آزمایش جدا می‌شود، شانسی برای حضور یافتن در مرحله آموزش ندارد و بطور مشابه در صورت انتخاب یک رکورد برای آموزش دیگر شانسی برای استفاده از این رکورد برای ارزیابی مدل ساخته شده وجود نخواهد داشت. همچنین مدل ساخته شده بستگی فراوانی به چگونگی تقسیم مجموعه داده اولیه به مجموعه داده‌های آموزشی و آزمایشی دارد. چنانچه روش Holdout را چندین بار اجرا کنیم و از نتایج حاصل میانگین گیری کنیم از روشی موسوم به Random Sub-sampling استفاده نموده ایم. که مهمترین عیب این روش نیز عدم کنترل بر روی تعداد دفعاتی که یک رکورد به عنوان نمونه آموزشی و یا نمونه آزمایشی مورد استفاده قرار می‌گیرد، است. به بیان دیگر در این روش ممکن است برخی رکوردها بیش از سایرین برای یادگیری و یا ارزیابی مورد استفاده قرار گیرند.
چنانچه در روش Random Sub-sampling به شکل هوشمندانه‌تری عمل کنیم به صورتی که هر کدام از رکوردها به تعداد مساوی برای یادگیری و تنها یکبار برای ارزیابی استفاده شوند، روش مزبور در متون علمی با نام Cross Validation شناخته می‌شود.
همچنین در روش جامع k-Fold Cross Validation کل مجموعه داده‌ها به k قسمت مساوی تقسیم می‌شوند. از k-1 قسمت به عنوان مجموعه داده‌های آموزشی استفاده می‌شود و براساس آن مدل ساخته می‌شود و با یک قسمت باقی مانده عملیات ارزیابی انجام می‌شود. فرآیند مزبور به تعداد k مرتبه تکرار خواهد شد، به گونه ای که از هر کدام از k قسمت تنها یکبار برای ارزیابی استفاده شده و در هر مرتبه یک دقت برای مدل ساخته شده، محاسبه می‌شود. در این روش ارزیابی دقت نهایی دسته بند برابر با میانگین k دقت محاسبه شده خواهد بود. معمول‌ترین مقداری که در متون علمی برای k در نظر گرفته می‌شود برابر با 10 می‌باشد. بدیهی است هر چه مقدار k بزرگتر شود، دقت محاسبه شده برای دسته بند قابل اعتماد‌تر بوده و دانش حاصل شده جامع‌تر خواهد بود و البته افزایش زمان ارزیابی دسته بند نیز مهمترین مشکل آن می‌باشد. حداکثر مقدار k برابر با تعداد رکوردهای مجموعه داده اولیه است که این روش ارزیابی با نام Leaving One Out شناخته می‌شود.
در روش هایی که تاکنون به آن اشاره شده، فرض بر آن است که عملیات انتخاب نمونه‌های آموزشی بدون جایگذاری صورت می‌گیرد. به بیان دیگر یک رکورد تنها یکبار در یک فرآیند آموزشی مورد توجه واقع می‌شود. چنانچه هر رکورد در صورت انتخاب شدن برای شرکت در عملیات یادگیری مدل بتواند مجدداً برای یادگیری مورد استفاده قرار گیرد روش مزبور با نام Bootstrap و یا   0.632 Bootstrap  شناخته می‌شود. (از آنجا که هر Bootstrap معادل 0.632 مجموعه داده اولیه است)
 

 
 ارزیابی در الگوریتم‌های خوشه بندی
به منظور ارزیابی الگوریتم‌های خوشه بندی می‌توان آنها به دو دسته تقسیم نمود:
شاخص‌های ارزیابی بدون ناظر، که گاهی در متون علمی با نام معیارهای داخلی شناخته می‌شوند، به آن دسته از معیارهایی گفته می‌شود که تعیین کیفیت عملیات خوشه بندی را با توجه به اطلاعات موجود در مجموعه داده بر عهده دارند. در مقابل، معیارهای ارزیابی با ناظر با نام معیار‌های خارجی نیز شناخته می‌شوند، که با استفاده از اطلاعاتی خارج از حیطه مجموعه داده‌های مورد بررسی، عملکرد الگوریتم‌های خوشه بندی را مورد ارزیابی قرار می‌دهند.
از آنجا که مهمترین وظیفه یک الگوریتم خوشه بندی آن است که بتواند به بهترین شکل ممکن فاصله درون خوشه ای را کمینه و فاصله بین خوشه ای را بیشینه نماید، کلیه معیارهای ارزیابی بدون ناظر سعی در سنجش کیفیت عملیات خوشه بندی با توجه به دو فاکتور تراکم خوشه ای و جدائی خوشه ای دارند. برآورده شدن هدف کمینه سازی درون خوشه ای و بیشینه سازی میان خوشه ای به ترتیب در گرو بیشینه نمودن تراکم هر خوشه و نیز بیشینه سازی جدایی میان خوشه‌ها می‌باشد. طیف وسیعی از معیارهای ارزیابی بدون ناظر وجود دارد که همگی در ابتدا تعریفی برای فاکتورهای تراکم و جدائی ارائه می‌دهند سپس توسط تابع (F(Cohesion, Separation مرتبط با خود، به ترکیب این دو فاکتور می‌پردازند. ذکر این نکته ضروری است که نمی‌توان هیچ کدام از معیارهای ارزیابی خوشه بندی را برای تمامی کاربردها مناسب دانست.

ارزیابی با ناظر الگوریتم‌های خوشه بندی، با هدف آزمایش و مقایسه عملکرد روش‌های خوشه بندی با توجه به حقایق مربوط به رکوردها صورت می‌پذیرد. به بیان دیگر هنگامی که اطلاعاتی از برچسب رکوردهای مجموعه داده مورد بررسی در اختیار داشته باشیم، می‌توانیم از آنها در عملیات ارزیابی عملکرد الگوریتم‌های خوشه بندی بهره بریم. لازم است در نظر داشته باشید در این بخش از برچسب رکوردها تنها در مرحله ارزیابی استفاده می‌شود و هر گونه بهره برداری از این برچسب‌ها در مرحله یادگیری مدل، منجر به تبدیل شدن روش کاوش داده از خوشه بندی به دسته بندی خواهد شد. مشابه با روش‌های بدون ناظر طیف وسیعی از معیارهای ارزیابی با ناظر نیز وجود دارد که در این قسمت با استفاده از روابط زیر به محاسبه معیارهای Rand Index و Jaccard می پردازیم به ترتیب در رابطه I و II نحوه محاسبه آنها نمایش داده شده است:

Rand Index را می‌توان به عنوان تعداد تصمیمات درست در خوشه بندی در نظر گرفت.
TP: به تعداد زوج داده هایی گفته می‌شود که باید در یک خوشه قرار می‌گرفتند، و قرار گرفته اند.
TN: به تعداد زوج داده هایی گفته می‌شود که باید در خوشه‌های جداگانه قرار داده می‌شدند و به درستی در خوشه‌های جداگانه جای داده شده اند.
FN: به تعداد زوج داده هایی گفته می‌شود که باید در یک خوشه قرار می‌گرفتند ولی در خوشه‌های جداگانه قرار داده شده اند.
FP: به تعداد زوج داده هایی اشاره دارد که باید در خوشه‌های متفاوت قرار می‌گرفتند ولی در یک خوشه قرار گرفته اند. 


 ارزیابی در الگوریتم‌های کشف قوانین انجمنی
به منظور ارزیابی الگوریتم‌های کشف قوانین انجمنی از آنجایی که این الگوریتم‌ها پتانسیل این را دارند که الگوها و قوانین زیادی تولید نمایند، جهت ارزیابی این قوانین به عواملی همچون شخص استفاده کننده از قوانین و نیز حوزه ای که مجموعه داده مورد بررسی به آن تعلق دارد، وابستگی زیادی پیدا می‌کنیم و بدین ترتیب کار پیدا کردن قوانین جذاب، به آسانی میسر نیست. فرض کنید قانونی با نام R داریم که به شکل A=>B می‌باشد، که در آن A و B زیر مجموعه ای از اشیاء می‌باشند.
پیشتر به معرفی دو معیار Support و Confidence پرداختیم. می‌دانیم از نسبت تعداد تراکنش هایی که در آن اشیاء A و B هر دو حضور دارند، به کل تعداد رکوردها Support بدست می‌آید که دارای مقداری عددی بین صفر و یک می‌باشد و هر چه این میزان بیشتر باشد، نشان می‌دهد که این دو شیء بیشتر با هم در ارتباط هستند. کاربر می‌تواند با مشخص کردن یک آستانه برای این معیار، تنها قوانینی را بدست آورد که Support آنها بیشتر از مقدار آستانه باشد، بدین ترتیب می‌توان با کاهش فضای جستجو، زمان لازم جهت پیدا کردن قوانین انجمنی را کمینه کرد. البته باید به ضعف این روش نیز توجه داشت که ممکن است قوانین با ارزشی را بدین ترتیب از دست دهیم. در واقع استفاده از این معیار به تنهایی کافی نیست. معیار Confidence نیز مقداری عددی بین صفر و یک می‌باشد، که هر چه این عدد بزرگتر باشد بر کیفیت قانون افزوده خواهد شد. استفاده از این معیار به همراه Support مکمل مناسبی برای ارزیابی قوانین انجمنی خواهد بود. ولی مشکلی که همچنان وجود دارد این است که امکان دارد قانونی با Confidence بالا وجود داشته باشد ولی از نظر ما ارزشمند نباشد.
از معیارهای دیگر قوانین انجمنی می‌توان به معیار Lift که با نام‌های Intersect Factor یا Interestingness نیز شناخته می‌شود اشاره کرد، که این معیار میزان استقلال میان اشیاء A و B را نشان می‌دهد که می‌تواند مقدار عددی بین صفر تا بی نهایت باشد. در واقع Lift میزان هم اتفاقی بین ویژگی‌ها را در نظر می‌گیرد و میزان رخداد تکی بخش تالی قانون (یعنی شیء B) را در محاسبات خود وارد می‌کند. (بر خلاف معیار Confidence)
مقادیر نزدیک به عدد یک معرف این هستند که A و B مستقل از یکدیگر می‌باشند، بدین ترتیب نشان دهنده قانون جذابی نمی‌باشند. چنانچه این معیار از عدد یک کمتر باشد، نشان دهنده این است که A و B با یکدیگر رابطه منفی دارند. هر چه مقدار این معیار بیشتر از عدد یک باشد، نشان دهنده این است که A اطلاعات بیشتری درباره B فراهم می‌کند که در این حالت جذابیت قانون A=>B بالاتر ارزیابی می‌شود. در ضمن این معیار نسبت به سمت چپ و راست قانون متقارن است در واقع اگر سمت چپ و راست قانون را با یکدیگر جابجا کنیم، مقدار این معیار تغییری نمی‌کند. از آنجائی که این معیار نمی‌تواند به تنهایی برای ارزیابی مورد استفاده قرار گیرد، و حتماً باید در کنار معیارهای دیگر باشد، باید مقادیر آن بین بازه صفر و یک نرمال شود. ترکیب این معیار به همراه Support و Confidence جزو بهترین روش‌های کاوش قوانین انجمنی است. مشکل این معیار حساس بودن به تعداد نمونه‌های مجموعه داده، به ویژه برای مجموعه تراکنش‌های کوچک می‌باشد. از این رو معیارهای دیگری برای جبران این نقص معرفی شده اند.
معیار Conviction برخی ضعف‌های معیارهای Confidence و Lift را جبران می‌نماید. محدوده قابل تعریف برای این معیار در حوزه 0.5 تا بی نهایت قرار می‌گیرد که هر چه این مقدار بیشتر باشد، نشان دهنده این است که آن قانون جذاب‌تر می‌باشد. بر خلاف Lift این معیار متقارن نمی‌باشد و مقدار این معیار برای دلالت‌های منطقی یعنی در جایی که Confidence قانون یک می‌باشد برابر با بی نهایت است و چنانچه A و B مستقل از هم باشند، مقدار این معیار برابر با عدد یک خواهد بود.


معیار Leverage که در برخی متون با نام Novelty (جدید بودن) نیز شناخته می‌شود، دارای مقداری بین 0.25- و 0.25+ می‌باشد. ایده مستتر در این معیار آن است که اختلاف بین میزان هم اتفاقی سمت چپ و راست قانون با آن مقداری که مورد انتظار است به چه اندازه می‌باشد.
معیار Jaccard که دارای مقداری عددی بین صفر و یک است، علاوه بر اینکه نشان دهنده وجود نداشتن استقلال آماری میان A و B می‌باشد، درجه همپوشانی میان نمونه‌های پوشش داده شده توسط هر کدام از آنها را نیز اندازه گیری می‌کند. به بیان دیگر این معیار فاصله بین سمت چپ و راست قانون را بوسیله تقسیم تعداد نمونه هایی که توسط هر دو قسمت پوشش داده شده اند بر نمونه هایی که توسط یکی از آنها پوشش داده شده است، محاسبه می‌کند. مقادیر بالای این معیار نشان دهنده این است که A و B تمایل دارند، نمونه‌های مشابهی را پوشش دهند. لازم است به این نکته اشاره شود از این معیار برای فهمیدن میزان همبستگی میان متغیرها استفاده می‌شود که از آن می‌توان برای یافتن قوانینی که دارای همبستگی بالا ولی Support کم هستند، استفاده نمود. برای نمونه در مجموعه داده سبد خرید، قوانین نادری که Support کمی دارند ولی همبستگی بالایی دارند، توسط این معیار می‌توانند کشف شوند.   

معیار (Coefficient (φ نیز به منظور اندازه گیری رابطه میان A و B مورد استفاده قرار می‌گیرد که محدوده این معیار بین 1- و 1+ می‌باشد.
از دیگر معیارهای ارزیابی کیفیت قوانین انجمنی، طول قوانین بدست آمده می‌باشد. به بیان دیگر با ثابت در نظر گرفتن معیارهای دیگر نظیر Support، Confidence و Lift قانونی برتر است که طول آن کوتاه‌تر باشد، بدلیل فهم آسانتر آن.
 

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

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

ساختار درختی
در بسیاری از مواقع ما با گروهی از اشیاء یا داده‌هایی سر و کار داریم که هر کدام از آن‌ها به گروهی دیگر مرتبط هستند. در این حالت از ساختار خطی نمی‌توانیم برای توصیف این ارتباط استفاده کنیم. پس بهترین ساختار برای نشان دادن این ارتباط ساختار شاخه ای Branched Structure است.
یک ساختار درختی یا یک ساختار شاخه‌ای شامل المان‌هایی به اسم گره Node است. هر گره می‌تواند به یک یا چند گره دیگر متصل باشد و گاهی اوقات این اتصالات مشابه یک سلسه مراتب hierarchically می‌شوند.
درخت‌ها در برنامه نویسی جایگاه ویژه‌ای دارند به طوری که استفاده‌ی از آن‌ها در بسیاری از برنامه‌ها وجود دارد و بسیاری از مثال‌های واقعی پیرامون ما را پشتیبانی می‌کنند.
در نمودار زیر مثالی وجود دارد که در آن یک تیم نرم افزاری نمایش داده شده‌است. در اینجا هر یک از بخش‌ها وظایف و مسئولیت‌هایی را بر دوش خود دارند که این مسئولیت‌ها به صورت سلسله مراتبی در تصویر زیر نمایش داده شده‌اند.

ما در ساختار بالا متوجه می‌شویم که چه بخشی زیر مجموعه‌ی چه بخشی است و سمت بالاتر هر بخش چیست. برای مثال ما متوجه شدیم که مدیر توسعه دهندگان، "سرپرست تیم" است که خود نیز مادون "مدیر پروژه" است و این را نیز متوجه می‌شویم که مثلا توسعه دهنده‌ی شماره یک هیچ مادونی ندارد و مدیر پروژه در راس همه است و هیچ مدیر دیگری بالای سر او قرار ندارد.

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

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

در شکل بالا به هر یک از دایره‌ها یک گره Node می‌گویند و به هر خط ارتباط دهنده بین گره‌ها لبه Edge گفته می‌شود. گره‌های 19 و 21 و 14 زیر گره‌های گره 7 محسوب می‌شوند. گره‌هایی که به صورت مستقیم به زیر گره‌های خودشان اشاره می‌کنند را گره‌های والد Parent می‌گویند و زیرگره‌های 7 را گره‌های فرزند ChildNodes. پس با این حساب می‌توانیم بگوییم گره‌های 1 و 12 و 31 را هم فرزند گره 19 هستند و گره 19 والد آن هاست. همچنین گره‌های یک والد را مثل 19 و 21 و 14 که والد مشترک دارند، گره‌های خواهر و برادر یا حتی همنژاد Sibling می‌گوییم. همچنین ارتباط بین گره 7 و گره‌های سطح دوم  و الی آخر یعنی 1 و 12 و 31 و 23 و 6 را که والد بودن آن به صورت غیر مستقیم است را جد یا ancestor می‌نامیم و نوه‌ها و نتیجه‌های آن‌ها را نسل descendants.

ریشه Root: به گره‌ای می‌گوییم که هیچ والدی ندارد و خودش در واقع اولین والد محسوب می‌شود؛ مثل گره 7.

برگ  Leaf: به گره‌هایی که هیچ فرزندی ندارند، برگ می‌گوییم. مثال گره‌های 1 و12 و 31 و 23 و 6

گره‌های داخلی Internal Nodes: گره هایی که نه برگ هستند و نه ریشه. یعنی حداقل یک فرزند دارند و خودشان یک گره فرزند محسوب می‌شوند؛ مثل گره‌های 19 و 14.

مسیر Path: راه رسیدن از یک گره به گره دیگر را مسیر می‌گویند. مثلا گره‌های 1 و 19 و 7 و 21 به ترتیب یک مسیر را تشکیل می‌دهند ولی گره‌های 1 و 19 و 23 از آن جا که هیچ جور اتصالی بین آن‌ها نیست، مسیری را تشکیل نمی‌دهند.

طول مسیر Length of Path: به تعداد لبه‌های یک مسیر، طول مسیر می‌گویند که می‌توان از تعداد گره‌ها -1 نیز آن را به دست آورد. برای نمونه : مسیر 1 و19 و 7 و 21 طول مسیرشان 3 هست.

عمق Depth: طول مسیر یک گره از ریشه تا آن گره را عمق درخت می‌گویند. عمق یک ریشه همیشه صفر است و برای مثال در درخت بالا، گره 19 در عمق یک است و برای گره 23 عمق آن 2 خواهد بود.

تعریف خود درخت Tree: درخت یک ساختار داده برگشتی recursive است که شامل گره‌ها و لبه‌ها، برای اتصال گره‌ها به یکدیگر است.

جملات زیر در مورد درخت صدق می‌کند:

  • هر گره می‌تواند فرزند نداشته باشد یا به هر تعداد که می‌خواهد فرزند داشته باشد.
  • هر گره یک والد دارد و تنها گره‌ای که والد ندارد، گره ریشه است (البته اگر درخت خالی باشد هیچ گره ای وجود ندارد).
  • همه گره‌ها از ریشه قابل دسترسی هستند و برای دسترسی به گره مورد نظر باید از ریشه تا آن گره، مسیری را طی کرد.
ار تفاع درخت Height: به حداکثر عمق یک درخت، ارتفاع درخت می‌گویند.
درجه گره Degree: به تعداد گره‌های فرزند یک گره، درجه آن گره می‌گویند. در درخت بالا درجه گره‌های 7 و 19 سه است. درجه گره 14 دو است و درجه برگ‌ها صفر است.
ضریب انشعاب Branching Factor: به حداکثر درجه یک گره در یک درخت، ضریب انشعاب آن درخت گویند.

پیاده سازی درخت

برای پیاده سازی یک درخت، از دو کلاس یکی جهت ساخت گره که حاوی اطلاعات است <TreeNode<T و دیگری جهت ایجاد درخت اصلی به همراه کلیه متدها و خاصیت هایش <Tree<T کمک می‌‌گیریم.

public class TreeNode<T>
{
    // شامل مقدار گره است
    private T value;
 
    // مشخص می‌کند که آیا گره والد دارد یا خیر
    private bool hasParent;
 
    // در صورت داشتن فرزند ، لیست فرزندان را شامل می‌شود
    private List<TreeNode<T>> children;
 
    /// <summary>سازنده کلاس </summary>
    /// <param name="value">مقدار گره</param>
    public TreeNode(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
        this.value = value;
        this.children = new List<TreeNode<T>>();
    }
 
    /// <summary>خاصیتی جهت مقداردهی گره</summary>
    public T Value
    {
        get
        {
            return this.value;
        }
        set
        {
            this.value = value;
        }
    }
 
    /// <summary>تعداد گره‌های فرزند را بر میگرداند</summary>
    public int ChildrenCount
    {
        get
        {
            return this.children.Count;
        }
    }
 
    /// <summary>به گره یک فرزند اضافه می‌کند</summary>
    /// <param name="child">آرگومان این متد یک گره است که قرار است به فرزندی گره فعلی در آید</param>
    public void AddChild(TreeNode<T> child)
    {
        if (child == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
 
        if (child.hasParent)
        {
            throw new ArgumentException(
                "The node already has a parent!");
        }
 
        child.hasParent = true;
        this.children.Add(child);
    }
 
    /// <summary>
    /// گره ای که اندیس آن داده شده است بازگردانده می‌شود
    /// </summary>
    /// <param name="index">اندیس گره</param>
    /// <returns>گره بازگشتی</returns>
    public TreeNode<T> GetChild(int index)
    {
        return this.children[index];
    }
}
 
/// <summary>این کلاس ساختار درخت را به کمک کلاس گره‌ها که در بالا تعریف کردیم میسازد</summary>
/// <typeparam name="T">نوع مقادیری که قرار است داخل درخت ذخیره شوند</typeparam>
public class Tree<T>
{
    // گره ریشه
    private TreeNode<T> root;
 
    /// <summary>سازنده کلاس</summary>
    /// <param name="value">مقدار گره اول که همان ریشه می‌شود</param>
    public Tree(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
 
        this.root = new TreeNode<T>(value);
    }
 
    /// <summary>سازنده دیگر برای کلاس درخت</summary>
    /// <param name="value">مقدار گره ریشه مثل سازنده اول</param>
    /// <param name="children">آرایه ای از گره‌ها که فرزند گره ریشه می‌شوند</param>
    public Tree(T value, params Tree<T>[] children)
        : this(value)
    {
        foreach (Tree<T> child in children)
        {
            this.root.AddChild(child.root);
        }
    }
 
    /// <summary>
    /// ریشه را بر میگرداند ، اگر ریشه ای نباشد نال بر میگرداند
    /// </summary>
    public TreeNode<T> Root
    {
        get
        {
            return this.root;
        }
    }
 
    /// <summary>پیمودن عرضی و نمایش درخت با الگوریتم دی اف اس </summary>
    /// <param name="root">ریشه (گره ابتدایی) درختی که قرار است پیمایش از آن شروع شود</param>
    /// <param name="spaces">یک کاراکتر جهت جداسازی مقادیر هر گره</param>
    private void PrintDFS(TreeNode<T> root, string spaces)
    {
        if (this.root == null)
        {
            return;
        }
 
        Console.WriteLine(spaces + root.Value);
 
        TreeNode<T> child = null;
        for (int i = 0; i < root.ChildrenCount; i++)
        {
            child = root.GetChild(i);
            PrintDFS(child, spaces + "   ");
        }
    }
 
    /// <summary>متد پیمایش درخت به صورت عمومی که تابع خصوصی که در بالا توضیح دادیم را صدا می‌زند</summary>
    public void TraverseDFS()
    {
        this.PrintDFS(this.root, string.Empty);
    }
}
 
/// <summary>
/// کد استفاده از ساختار درخت
/// </summary>
public static class TreeExample
{
    static void Main()
    {
        // Create the tree from the sample
        Tree<int> tree =
            new Tree<int>(7,
                new Tree<int>(19,
                    new Tree<int>(1),
                    new Tree<int>(12),
                    new Tree<int>(31)),
                new Tree<int>(21),
                new Tree<int>(14,
                    new Tree<int>(23),
                    new Tree<int>(6))
            );
 
        // پیمایش درخت با الگوریتم دی اف اس یا عمقی
        tree.TraverseDFS();
 
        // خروجی
        // 7
        //       19
        //        1
        //        12
        //        31
        //       21
        //       14
        //        23
        //        6
    }
}
کلاس TreeNode وظیفه‌ی ساخت گره را بر عهده دارد و با هر شیء‌ایی که از این کلاس می‌سازیم، یک گره ایجاد می‌کنیم که با خاصیت Children و متد AddChild آن می‌توانیم هر تعداد گره را که می‌خواهیم به فرزندی آن گره در آوریم که باز خود آن گره می‌تواند در خاصیت Children یک گره دیگر اضافه شود. به این ترتیب با ساخت هر گره و ایجاد رابطه از طریق خاصیت children هر گره درخت شکل می‌گیرد. سپس گره والد در ساختار کلاس درخت Tree قرار می‌گیرد و این کلاس شامل متدهایی است که می‌تواند روی درخت، عملیات پردازشی چون پیمایش درخت را انجام دهد.


پیمایش درخت به روش عمقی (DFS (Depth First Search

هدف از پیمایش درخت ملاقات یا بازبینی (تهیه لیستی از همه گره‌های یک درخت) تنها یکبار هر گره در درخت است. برای این کار الگوریتم‌های زیادی وجود دارند که ما در این مقاله تنها دو روش DFS و BFS را بررسی می‌کنیم.

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

همین درخت را در نظر بگیرید:


 پیمایش درخت را از گره 7 آغاز می‌کنیم و آن را به عنوان ریشه در نظر می‌گیریم. حتی می‌توانیم پیمایش را از گره مثلا 19 آغاز کنیم و آن را برای پیمایش ریشه در نظر بگیریم ولی ما از همان 7 پیمایش را آغاز می‌کنیم:

ابتدا گره 7 ملاقات شده و آن را می‌نویسیم. سپس فرزندانش را بررسی می‌کنیم که سه فرزند دارد. یکی از فرزندان مثل گره 19 را انتخاب کرده و آن را ملاقات می‌کنیم (با هر بار ملاقات آن را چاپ می‌کنیم) سپس فرزندان آن را بررسی می‌کنیم و یکی از گره‌ها را انتخاب می‌کنیم و ملاقاتش می‌کنیم؛ برای مثال گره 1. از آن جا که گره یک، برگ است و فرزندی ندارد یک مرحله به سمت بالا برمی‌گردیم و برگ‌های 12 و 31 را هم ملاقات می‌کنیم. حالا همه‌ی فرزندان گره 19 را بررسی کردیم، بر می‌گردیم یک مرحله به سمت بالا و گره 21 را ملاقات می‌کنیم و از آنجا که گره 21 برگ است و فرزندی ندارد به بالا باز می‌گردیم و بعد گره 14 و فرزندانش 23 و 6 هم بررسی می‌شوند. پس ترتیب چاپ ما اینگونه می‌شود:

7-19-1-12-31-21-14-23-6


پیمایش درخت به روش (BFS (Breadth First Search 

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

7-19-21-14-1-12-31-23-6

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

برای این پیمایش از صف کمک گرفته می‌شود که مراحل زیر روی صف صورت می‌گیرد:

  • ریشه  وارد صف Q می‌شود.
  • دو مرحله زیر مرتبا تکرار می‌شوند:
  1. اولین گره صف به نام V را از Q در یافت می‌کنیم و آن را چاپ می‌کنیم.
  2. فرزندان گره V  را به صف اضافه می‌کنیم.
این نوع پیمایش، پیاده سازی راحتی دارد و همیشه نزدیک‌ترین گره‌ها به ریشه را می‌خواند و در هر مرحله گره‌هایی که می‌خواند از ریشه دورتر و دورتر می‌شوند.