پاسخ به بازخورد‌های پروژه‌ها
ایجاد فاکتور فروش
نام فیلد مورد نظر را که در select آخر منبع داده شما وجود دارد، به صورت رشته‌ای معرفی کنید. overload مناسب جهت انجام آن پیش بینی شده.
مطالب
آموزش فایرباگ - #4 - JavaScript Development
در قسمت قبل با توابع خط فرمان آشنا شدیم . در این قسمت با توابع کنسول آشنا خواهیم شد .

فایرباگ یک متغییر عمومی به نام console دارد که به همه‌ی صفحات باز شده در فایرفاکس اضافه می‌کند . این شیء متدهایی دارد که بوسیله آن‌ها می‌توانیم عملیاتی در برنامه مان انجام داده و اطلاعاتی را در کنسول چاپ کنیم .

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

توابع کنسول - Console API :

توجه : همانند قسمت قبل ، در این قسمت هم برای همراه شدن با تست‌ها ، کد صفحه‌ی زیر را ذخیره کنید و برای اجرای کدها ، آن‌ها را در قسمت خط فرمان ( در تب کنسول ) قرار بدهید و دکمه‌ی Run ( یا Ctrl + Enter ) را بزنید .
<input type="button" onclick="startTrace('Some Text')" value="startTrace" />
<input type="button" onclick="startError()" value="test Error" />

<script type="text/javascript">
    function startTrace(str) {
        return method1(100, 200);
    }
    function method1(arg1, arg2) {
        return method2(arg1 + arg2 + 100);
    }
    function method2(arg1) {
        var var1 = arg1 / 100;
        return method3(var1);
    }
    function method3(arg1) {
        console.trace();
        var total = arg1 * 100;
        return total;
    }

    function testCount() {
        // do something
        console.count("testCount() Calls Count .");
    }

    function startError() {
        testError();
    }

    function testError() {
        var errorObj = new Error();
        errorObj.message = "this is a test error";
        console.exception(errorObj);
    }

    function testFunc() {
        var t = 0;
        for (var i = 0; i < 100; i++) {
            t += i;
        }
    }
</script>
  • console.log(object[,object,...])

    این دستور یک پیغام در کنسول چاپ می‌کند .
    console.log("This is a log message!");
    نتیجه :



    این دستور را می‌توانیم به شکل‌های مختلفی فراخوانی کنیم .
    مثلا :
    console.log(1 , "+" , 2 , "=", (1+2));
    نتیجه :


    در این دستور می‌توانیم از چند حرف جایگزین هم استفاده کنیم .


    مثال :

    console.log("Firebug 1.0 beta was %s in December %i.","released",2006);
    نتیجه :


    عملکرد 3 جایگزین نخست با توجه با مثال قبل مشخص شد . پس به سراغ جایگزین %o و %c می‌رویم .
    اگر در رشته‌ی مورد نظر ، یک شیء ( تابع ، آرایه ، ... ) برای جایگزین %o ارسال کنیم ، در خروجی آن شیء بصورت لینک نمایش داده می‌شود که با کلیک بروی آن ، فایرباگ آن شیء را در تب مناسبش Inspect می‌کند .
    مثال :
    console.log("this is a test functin : %o",testFunc);

    نتیجه :



    و زمانی که بروی لینک testFunc کلیک کنیم :



    یک ترفند : بوسیله جایگزین %o توانستیم به تابع مورد نظر لینک بدهیم . اگر بجای جایگزین %o از %s استفاده کنیم ، می‌توانیم بدنه‌ی تابع را ببینیم :
    console.log("this is a test functin : %s",testFunc);
    نتیجه :




    توسط جایگزین %c هم می‌توانید خروجی را فرمت کنید .

    console.log("%cThis is a Style Formatted Log","color:green;text-decoration:underline;");

    نتیجه :


  • console.debug(object[, object, ...])
  • console.info(object[, object, ...])
  • console.warn(object[, object, ...])
  • console.error(object[, object, ...])

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



  • console.assert(expression[, object, ...])

    چک می‌کند که عبارت ارسال شده true هست یا نه . اگر true نبود ، پیغام وارد شده را چاپ و یک استثناء ایجاد می‌کند .
    console.assert(1==1,"this is a test error");
    console.assert(1!=1,"this is a test error");

    نتیجه :



  • console.clear()
  • console.dir(object)
  • console.dirxml(node)
  • console.profile([title])
  • console.profileEnd()

  • این توابع معادل توابع همنامشان در خط فرمان هستند که در قسمت قبل با عملکردشان آشنا شدیم .

  • console.trace()

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


    حتما متوجه شدید که متد method3 چگونه در کدهایمان فراخوانی شده است !؟
    ابتدا با کلیک بروی دکمه‌ی startTrace ، متد startTrace اجرا شده و به همین ترتیب متد startTrace متد method1 ، متد method1 هم متد method2 و در نهایت method2 متد method3 را فراخوانی کرده است .
    دستور trace زمانی که در حال بررسی کدهای برنامه نویسان دیگر هستید ، بسیار می‌تواند به شما کمک کند .

  • console.group(object[, object, ...])

    با این دستور می‌توانید لاگ‌های کنسول را بصورت تو در تو گروه بندی کنید .
    console.group("Group1");
    console.log("Log in Group1");
    console.group("Group2");
    console.log("Log in Group2");
    console.group("Group3");
    console.log("Log in Group3");
    نتیجه :



  • console.groupCollapsed(object[, object, ...])

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

  • console.groupEnd()

    به آخرین گروه بندی ایجاد شده خاتمه می‌دهد .

  • console.time(name)

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

  • console.timeEnd(name)

    تایمر همنام را متوقف و زمان طی شده را چاپ می‌کند .
    console.time("TestTime");
    var t = 1;
    for (var i = 0; i < 100000; i++) { t *= (i + t) }
    console.timeEnd("TestTime");
    نتیجه :


  • console.timeStamp()

    توضیحات کامل را از اینجا دریافت کنید .

  • console.count([title])

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


  • console.exception(error-object[, object, ...])

    یک پیغام خطا را به همراه ردیابی کامل اجرای کدها تا زمان رویداد خطا ( مانند متد trace ) چاپ می‌کند .
    در صفحه‌ی تست این متد را اجرا کنید :
    startError();
    نتیجه :

     توجه کنید که ما برای مشاهده‌ی عملکرد صحیح این دستور ، آن را در تابع testError قرار دادیم و بوسیله تابع startError آن فراخوانی کردیم .

  • console.table(data[, columns])

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


    برای اطلاعات بیشتر به اینجا مراجعه کنید .

منابع :
مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت سوم - تنظیم مقادیر خواص اشیاء
در قسمت قبل، چون متد Validate سرویس تصدیق هویت استفاده شده، همواره مقدار false را بر می‌گرداند:
_identityVerifier.Initialize();
var isValidIdentity = _identityVerifier.Validate(
     application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
شیء Mock آن‌را طوری تنظیم کردیم که بر اساس یک applicant مشخص، خروجی true را بازگشت دهد. اما در این بین، کدهای بررسی سرویس creditScorer را کامنت کردیم:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
if (_creditScorer.Score < MinimumCreditScore)
{
    return application.IsAccepted;
}
تا آزمایش واحد ما با موفقیت به پایان برسد. در این قسمت، کار تنظیم مقادیر خواص آن‌را در آزمون واحد، به کمک Mocked objects انجام می‌دهیم تا این قسمت از کد نیز پوشش داده شود. برای این منظور به کلاس LoanApplicationProcessor مراجعه کرده و در متد Process آن، ابتدا مجددا از همان overload ساده‌ی فوق متد Validate بجای نمونه‌ی ref دار استفاده کرده و سپس کدهای creditScorer را نیز از حالت کامنت خارج می‌کنیم.


تنظیم مقدار خاصیت Score شیء Mock شده

اینترفیس ICreditScorer به صورت زیر تعریف شده‌است و دارای خاصیت Score می‌باشد که مقدار عددی آن با مقدار حداقل اعتبار تنظیم شده‌ی در کلاس LoanApplicationProcessor مقایسه خواهد شد (MinimumCreditScore = 100_000):
namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
    }
}
برای تنظیم مقدار خاصیت Score، در متد Accept آزمون‌های واحد تهیه شده، می‌توان به صورت زیر عمل کرد:
var mockCreditScorer = new Mock<ICreditScorer>();
mockCreditScorer.Setup(x => x.Score).Returns(110_000);
که بسیار شبیه به نحوه‌ی تنظیم مقادیر بازگشتی متدها است. در متد Setup می‌توان به صورت strongly typed به تمام خواص اینترفیس ICreditScorer دسترسی یافت و سپس توسط متد Returns، مقدار بازگشتی آن‌ها را تنظیم نمود.
اکنون اگر متد آزمایش واحد Accept را بررسی کنیم، چون شخص درخواست دهنده، دارای اعتبار بیشتری از حداقل اعتبار مورد نیاز است، این آزمایش با موفقیت به پایان خواهد رسید. اگر این تنظیم صورت نمی‌گرفت، شیء mockCreditScorer، مقدار پیش‌فرض int یا همان صفر را به عنوان مقدار Score بازگشت می‌داد.


تنظیم مقادیر خواص تو در تو و سلسله مراتبی اشیاء Mock شده

برای کار با خواص تو در تو، ابتدا دو مدل زیر را ایجاد می‌کنیم:
namespace Loans.Models
{
    public class ScoreResult
    {
        public ScoreValue ScoreValue { get; }
    }

    public class ScoreValue
    {
        public int Score { get; }
    }
}
اکنون بجای مقدار ساده‌ی int Score { get; }، از نمونه‌ی ScoreResult فوق، در اینترفیس ICreditScorer استفاده خواهیم کرد:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
        
        ScoreResult ScoreResult { get; }
    }
}
در ادامه برای استفاده‌ی از ScoreResult، به کلاس LoanApplicationProcessor مراجعه کرده و در انتهای متد Process آن، این تغییر را ایجاد می‌کنیم:
//if (_creditScorer.Score < MinimumCreditScore)
if (_creditScorer.ScoreResult.ScoreValue.Score < MinimumCreditScore)
اینبار اگر متد آزمون واحد Accept را اجرا کنیم، با یک null reference exception به پایان می‌رسد؛ چون اولین سطح این شیء تو در تو، یعنی ScoreResult، مساوی نال است.
برای رفع این مشکل در متد آزمون واحد Accept، باید به صورت زیر عمل کرد:
var mockCreditScorer = new Mock<ICreditScorer>();
mockCreditScorer.Setup(x => x.Score).Returns(110_000);

var mockScoreValue = new Mock<ScoreValue>();
mockScoreValue.Setup(x => x.Score).Returns(110_000);

var mockScoreResult = new Mock<ScoreResult>();
mockScoreResult.Setup(x => x.ScoreValue).Returns(mockScoreValue.Object);

mockCreditScorer.Setup(x => x.ScoreResult).Returns(mockScoreResult.Object);
ابتدا از پایین‌ترین سطح یعنی ScoreValue شروع و مقدار خاصیت Score آن‌را تنظیم می‌کنیم.
سپس یک سطح بالاتر را یعنی ScoreResult را تنظیم خواهیم کرد. در اینجا نیاز است خاصیت ScoreValue آن به mock object قبلی تنظیم شود. به همین جهت Returns آن به خاصیت Object شیء mockScoreValue، تنظیم شده‌است.
در آخر برای تنظیم خاصیت ScoreResult شیء mockCreditScorer اصلی، از شیء mockScoreResult استفاده خواهیم کرد.

در این حالت اگر متد آزمون واحد Accept را اجرا کنیم، اینبار به خطای زیر برخواهیم خورد:
Test method Loans.Tests.LoanApplicationProcessorShould.Accept threw exception:
System.NotSupportedException: Unsupported expression: x => x.Score
Non-overridable members (here: ScoreValue.get_Score) may not be used in setup / verification expressions.
عنوان می‌کند که خاصیت Score شیء ScoreValue، قابل بازنویسی نیست (Non-overridable). منظورش این است که برای mocking آن خاصیت، باید آن‌را virtual تعریف کنیم تا کتابخانه‌ی Moq بتواند آن‌را بازنویسی کند. به همین جهت، هر دو خاصیتی را که در اینجا قصد بازنویسی آن‌ها را داریم، به صورت virtual تعریف می‌کنیم:
namespace Loans.Models
{
    public class ScoreResult
    {
        public virtual ScoreValue ScoreValue { get; }
    }

    public class ScoreValue
    {
        public virtual int Score { get; }
    }
}
اکنون اگر متد آزمایش واحد Accept را بررسی کنیم با موفقیت به پایان خواهد رسید.


ساده سازی روش تنظیم مقادیر خواص تو در تو و سلسله مراتبی اشیاء Mock شده

روش دیگری نیز برای تنظیم مقادیر خواص تو در تو در کتابخانه‌ی Moq وجود دارد:
mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000);
کتابخانه‌ی Moq قادر است به نحوی که مشاهده می‌کنید، سلسله مراتب اشیاء را به صورت strongly typed ایجاد کرده و در نهایت خاصیت Score آن‌را به 110_000 تنظیم کند.
بدیهی است در این حالت نیز باید شرط virtual بودن این خواص، برقرار باشد؛ در غیراینصورت همان استثنای NotSupportedException را دریافت خواهیم کرد.

یک نکته: اگر در زمان تشکیل یک Mock object، مقدار خاصیت DefaultValue آن‌را به صورت زیر تنظیم کنیم:
var mockCreditScorer = new Mock<ICreditScorer> { DefaultValue = DefaultValue.Mock };
تمام خواص تو در توی موجود در ICreditScorer، به صورت خودکار با نمونه‌های پیش‌فرض آن‌ها مقدار دهی و آماده‌ی استفاده خواهند شد. اگر بجای مقدار DefaultValue.Mock از DefaultValue.Empty استفاده شود، این مقادیر پیش‌فرض، نال خواهد بود (که همان حالت پیش‌فرض new Mock است).


بررسی تغییرات مقادیر خواص اشیاء Mock شده

کتابخانه‌ی Moq، امکان ردیابی تغییرات مقادیر خواص اشیاء Mock شده را نیز داراست. برای نمایش آن، فرض کنید خاصیت جدید Count را به اینترفیس ICreditScorer اضافه کرده‌ایم:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
        
        ScoreResult ScoreResult { get; }
        
        int Count { get; set; }
    }
}
سپس در کلاس LoanApplicationProcessor و متد Process آن، هربار که CalculateScore فراخوانی می‌شود، یکبار مقدار Count را افزایش می‌دهیم:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
_creditScorer.Count++;
اکنون در متد آزمون واحد Accept، بررسی می‌کنیم که آیا پس از یکبار فراخوانی متد CalculateScore، مقدار Count برای مثال 1 شده‌است یا خیر؟
Assert.AreEqual(1, mockCreditScorer.Object.Count);
تا اینجا اگر آزمون واحد را اجرا کنیم، با شکست مواجه خواهد شد. چون کتابخانه‌ی Moq تغییرات مقادیر خواص شیء mockCreditScorer.Object را ردیابی نمی‌کند و مقدار mockCreditScorer.Object.Count، همان مقدار پیش‌فرض نوع int، یعنی صفر می‌باشد.
برای فعال سازی ردیابی تغییرات مقادیر خاصیت Count، تنها کافی است آن‌را توسط متد SetupProperty، معرفی کنیم:
mockCreditScorer.SetupProperty(x => x.Count);
پس از این تغییر، بررسی متد آزمون واحد Accept با موفقیت به پایان می‌رسد.

در اینجا می‌توان یک مقدار اولیه را هم درنظر گرفت:
mockCreditScorer.SetupProperty(x => x.Count, 10);
بدیهی است در این صورت Assert.AreEqual ما با شکست مواجه می‌شود؛ چون اینبار مقدار Count نهایی، بر اساس این مقدار اولیه، 11 خواهد بود.


فعالسازی بررسی تغییرات تمام مقادیر خواص اشیاء Mock شده

اگر تعداد خواصی که قرار است مورد ردیابی قرارگیرند زیاد است، بجای فراخوانی متد SetupProperty بر روی تک تک آن‌ها، می‌توان تمام آن‌ها را به صورت زیر تحت کنترل قرار داد:
mockCreditScorer.SetupAllProperties();

نکته‌ی مهم: محل قرارگیری SetupAllProperties مهم است. برای مثال اگر این سطر را پس از سطر تنظیم مقدار پیش‌فرض x.ScoreResult.ScoreValue.Score قرار دهید، آزمایش با شکست مواجه می‌شود؛ چون تنظیمات بازگشت مقادیر پیش‌فرض خواص را به طور کامل بازنویسی می‌کند. بنابراین این سطر باید پیش از سطر تنظیم مقادیر پیش‌فرض خواص Mock شده، فراخوانی شود تا بر روی این مقادیر تنظیمی، تاثیری نداشته باشد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-03.zip
مطالب
بررسی مقدمات کتابخانه‌ی JSON.NET
چرا JSON.NET؟
JSON.NET یک کتابخانه‌ی سورس باز کار با اشیاء JSON در دات نت است. تاریخچه‌ی آن به 8 سال قبل بر می‌گردد و توسط یک برنامه نویس نیوزیلندی به نام James Newton King تهیه شده‌است. اولین نگارش آن در سال 2006 ارائه شد؛ مقارن با زمانی که اولین استاندارد JSON نیز ارائه گردید.
این کتابخانه از آن زمان تا کنون، 6 میلیون بار دانلود شده‌است و به علت کیفیت بالای آن، این روزها پایه اصلی بسیاری از کتابخانه‌ها و فریم ورک‌های دات نتی می‌باشد؛ مانند RavenDB تا ASP.NET Web API و SignalR مایکروسافت و همچنین گوگل نیز از آن جهت تدارک کلاینت‌های کار با API خود استفاده می‌کنند.
هرچند دات نت برای نمونه در نگارش سوم آن جهت مصارف WCF کلاسی را به نام DataContractJsonSerializer ارائه کرد، اما کار کردن با آن محدود است به فرمت خاص WCF به همراه عدم انعطاف پذیری و سادگی کار با آن. به علاوه باید درنظر داشت که JSON.NET از دات نت 2 به بعد تا مونو، Win8 و ویندوز فون را نیز پشتیبانی می‌کند.

برای نصب آن نیز کافی است دستور ذیل را در کنسول پاورشل نیوگت اجرا کنید:
 PM> install-package Newtonsoft.Json

معماری JSON.NET

کتابخانه‌ی JSON.NET از سه قسمت عمده تشکیل شده‌است:
الف) JsonSerializer
ب) LINQ to JSON
ج) JSON Schema


الف) JsonSerializer
کار JsonSerializer تبدیل اشیاء دات نتی به JSON و برعکس است. مزیت مهم آن امکانات قابل توجه تنظیم عملکرد و خروجی آن می‌باشد که این تنظیمات را به شکل ویژگی‌های خواص نیز می‌توان اعمال نمود. به علاوه امکان سفارشی سازی هر کدام نیز توسط کلاسی به نام JsonConverter، پیش بینی شده‌است.
یک مثال:
 var roles = new List<string>
{
   "Admin",
   "User"
};
string json = JsonConvert.SerializeObject(roles, Formatting.Indented);
در اینجا نحوه‌ی استفاده از JSON.NET را جهت تبدیل یک شیء دات نتی، به معادل JSON آن مشاهده می‌کنید. اعمال تنظیم Formatting.Indented سبب خواهد شد تا خروجی آن دارای Indentation باشد. برای نمونه اگر در برنامه‌ی خود قصد دارید فرمت JSON تو در تویی را به نحو زیبا و خوانایی نمایش دهید یا چاپ کنید، همین تنظیم ساده کافی خواهد بود.
و یا در مثال ذیل استفاده از یک anonymous object را مشاهده می‌کنید:
 var jsonString = JsonConvert.SerializeObject(new
{
   Id =1,
   Name = "Test"
}, Formatting.Indented);
به صورت پیش فرض تنها خواص عمومی کلاس‌ها توسط JSON.NET تبدیل خواهند شد.


تنظیمات پیشرفته‌تر JSON.NET

مزیت مهم JSON.NET بر سایر کتابخانه‌ها‌ی موجود مشابه، قابلیت‌های سفارشی سازی قابل توجه آن است. در مثال ذیل نحوه‌ی معرفی JsonSerializerSettings را مشاهده می‌نمائید:
var jsonData = JsonConvert.SerializeObject(new
{
   Id = 1,
   Name = "Test",
   DateTime = DateTime.Now
}, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   Converters =
   {
      new JavaScriptDateTimeConverter()
   }
});
در اینجا با استفاده از تنظیم JavaScriptDateTimeConverter، می‌توان خروجی DateTime استانداردی را به مصرف کنندگان جاوا اسکریپتی سمت کاربر ارائه داد؛ با خروجی ذیل:
 {
  "Id": 1,
  "Name": "Test",
  "DateTime": new Date(1409821985245)
}


نوشتن خروجی JSON در یک استریم

خروجی متد JsonConvert.SerializeObject یک رشته‌است که در صورت نیاز به سادگی توسط متد File.WriteAllText در یک فایل قابل ذخیره می‌باشد. اما برای رسیدن به حداکثر کارآیی و سرعت می‌توان از استریم‌ها نیز استفاده کرد:
using (var stream = File.CreateText(@"c:\output.json"))
{
    var jsonSerializer = new JsonSerializer
   {
      Formatting = Formatting.Indented
   };
   jsonSerializer.Serialize(stream, new
   {
     Id = 1,
     Name = "Test",
     DateTime = DateTime.Now
   });
}
کلاس JsonSerializer و متد Serialize آن یک استریم را نیز جهت نوشتن خروجی می‌پذیرند. برای مثال response.Output برنامه‌های وب نیز یک استریم است و در اینجا نوشتن مستقیم در استریم بسیار سریعتر است از تبدیل شیء به رشته و سپس ارائه خروجی آن؛ زیرا سربار تهیه رشته JSON از آن حذف می‌گردد و نهایتا GC کار کمتری را باید انجام دهد.


تبدیل JSON رشته‌ای به اشیاء دات نت

اگر رشته‌ی jsonData ایی را که پیشتر تولید کردیم، بخواهیم تبدیل به نمونه‌ای از شیء User ذیل کنیم:
public class User
{
   public int Id { set; get; }
   public string Name { set; get; }
   public DateTime DateTime { set; get; }
}
خواهیم داشت:
 var user = JsonConvert.DeserializeObject<User>(jsonData);
در اینجا از متد DeserializeObject به همراه مشخص سازی صریح نوع شیء نهایی استفاده شده‌است.
البته در اینجا با توجه به استفاده از JavaScriptDateTimeConverter برای تولید jsonData، نیاز است چنین تنظیمی را نیز در حالت DeserializeObject مشخص کنیم:
var user = JsonConvert.DeserializeObject<User>(jsonData, new JsonSerializerSettings
{
   Converters = {  new JavaScriptDateTimeConverter() }
});


مقدار دهی یک نمونه یا وهله‌ی از پیش موجود

متد JsonConvert.DeserializeObject یک شیء جدید را ایجاد می‌کند. اگر قصد دارید صرفا تعدادی از خواص یک وهله‌ی موجود، توسط JSON.NET مقدار دهی شوند از متد PopulateObject استفاده کنید:
 JsonConvert.PopulateObject(jsonData, user);


کاهش حجم JSON تولیدی

زمانیکه از متد JsonConvert.SerializeObject استفاده می‌کنیم، تمام خواص عمومی تبدیل به معادل JSON آن‌ها خواهند شد؛ حتی خواصی که مقدار ندارند. این خواص در خروجی JSON، با مقدار null مشخص می‌شوند. برای حذف این خواص از خروجی JSON نهایی تنها کافی است در تنظیمات JsonSerializerSettings، مقدار NullValueHandling = NullValueHandling.Ignore مشخص گردد.
var jsonData = JsonConvert.SerializeObject(object, new JsonSerializerSettings
{
   NullValueHandling = NullValueHandling.Ignore,
   Formatting = Formatting.Indented
});
مورد دیگری که سبب کاهش حجم خروجی نهایی خواهد شد، تنظیم DefaultValueHandling = DefaultValueHandling.Ignore است. در این حالت کلیه خواصی که دارای مقدار پیش فرض خودشان هستند، در خروجی JSON ظاهر نخواهند شد. مثلا مقدار پیش فرض خاصیت int مساوی صفر است. در این حالت کلیه خواص از نوع int که دارای مقدار صفر می‌باشند، در خروجی قرار نمی‌گیرند.
به علاوه حذف Formatting = Formatting.Indented نیز توصیه می‌گردد. در این حالت فشرده‌ترین خروجی ممکن حاصل خواهد شد.


مدیریت ارث بری توسط JSON.NET

در مثال ذیل کلاس کارمند و کلاس مدیر را که خود نیز در اصل یک کارمند می‌باشد، ملاحظه می‌کنید:
public class Employee
{
    public string Name { set; get; }
}

public class Manager : Employee
{
    public IList<Employee> Reports { set; get; }
}
در اینجا هر مدیر لیست کارمندانی را که به او گزارش می‌دهند نیز به همراه دارد. در ادامه نمونه‌ای از مقدار دهی این اشیاء ذکر شده‌اند:
 var employee = new Employee { Name = "User1" };
var manager1 = new Manager { Name = "User2" };
var manager2 = new Manager { Name = "User3" };
manager1.Reports = new[] { employee, manager2 };
manager2.Reports = new[] { employee };
با فراخوانی
 var list = JsonConvert.SerializeObject(manager1, Formatting.Indented);
یک چنین خروجی JSON ایی حاصل می‌شود:
{
  "Reports": [
    {
      "Name": "User1"
    },
    {
      "Reports": [
        {
          "Name": "User1"
        }
      ],
      "Name": "User3"
    }
  ],
  "Name": "User2"
}
این خروجی JSON جهت تبدیل به نمونه‌ی معادل دات نتی خود، برای مثال جهت رسیدن به manager1 در کدهای فوق، چندین مشکل را به همراه دارد:
- در اینجا مشخص نیست که این اشیاء، کارمند هستند یا مدیر. برای مثال مشخص نیست User2 چه نوعی دارد و باید به کدام شیء نگاشت شود.
- مشکل دوم در مورد کاربر User1 است که در دو قسمت تکرار شده‌است. این شیء JSON اگر به نمونه‌ی معادل دات نتی خود نگاشت شود، به دو وهله از User1 خواهیم رسید و نه یک وهله‌ی اصلی که سبب تولید این خروجی JSON شده‌است.

برای حل این دو مشکل، تغییرات ذیل را می‌توان به JSON.NET اعمال کرد:
var list = JsonConvert.SerializeObject(manager1, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   TypeNameHandling = TypeNameHandling.Objects,
   PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
با این خروجی:
{
  "$id": "1",
  "$type": "JsonNetTests.Manager, JsonNetTests",
  "Reports": [
    {
      "$id": "2",
      "$type": "JsonNetTests.Employee, JsonNetTests",
      "Name": "User1"
    },
    {
      "$id": "3",
      "$type": "JsonNetTests.Manager, JsonNetTests",
      "Reports": [
        {
          "$ref": "2"
        }
      ],
      "Name": "User3"
    }
  ],
  "Name": "User2"
}
- با تنظیم TypeNameHandling = TypeNameHandling.Objects سبب خواهیم شد تا خاصیت اضافه‌ای به نام $type به خروجی JSON اضافه شود. این نوع، در حین فراخوانی متد JsonConvert.DeserializeObject جهت تشخیص صحیح نگاشت اشیاء بکار گرفته خواهد شد و اینبار مشخص است که کدام شیء، کارمند است و کدامیک مدیر.
- با تنظیم PreserveReferencesHandling = PreserveReferencesHandling.Objects شماره Id خودکاری نیز به خروجی JSON اضافه می‌گردد. اینبار اگر به گزارش دهنده‌ها با دقت نگاه کنیم، مقدار $ref=2 را خواهیم دید. این مورد سبب می‌شود تا در حین نگاشت نهایی، دو وهله متفاوت از شیء با Id=2 تولید نشود.

باید دقت داشت که در حین استفاده از JsonConvert.DeserializeObject نیز باید JsonSerializerSettings یاد شده، تنظیم شوند.


ویژگی‌های قابل تنظیم در JSON.NET

علاوه بر JsonSerializerSettings که از آن صحبت شد، در JSON.NET امکان تنظیم یک سری از ویژگی‌ها به ازای خواص مختلف نیز وجود دارند.
- برای نمونه ویژگی JsonIgnore معروفترین آن‌ها است:
public class User
{
   public int Id { set; get; }

   [JsonIgnore]
   public string Name { set; get; }

   public DateTime DateTime { set; get; }
}
JsonIgnore سبب می‌شود تا خاصیتی در خروجی نهایی JSON تولیدی حضور نداشته باشد و از آن صرفنظر شود.

- با استفاده از ویژگی JsonProperty اغلب مواردی را که پیشتر بحث کردیم مانند NullValueHandling، TypeNameHandling و غیره، می‌توان تنظیم نمود. همچنین گاهی از اوقات کتابخانه‌های جاوا اسکریپتی سمت کاربر، از اسامی خاصی که از روش‌های نامگذاری دات نتی پیروی نمی‌کنند، در طراحی خود استفاده می‌کنند. در اینجا می‌توان نام خاصیت نهایی را که قرار است رندر شود نیز صریحا مشخص کرد. برای مثال:
[JsonProperty(PropertyName = "m_name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { set; get; }
همچنین در اینجا امکان تنظیم Order نیز وجود دارد. برای مثال مشخص کنیم که خاصیت X در ابتدا قرار گیرد و پس از آن خاصیت Y رندر شود.

- استفاده از ویژگی JsonObject به همراه مقدار OptIn آن به این معنا است که از کلیه خواصی که دارای ویژگی JsonProperty نیستند، صرفنظر شود. حالت پیش فرض آن OptOut است؛ یعنی تمام خواص عمومی در خروجی JSON حضور خواهند داشت منهای مواردی که با JsonIgnore مزین شوند.
[JsonObject(MemberSerialization.OptIn)]
public class User
{
    public int Id { set; get; }

    [JsonProperty]
    public string Name { set; get; }
 
    public DateTime DateTime { set; get; }
}

- با استفاده از ویژگی JsonConverter می‌توان نحوه‌ی رندر شدن مقدار خاصیت را سفارشی سازی کرد. برای مثال:
[JsonConverter(typeof(JavaScriptDateTimeConverter))]
public DateTime DateTime { set; get; }


تهیه یک JsonConverter سفارشی

با استفاده از JsonConverterها می‌توان کنترل کاملی را بر روی اعمال serialization و deserialization مقادیر خواص اعمال کرد. مثال زیر را در نظر بگیرید:
public class HtmlColor
{
   public int Red { set; get; }
   public int Green { set; get; }
   public int Blue { set; get; }
}

var colorJson = JsonConvert.SerializeObject(new HtmlColor
{
  Red = 255,
  Green = 0,
  Blue = 0
}, Formatting.Indented);
در اینجا علاقمندیم، در حین عملیات serialization، بجای اینکه مقادیر اجزای رنگ تهیه شده به صورت int نمایش داده شوند، کل رنگ با فرمت hex رندر شوند. برای اینکار نیاز است یک JsonConverter سفارشی را تدارک دید:
    public class HtmlColorConverter : JsonConverter
    {

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(HtmlColor);
        }

        public override object ReadJson(JsonReader reader, Type objectType,
                                        object existingValue, JsonSerializer serializer)
        {
            throw new NotSupportedException();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var color = value as HtmlColor;
            if (color == null)
                return;

            writer.WriteValue("#" + color.Red.ToString("X2")
                + color.Green.ToString("X2") + color.Blue.ToString("X2"));
        }
    }
کار با ارث بری از کلاس پایه JsonConverter شروع می‌شود. سپس باید تعدادی از متدهای این کلاس پایه را بازنویسی کرد. در متد CanConvert اعلام می‌کنیم که تنها اشیایی از نوع کلاس HtmlColor را قرار است پردازش کنیم. سپس در متد WriteJson منطق سفارشی خود را می‌توان پیاده سازی کرد.
از آنجائیکه این تبدیلگر صرفا قرار است برای حالت serialization استفاده شود، قسمت ReadJson آن پیاده سازی نشده‌است.

در آخر برای استفاده از آن خواهیم داشت:
var colorJson = JsonConvert.SerializeObject(new HtmlColor
{
  Red = 255,
  Green = 0,
  Blue = 0
},  new JsonSerializerSettings
    {
      Formatting = Formatting.Indented,
      Converters = { new HtmlColorConverter() }
    });   
مطالب
الگویی برای مدیریت دسترسی همزمان به ConcurrentDictionary
ConcurrentDictionary، ساختار داده‌ای است که امکان افزودن، دریافت و حذف عناصری را به آن به صورت thread-safe میسر می‌کند. اگر در برنامه‌ای نیاز به کار با یک دیکشنری توسط چندین thread وجود داشته باشد، ConcurrentDictionary راه‌حل مناسبی برای آن است.
اکثر متدهای این کلاس thread-safe طراحی شده‌اند؛ اما با یک استثناء: متد GetOrAdd آن thread-safe نیست:
 TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory);


بررسی نحوه‌ی کار با متد GetOrAdd

این متد یک کلید را دریافت کرده و سپس بررسی می‌کند که آیا این کلید در مجموعه‌ی جاری وجود دارد یا خیر؟ اگر کلید وجود داشته باشد، مقدار متناظر با آن بازگشت داده می‌شود و اگر خیر، delegate ایی که به عنوان پارامتر دوم آن معرفی شده‌است، اجرا خواهد شد، سپس مقدار بازگشت داده شده‌ی توسط آن به مجموعه اضافه شده و در آخر این مقدار به فراخوان بازگشت داده می‌شود.
var dictionary = new ConcurrentDictionary<string, string>();
 
var value = dictionary.GetOrAdd("key1", x => "item 1");
Console.WriteLine(value);
 
value = dictionary.GetOrAdd("key1", x => "item 2");
Console.WriteLine(value);
در این مثال زمانیکه اولین GetOrAdd فراخوانی می‌شود، مقدار item 1 بازگشت داده خواهد شد و همچنین این مقدار را در مجموعه‌ی جاری، به کلید key1 انتساب می‌دهد. در دومین فراخوانی، چون key1 در دیکشنری، دارای مقدار است، همان را بازگشت می‌دهد و دیگر به value factory ارائه شده مراجعه نخواهد کرد. بنابراین خروجی این مثال به صورت ذیل است:
item 1
item 1


دسترسی همزمان به متد GetOrAdd امن نیست

ConcurrentDictionary برای اغلب متدهای آن به صورت توکار مباحث قفل‌گذاری چند ریسمانی را اعمال می‌کند؛ اما نه برای متد GetOrAdd. زمانیکه valueFactory آن در حال اجرا است، دسترسی همزمان به آن thread-safe نیست و ممکن است بیش از یکبار فراخوانی شود.
یک مثال:
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var dictionary = new ConcurrentDictionary<int, int>();
            var options = new ParallelOptions { MaxDegreeOfParallelism = 100 };
            var addStack = new ConcurrentStack<int>();

            Parallel.For(1, 1000, options, i =>
            {
                var key = i % 10;
                dictionary.GetOrAdd(key, k =>
                {
                    addStack.Push(k);
                    return i;
                });
            });

            Console.WriteLine($"dictionary.Count: {dictionary.Count}");
            Console.WriteLine($"addStack.Count: {addStack.Count}");
        }
    }
}
یک نمونه خروجی این مثال می‌تواند به صورت ذیل باشد:
dictionary.Count: 10
addStack.Count: 13
در اینجا هر چند 10 آیتم در دیکشنری ذخیره شده‌اند، اما عملیاتی که در value factory متد GetOrAdd آن صورت گرفته، 13 بار اجرا شده‌است (بجای 10 بار).
علت اینجا است که در این بین، متد GetOrAdd توسط ترد A فراخوانی می‌شود، اما key را در دیکشنری جاری پیدا نمی‌کند. به همین جهت شروع به اجرای valueFactory آن خواهد کرد. در همین زمان ترد B نیز به دنبال همین key است. ترد قبلی هنوز به پایان کار خودش نرسیده‌است که مجددا valueFactory متعلق به همین key اجرا خواهد شد. به همین جهت است که در ConcurrentStack اجرا شده‌ی در valueFactory، بیش از 10 آیتم موجود هستند.


الگویی برای مدیریت دسترسی همزمان امن به متد GetOrAdd‌

یک روش برای دسترسی همزمان امن به متد GetOrAdd، توسط تیم ASP.NET Core به صورت ذیل ارائه شده‌است:
// 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the pipeline more
// once. To prevent this Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple
// threads but only one of the objects succeeds in creating a pipeline.
private readonly ConcurrentDictionary<Type, Lazy<RequestDelegate>> _pipelinesCache = 
new ConcurrentDictionary<Type, Lazy<RequestDelegate>>();
در اینجا با استفاده از کلاس Lazy، از ایجاد چندین pipeline به ازای یک key مشخص جلوگیری شده‌است.
یک مثال:
namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var dictionary = new ConcurrentDictionary<int, Lazy<int>>();
            var options = new ParallelOptions { MaxDegreeOfParallelism = 100 };
            var addStack = new ConcurrentStack<int>();

            Parallel.For(1, 1000, options, i =>
            {
                var key = i % 10;
                dictionary.GetOrAdd(key, k => new Lazy<int>(() =>
                {
                    addStack.Push(k);
                    return i;
                }));
            });

            // Access the dictionary values to create lazy values.
            foreach (var pair in dictionary)
                Console.WriteLine(pair.Value.Value);

            Console.WriteLine($"dictionary.Count: {dictionary.Count}");
            Console.WriteLine($"addStack.Count: {addStack.Count}");
        }
    }
}
با این خروجی:
10
1
2
3
4
5
6
7
8
9
dictionary.Count: 10
addStack.Count: 10
اینبار، هم dictionary و هم addStack دارای 10 عضو هستند که به معنای تنها اجرای 10 بار value factory است و نه بیشتر.
در این مثال دو تغییر صورت گرفته‌اند:
الف) مقادیر ConcurrentDictionary به صورت Lazy معرفی شده‌اند.
ب) متد GetOrAdd نیز یک مقدار Lazy را بازگشت می‌دهد.

زمانیکه از اشیاء Lazy استفاده می‌شود، خروجی‌های بازگشتی از GetOrAdd، توسط این اشیاء Lazy محصور خواهند شد. اما نکته‌ی مهم اینجا است که هنوز value factory آن‌ها فراخوانی نشده‌است. این فراخوانی تنها زمانی صورت می‌گیرد که به خاصیت Value یک شیء Lazy دسترسی پیدا کنیم و این دسترسی نیز به صورت thread-safe طراحی شده‌است. یعنی حتی اگر چند ترد new Lazy یک key مشخص را بازگشت دهند، تنها یکبار value factory متد GetOrAdd با دسترسی به خاصیت Value این اشیاء Lazy فراخوانی می‌شود و مابقی تردها منتظر مانده و تنها مقدار ذخیره شده‌ی در دیکشنری را دریافت می‌کنند و سبب اجرای مجدد value factory سنگین و زمانبر آن، نخواهند شد.

بر این مبنا می‌توان یک LazyConcurrentDictionary را نیز به صورت ذیل طراحی کرد:
    public class LazyConcurrentDictionary<TKey, TValue>
    {
        private readonly ConcurrentDictionary<TKey, Lazy<TValue>> _concurrentDictionary;
        public LazyConcurrentDictionary()
        {
            _concurrentDictionary = new ConcurrentDictionary<TKey, Lazy<TValue>>();
        }

        public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
        {
            var lazyResult = _concurrentDictionary.GetOrAdd(key,
             k => new Lazy<TValue>(() => valueFactory(k), LazyThreadSafetyMode.ExecutionAndPublication));
            return lazyResult.Value;
        }
    }
در اینجا ممکن است چندین ترد همزمان متد GetOrAdd را دقیقا با یک کلید مشخص فراخوانی کنند؛ اما تنها چندین شیء Lazy بسیار سبک که هنوز اطلاعات محصور شده‌ی توسط آن‌ها اجرا نشده‌است، ایجاد خواهند شد. اولین تردی که به خاصیت Value آن دسترسی پیدا کند، سبب اجرای delegate زمانبر و سنگین آن شده و مابقی تردها مجبور به منتظر ماندن جهت بازگشت این نتیجه از دیکشنری خواهند شد (و نه اجرای مجدد delegate).
در مثال فوق، به صورت صریحی پارامتر LazyThreadSafetyMode نیز مقدار دهی شده‌است. هدف از آن اطمینان حاصل کردن از آغاز این شیء Lazy با دسترسی به خاصیت Value آن، تنها توسط یک ترد است.

نمونه‌ی دیگر کار با خاصیت ویژه‌ی Value شیء Lazy را در مطلب «پشتیبانی توکار از ایجاد کلاس‌های Singleton از دات نت 4 به بعد» پیشتر در این سایت مطالعه کرده‌اید.
مطالب
آشنایی با NHibernate - قسمت دوم

آزمون واحد کلاس نگاشت تهیه شده

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

در این قسمت قصد داریم برای بررسی وضعیت کلاس نگاشت تهیه شده یک آزمون واحد تهیه کنیم. برای این منظور ارجاعی را به اسمبلی nunit.framework.dll به پروژه UnitTests که در ابتدای کار به solution جاری در VS.Net افزوده بودیم، اضافه نمائید (همچنین ارجاع‌هایی به اسمبلی‌های پروژه NHSample1 ، FluentNHibernate ، System.Data.SQLite ، NHibernate.ByteCode.Castle و Nhibernate نیز نیاز هستند). تمام اسمبلی‌های این فریم ورک‌ها از پروژه FluentNHibernate قابل استخراج هستند.

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

using FluentNHibernate;
using NHSample1.Domain;

namespace UnitTests
{
public class TestModel : PersistenceModel
{
public TestModel()
{
AddMappingsFromAssembly(typeof(CustomerMapping).Assembly);
}
}
}

کلاس FixtureBase : (جهت ایجاد سشن NHibernate در ابتدای آزمون واحد و سپس پاکسازی اشیاء در پایان کار)

using NUnit.Framework;
using NHibernate;
using FluentNHibernate;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;

namespace UnitTests
{
public class FixtureBase
{
protected SessionSource SessionSource { get; set; }
protected ISession Session { get; private set; }

[SetUp]
public void SetupContext()
{
var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.InMemory);

SessionSource = new SessionSource(
cfg.BuildConfiguration().Properties,
new TestModel());

Session = SessionSource.CreateSession();
SessionSource.BuildSchema(Session);
}

[TearDown]
public void TearDownContext()
{
Session.Close();
Session.Dispose();
}
}
}

و کلاس CustomerMapping_Fixture.cs : (جهت بررسی صحت نگاشت تهیه شده با کمک دو کلاس قبل)

using NUnit.Framework;
using FluentNHibernate.Testing;
using NHSample1.Domain;

namespace UnitTests
{
[TestFixture]
public class CustomerMapping_Fixture : FixtureBase
{
[Test]
public void can_correctly_map_customer()
{
new PersistenceSpecification<Customer>(Session)
.CheckProperty(c => c.Id, 1001)
.CheckProperty(c => c.FirstName, "Vahid")
.CheckProperty(c => c.LastName, "Nasiri")
.CheckProperty(c => c.AddressLine1, "Addr1")
.CheckProperty(c => c.AddressLine2, "Addr2")
.CheckProperty(c => c.PostalCode, "1234")
.CheckProperty(c => c.City, "Tehran")
.CheckProperty(c => c.CountryCode, "IR")
.VerifyTheMappings();
}
}
}

توضیحات:
اکنون به عنوان یک برنامه نویس متعهد نیاز است تا کار صورت گرفته در قسمت قبل را آزمایش کنیم.
کار بررسی صحت نگاشت تعریف شده در قسمت قبل توسط کلاس استاندارد PersistenceSpecification فریم ورک FluentNHibernate انجام خواهد شد (در کلاس CustomerMapping_Fixture). این کلاس برای انجام عملیات آزمون واحد نیاز به کلاس پایه دیگری به نام FixtureBase دارد که در آن کار ایجاد سشن NHibernate (در قسمت استاندارد SetUp آزمون واحد) و سپس آزاد سازی آن را در هنگام خاتمه کار ، انجام می‌دهد (در قسمت TearDown آزمون واحد).
این ویژگی‌ها که در مباحث آزمون واحد نیز به آن‌ها اشاره شده است، سبب اجرای متدهایی پیش از اجرا و بررسی هر آزمون واحد و سپس آزاد سازی خودکار منابع خواهند شد.
برای ایجاد یک سشن NHibernate نیاز است تا نوع دیتابیس و همچنین رشته اتصالی به آن (کانکشن استرینگ) مشخص شوند. فریم ورک Fluent NHibernate با ایجاد کلاس‌های کمکی برای این امر، به شدت سبب ساده‌ سازی انجام آن شده است. در این مثال، نوع دیتابیس به SQLite و در حالت دیتابیس در حافظه (in memory)، تنظیم شده است (برای انجام امور آزمون واحد با سرعت بالا).
جهت اجرای هر دستوری در NHibernate نیاز به یک سشن می‌باشد. برای تعریف شیء سشن، نه تنها نیاز به مشخص سازی نوع و حالت دیتابیس مورد استفاده داریم، بلکه نیاز است تا وهله‌ای از کلاس استاندارد PersistanceModel را نیز جهت مشخص سازی کلاس نگاشت مورد استفاده مشخص نمائیم. برای این منظور کلاس TestModel فوق تعریف شده است تا این نگاشت را از اسمبلی مربوطه بخواند و مورد استفاده قرار دهد (بر پایی اولیه این مراحل شاید در ابتدای امر کمی زمانبر باشد اما در نهایت یک پروسه استاندارد است). توسط این کلاس به سیستم اعلام خواهیم کرد که اطلاعات نگاشت را باید از کدام کلاس دریافت کند.
تا اینجای کار شیء SessionSource را با معرفی نوع دیتابیس و همچنین محل دریافت اطلاعات نگاشت اشیاء معرفی کردیم. در دو سطر بعدی متد SetupContext کلاس FixtureBase ، ابتدا یک سشن را از این منبع سشن تهیه می‌کنیم. شیء منبع سشن در این فریم ورک در حقیقت یک factory object است (الگوهای طراحی برنامه نویسی شیءگرا) که امکان دسترسی به انواع و اقسام دیتابیس‌ها را فراهم می‌سازد. برای مثال اگر روزی نیاز بود از دیتابیس اس کیوال سرور استفاده شود، می‌توان از کلاس MsSqlConfiguration بجای SQLiteConfiguration استفاده کرد و همینطور الی آخر.
در ادامه توسط شیء SessionSource کار ساخت database schema را نیز به صورت پویا انجام خواهیم داد. بله، همانطور که متوجه شده‌اید، کار ساخت database schema نیز به صورت پویا توسط فریم ورک NHibernate با توجه به اطلاعات کلاس‌های نگاشت، صورت خواهد گرفت.

این مراحل، نحوه ایجاد و بر پایی یک آزمایشگاه آزمون واحد فریم ورک Fluent NHibernate را مشخص ساخته و در پروژه‌های شما می‌توانند به کرات مورد استفاده قرار گیرند.

در ادامه اگر آزمون واحد را اجرا نمائیم (متد can_correctly_map_customer در کلاس CustomerMapping_Fixture)، نتیجه باید شبیه به شکل زیر باشد:



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

نکته:
شاید سؤال بپرسید که در تابع can_correctly_map_customer عملا چه اتفاقاتی رخ داده است؟ برای بررسی آن در متد SetupContext کلاس FixtureBase ، اولین سطر آن‌را به صورت زیر تغییر دهید تا عبارات SQL نهایی تولید شده را نیز بتوانیم در حین عملیات تست مشاهده نمائیم:

var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.ShowSql().InMemory);




مطابق متد تست فوق، عبارات تولید شده به شرح زیر هستند:

NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 2, @p1 = 1
NHibernate: INSERT INTO "Customer" (FirstName, LastName, AddressLine1, AddressLine2, PostalCode, City, CountryCode, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7);@p0 = 'Vahid', @p1 = 'Nasiri', @p2 = 'Addr1', @p3 = 'Addr2', @p4 = '1234', @p5 = 'Tehran', @p6 = 'IR', @p7 = 1001
NHibernate: SELECT customer0_.Id as Id0_0_, customer0_.FirstName as FirstName0_0_, customer0_.LastName as LastName0_0_, customer0_.AddressLine1 as AddressL4_0_0_, customer0_.AddressLine2 as AddressL5_0_0_, customer0_.PostalCode as PostalCode0_0_, customer0_.City as City0_0_, customer0_.CountryCode as CountryC8_0_0_ FROM "Customer" customer0_ WHERE customer0_.Id=@p0;@p0 = 1001

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

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