اشتراک‌ها
کتاب کار برنامه‌ نویسان

کتاب «Coders at Work  » نوشته پیتر سیبل در سال 2009 به چاپ رسیده و با وجودی که مدّت زیادی از انتشار آن نمی‌گذرد مورد استقبال زیادی از سوی طراحان نرم‌افزار و برنامه‌نویسان قرار گرفته است. پیتر سیبل، که نام کتابش را از کتاب‌های معروف و پیشین «Writers at Work  » و «Founders at Work  » اقتباس کرده، در این کتاب به مصاحبه با پانزده تن از برجسته‌ترین و سرشناس‌ترین برنامه‌نویسان و دانشمندان رایانه، با پیش زمینه تحصیلی و علاقه‌مندی‌های متفاوت در این حوزه، پرداخته و خواننده را با ایده‌های آن‌ها درباره زندگی و برنامه‌نویسی آشنا ساخته است.  

کتاب کار برنامه‌ نویسان
مطالب
آشنایی با M.A.F - قسمت دوم

قسمت قبل بیشتر آشنایی با یک سری از اصطلاحات مرتبط با فریم ورک MAF بود و همچنین نحوه‌ی کلی استفاده از آن. در این قسمت یک مثال ساده را با آن پیاده سازی خواهیم کرد و فرض قسمت دوم بر این است که افزونه‌ی Visual Studio Pipeline Builder را نیز نصب کرده‌اید.

یک نکته پیش از شروع:
- اگر افزونه‌ی Visual Studio Pipeline Builder پس از نصب به منوی Tools اضافه نشده است، یک پوشه‌ی جدید را به نام Addins در مسیر Documents\Visual Studio 2008 ایجاد کرده و سپس فایل‌های آن‌را در این مسیر کپی کنید.

ساختار اولیه یک پروژه MAF

- پروژ‌ه‌هایی که از MAF استفاده می‌کنند، نیاز به ارجاعاتی به دو اسمبلی استاندارد System.AddIn.dll و System.AddIn.Contract.dll دارند (مطابق شکل زیر):



- ساختار آغازین یک پروژه MAF از سه پروژه تشکیل می‌شود که توسط افزونه‌ی Visual Studio Pipeline Builder به 7 پروژه بسط خواهد یافت.
این سه پروژه استاندارد آغازین شامل موارد زیر هستند:



- هاست: همان برنامه‌ی اصلی که قرار است از افزونه استفاده کند.
- قرار داد: نحو‌ه‌ی تعامل هاست و افزونه در این پروژه تعریف می‌شود. (یک پروژه از نوع class library)
- افزونه: کار پیاده سازی قرار داد را عهده دار خواهد شد. (یک پروژه از نوع class library)

- همچنین مرسوم است جهت مدیریت بهتر خروجی‌های حاصل شده یک پوشه Output را نیز به این solution اضافه کنند:



اکنون با توجه به این محل خروجی، به خواص Build سه پروژه موجود مراجعه کرده و مسیر Build را اندکی اصلاح خواهیم کرد (هر سه مورد بهتر است اصلاح شوند)، برای مثال:



نکته‌ی مهم هم اینجا است که خروجی host باید به ریشه این پوشه تنظیم شود و سایر پروژه‌ها هر کدام خروجی خاص خود را در پوشه‌ای داخل این ریشه باید ایجاد کنند.



تا اینجا قالب اصلی کار آماده شده است. قرارداد ما هم به شکل زیر است (ویژگی AddInContract آن نیز نباید فراموش شود):

using System.AddIn.Pipeline;
using System.AddIn.Contract;

namespace CalculatorConract
{
[AddInContract]
public interface ICalculatorContract : IContract
{
double Operate(string operation, double a, double b);
}
}

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

ایجاد pipeline

اگر قسمت قبل را مطالعه کرده باشید، یک راه حل مبتنی بر MAF از 7 پروژه تشکیل می‌شود که عمده‌ترین خاصیت آن‌ها مقاوم کردن سیستم در مقابل تغییرات نگارش قرارداد است. در این حالت اگر قرار داد تغییر کند، نه هاست و نه افزونه‌ی قدیمی، نیازی به تغییر در کدهای خود نخواهند داشت و این پروژه‌های میانی هستند که کار وفق دادن (adapters) نهایی را برعهده می‌گیرند.


برای ایجاد خودکار View ها و همچنین Adapters ، از افزونه‌ی Visual Studio Pipeline Builder که پیشتر معرفی شد استفاده خواهیم کرد.



سه گزینه‌ی آن هم مشخص هستند. نام پروژه‌ی قرارداد، مسیر پروژه‌ی هاست و مسیر خروجی نهایی معرفی شده. پیش از استفاده از این افزونه نیاز است تا یکبار solution مورد نظر کامپایل شود. پس از کلیک بر روی دکمه‌ی OK، پروژه‌های ذکر شده ایجاد خواهند شد:


پس از ایجاد این پروژه‌ها، نیاز به اصلاحات مختصری در مورد نام اسمبلی و فضای نام هر کدام می‌باشد؛ زیرا به صورت پیش فرض هر کدام به نام template نامگذاری شده‌اند:



پیاده سازی افزونه

قالب کاری استفاده از این فریم ورک آماده است. اکنون نوبت به پیاده سازی یک افزونه می‌باشد. به پروژه AddIn مراجعه کرده و ارجاعی را به اسمبلی AddInView خواهیم افزود. به این صورت افزونه‌ی ما به صورت مستقیم با قرارداد سروکار نداشته و ارتباطات، در راستای همان pipeline تعریف شده، جهت مقاوم شدن در برابر تغییرات صورت می‌گیرد:
using System;
using CalculatorConract.AddInViews;
using System.AddIn;

namespace CalculatorAddIn
{
[AddIn]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
throw new NotImplementedException();
}
}
}

در اینجا افزونه‌ی ما باید اینترفیس ICalculator مربوط به AddInView را پیاده سازی نماید که برای مثال خواهیم داشت:

using System;
using CalculatorConract.AddInViews;
using System.AddIn;

namespace CalculatorAddIn
{
[AddIn("افزونه یک", Description = "توضیحات", Publisher = "نویسنده", Version = "نگارش یک")]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
switch (operation)
{
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
default:
throw new NotSupportedException("عملیات مورد نظر توسط این افزونه پشتیبانی نمی‌شود");
}
}
}
}

همانطور که در قسمت قبل نیز ذکر شد، این کلاس باید با ویژگی AddIn مزین شود که توسط آن می‌توان توضیحاتی در مورد نام ، نویسنده و نگارش افزونه ارائه داد.


استفاده از افزونه‌ی تولید شده

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

نکته‌ی مهم!
در هر دو ارجاع به HostView و یا AddInView باید خاصیت Copy to local به false تنظیم شود، در غیر اینصورت افزونه‌ی شما بارگذاری نخواهد شد.



پس از افزودن ارجاعی به HostView، نمونه‌ای از استفاده از افزونه‌ی تولید شده به صورت زیر می‌تواند باشد که توضیحات مربوطه به صورت کامنت آورده شده است:

using System;
using System.AddIn.Hosting;
using CalculatorConract.HostViews;

namespace Calculator
{
class Program
{
private static ICalculator _calculator;

static void doOperation()
{
Console.WriteLine("1+2: {0}", _calculator.Operate("+", 1, 2));
}

static void Main(string[] args)
{
//مسیر پوشه ریشه مربوطه به خط لوله افزونه‌ها
string path = Environment.CurrentDirectory;

//مشخص سازی مسیر خواندن و کش کردن افزونه‌ها
AddInStore.Update(path);

//یافتن افزونه‌هایی سازگار با شرایط قرارداد پروژه
//در اینجا هیچ افزونه‌ای بارگذاری نمی‌شود
var addIns = AddInStore.FindAddIns(typeof(ICalculator), path);

//اگر افزونه‌ای یافت شد
if (addIns.Count > 0)
{
var addIn = addIns[0]; //استفاده از اولین افزونه
Console.WriteLine("1st addIn: {0}", addIn.Name);

//فعال سازی افزونه و همچنین مشخص سازی سطح دسترسی آن
_calculator = addIn.Activate<ICalculator>(AddInSecurityLevel.Intranet);

//یک نمونه از استفاده آن
doOperation();
}

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

چند نکته جالب توجه در مورد قابلیت‌های ارائه شده:
- مدیریت load و unload پویا
- امکان تعریف سطح دسترسی و ویژگی‌های امنیتی اجرای یک افزونه
- امکان ایزوله سازی پروسه اجرای افزونه از هاست (در ادامه توضیح داده خواهد شد)
- مقاوم بودن پروژه به نگارش‌های مختلف قرارداد


اجرای افزونه در یک پروسه مجزا

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

//فعال سازی افزونه و همچنین مشخص سازی سطح دسترسی آن
//همچنین جدا سازی پروسه اجرایی افزونه از هاست
_calculator = addIn.Activate<ICalculator>(
new AddInProcess(),
AddInSecurityLevel.Intranet);

در این حالت اگر پس از فعال شدن افزونه، یک break point قرار دهیم و به task manager ویندوز مراجعه نمائیم، پروسه‌ی مجزای افزونه قابل مشاهده است.



برای مطالعه بیشتر + ، + ، + و +

نظرات مطالب
BloggerToCHM
سلام،
من برای کار با SQLite از افزونه زیر استفاده می‌کنم:
https://addons.mozilla.org/en-US/firefox/addon/5817
دیتابیس خودتون را با آن باز کنید.
بعد در برگه execute sql آن، عبارت sql مورد نظر خودتون رو تست کنید تا خطای آن واضح مشخص شود.
همچنین اینجا می‌توان بهتر دید که آیا جدول مورد نظر وجود دارد یا خیر.
مطالب
آشنایی با Oslo - قسمت اول

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

دریافت Oslo

Oslo از سه قسمت عمده تشکیل شده است:
  • الف) زبان مدل سازی M
  • ب) ابزار مدل سازی Quadrant
  • ج) استفاده از SQL Server به عنوان مخزن

زبان مدل سازی M از سه قسمت به نام‌های MGraph ، MGrammer و MSchema تشکیل می‌شود.
MGrammer : گرامر مورد استفاده در SDL را تعریف می‌کند. Syntax Directed Translation
MSchema : طرح مدل را تعریف خواهد کرد.
MGraph : اگر MSchema بیانگر انواع باشد، MGraph بیانگر وهله‌ها خواهد بود.

یک مثال:
برنامه‌ی Intellipad را اجرا کنید (فرض بر این است که SDK فوق را نصب کرده‌اید)



در اینجا حالت را بر روی M Mode قرار دهید (مطابق تصویر) و همچنین از منوی ظاهر شده‌ی M Mode ، گزینه‌ی Generic T-SQL preview را هم انتخاب کنید.

اولین ماژول ما به صورت زیر است:

module Test1
{
type ApplicationUser
{
UserID : Integer64=AutoNumber();
FirstName :Text#15;
LastName : Text#25;
Password : Text#10;
} where identity UserID;
}

ابتدا نام ماژول مشخص می‌شود. شبیه به معرفی یک فضای نام در برگیرنده‌ی اشیای مربوطه. سپس type ، بیانگر همان MSchema خواهد بود.
در این مثال شناسه‌ی کاربری از نوع Integer64 خود افزایش یابنده تعریف شده است (نوع identity در اس کیوال سرور).
فیلدهای نام ، نام خانوادگی و کلمه‌ی عبور از نوع متنی با اندازه‌های مشخص 15 ، 25 و 10 کاراکتر تعریف شده‌اند. اگر اندازه مشخص نبود نوع را تنها Text تعریف کنید.
نکته:
1-اگر پس از Text علامت ? قرار گیرد، به معنای فیلدی از نوع nullable خواهد بود و برعکس. زیبایی Intellipad هم در اینجا است که بلافاصله پس از تایپ شما، عبارت T-SQL معادل را تولید می‌کند.
2-در اینجا UserID‌ به صورت identity معرفی شده است. در زبان ام ، identity همانند primary key‌ در عبارات T-SQL عمل می‌کند و نباید اشتباه گرفته شود.

تا اینجا فقط یک type تعریف شده است. برای تبدیل آن به یک جدول باید آن‌را توسعه داد.

ApplicationUserCollection : ApplicationUser*;

این سطر را به پس از تعریف type اضافه نمائید. علامت ستاره در اینجا به معنای صفر یا بیشتر است و جهت بسط نوع تعریف شده به یک مجموعه به کار می‌رود. اکنون با اضافه شدن این سطر، Intellipad بلافاصله عبارات T-SQL معادل را تولید خواهد کرد که در تصویر مشخص است. به این صورت MGraph ما که بیانگر وهله‌هایی از نوع ApplicationUser هستند تولید گردید.

اکنون قصد داریم گروهی از کاربرها را به صورت نمونه ایجاد کنیم:

ApplicationUserCollection
{
//using a named instance
User1 {
FirstName="user1",
LastName="name1",
Password="1@34"
},
User2 {
FirstName="user2",
LastName="name2",
Password="123@4"
},
User3 {
FirstName="user3",
LastName="name3",
Password="56#2"
},
User4 {
FirstName="user4",
LastName="name4",
Password="789@5"
}
}

سطرهای فوق را پس از تعریف ApplicationUserCollection در Intellipad اضافه کنید. بلافاصله Intellipad عبارات T-SQL معادل را برای ما تولید خواهد کرد.



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

نظرات مطالب
استفاده از DbProviderFactory
من توصیه می‌کنم که ADO.NET رو به شکل خام آن فراموش کنید. این نوع روش‌ها هرچند پایه و اساس تمام ORMهای نوشته شده هستند، اما فقط ابتدای کار را به شما نشان می‌دهند. واقعیت این است که سوئیچ کردن بین بانک‌های اطلاعاتی مختلف نیاز به تولید SQL قابل فهم برای آن موتور خاص را نیز دارد. اینجا است که ORMها در وقت شما صرفه جویی می‌کنند. شما کوئری LINQ می‌نویسید اما در پشت صحنه بر اساس پروایدر مورد استفاده، این کوئری LINQ به معادل SQL قابل فهم برای بانک اطلاعاتی مورد نظر ترجمه می‌شود. خیلی از توابع هستند که در بانک‌های اطلاعاتی مختلف تفاوت می‌کنند و این SQL ایی که مورد بحث است ... در عمل آنچنان استاندارد نیست. توابع تاریخ در SQLite با SQL Server فرق می‌کند. نوع‌های داده‌ای این‌ها عموما تطابق ندارد و مسایل دیگر. ORMها می‌توانند این مسایل را به خوبی مدیریت کنند بدون اینکه شما آنچنان درگیر این جزئیات شوید.
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها
اولین Middleware موجود در بسته‌ی Microsoft.AspNetCore.Diagnostics را در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 3 - Middleware چیست؟» با نمایش welcome page آن، بررسی کردیم. در این مطلب سایر صفحات مخصوص توسعه دهنده‌های موجود در این بسته را مرور خواهیم کرد.



مشاهده‌ی جزئیات اطلاعات سرور و بسته‌های نصب شده‌ی بر روی آن

در نگارش‌های قبل از RTM، با فراخوانی app.UseRuntimeInfoPage در متد Configure کلاس Startup، ریز اطلاعاتی از وضعیت سرور و بسته‌های موجود در آن با مراجعه‌ی به آدرس http://site/runtimeinfo نمایش داده می‌شدند. این مورد خاص از نگارش RTM حذف شده‌است (احتمالا به دلایل امنیتی). البته اگر علاقمند به بررسی کدهای آن باشید، هنوز تاریخچه‌ی آن در GitHub موجود است .


مدیریت خطاها در برنامه‌های ASP.NET Core 1.0

به متد Configure کلاس Startup مراجعه کرد و یک سطر استثناء را به ابتدای کدهای Middleware انتهایی آن اضافه کنید:
public void Configure(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        throw new Exception("Generic Error");
        await context.Response.WriteAsync("Hello DNT!");
    });
}
هدف این است که بررسی کنیم اگر استثنایی در یک Middleware رخ داد، برنامه چه خروجی را نمایش می‌دهد.
در این حالت اگر برنامه را اجرا کنیم، این خروجی را دریافت خواهیم کرد:


و اگر به وضعیت بازگشت داده شده‌ی از طرف سرور دقت کنیم، فقط internal server error است:


در اینجا برخلاف نگارش‌های قبلی ASP.NET، دیگر حتی صفحه‌ی زرد رنگ معروف نمایش خطاها (yellow screen of death) نیز فعال نیستند. برای فعال سازی آن نیاز است Middleware مرتبط با آن‌را به نحو ذیل به برنامه معرفی کنیم:
public void Configure(IApplicationBuilder app)
{
   app.UseDeveloperExceptionPage();
پس از این فعال سازی، اگر مجددا برنامه را اجرا کنید، این خروجی را می‌توان در مرورگر مشاهده کرد:


به دلایل امنیتی و عدم نشت اطلاعات سمت سرور و خصوصا عدم امکان دیباگ از راه دور برنامه توسط مهاجمین، این Middleware به صورت پیش فرض فعال نیست.
بنابراین این سؤال مطرح می‌شود که چگونه می‌توان این صفحه را تنها در حین توسعه‌ی برنامه نمایش داد؟
پاسخ آن به نحوه‌ی طراحی متد Configure در کلاس Startup بر می‌گردد. این متد امضای ثابتی را ندارد. هر تعداد سرویسی را که نیاز داشتید، می‌توانید به عنوان پارامتر این متد معرفی کنید و کار تزریق وابستگی‌ها و نمونه سازی آن‌ها، توسط امکانات توکار ASP.NET Core به صورت خودکار انجام می‌شود. برای مثال سرویس IApplicationBuilder، یکی از سرویس‌های توکار ASP.NET Core است و برای تنظیم آن نیازی نیست تا کار خاصی را انجام دهیم. به همین جهت است که صرفا معرفی اینترفیس آن در این متد، وهله‌ای را از سازنده‌ی برنامه در اختیار ما قرار می‌دهد. سرویس‌ها را در مطلبی جداگانه مورد بررسی قرار خواهیم داد، اما فعلا جهت تکمیل بحث باید درنظر داشت که یکی دیگر از سرویس‌های توکار ASP.NET Core، به نام IHostingEnvironment، اطلاعاتی را در مورد محیطی که برنامه را در آن اجرا می‌کنیم در اختیار ما قرار می‌دهد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
روش معرفی آن نیز همانند روش معرفی سرویس IApplicationBuilder است و تنها کافی است به عنوان یک پارامتر جدید متد Configure معرفی شود. وهله سازی و تنظیمات آن نیز به صورت خودکار توسط ASP.NET Core انجام خواهد شد. اکنون پس از تزریق این سرویس، می‌توان صفحه‌ی نمایش جزئیات خطاها را تنها محدود به محیط توسعه کرد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
در مورد انواع محیط‌های توسعه، در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution» در انتهای بحث به «نقش فایل launchsetting.json» اشاره شد. اگر بر روی پروژه کلیک راست کرده و به صفحه‌ی properties آن مراجعه کنید و یا دوبار کلیک بر روی گره properties، یک چنین تنظیمی را می‌توان مشاهده کرد:


این متغیر محیطی می‌تواند سه مقدار Development, Staging و Production را داشته باشد و بر اساس این متغیر و مقدار آن است که یکی از سه متد ذیل مفهوم پیدا می‌کنند و true یا false را باز می‌گردانند:
if(env.IsDevelopment()){ }
if(env.IsProduction()){ }
if(env.IsStaging()){ }


نمایش و مدیریت خطاها در حالت Production

از app.UseDeveloperExceptionPage صرفا در حالت توسعه استفاده کنید؛ چون اطلاعات نمایش داده شده‌ی توسط آن، بیش از اندازه برای مهاجمین مفید است. اما در حالت توزیع نهایی بر روی سرور چه باید کرد؟
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(errorHandlingPath: "/MyControllerName/SomeActionMethodName");
    }
در این حالت از Middleware دیگری به نام ExceptionHandler با فراخوانی app.UseExceptionHandler می‌توان کمک گرفت. کار آن هدایت کاربر به صفحه‌ای خاص از برنامه، در صورت بروز استثنایی است. در اینجا شما می‌توانید یک صفحه‌ی عمومی «خطایی رخ داده‌است» را بدون ذکر هیچ نوع جزئیاتی، به کاربر نمایش دهید.
به علاوه در اینجا (در این قسمت خاص برنامه که توسط پارامتر errorHandlingPath مشخص شده‌است) با استفاده از قطعه کد ذیل، دسترسی کاملی را به اطلاعات خطای رخ داده، جهت ثبت و لاگ آن دارید:
 var feature = HttpContext.Features.Get<IExceptionHandlerFeature>();
var error = feature?.Error;


بررسی میان‌افزار StatusCode

این میان افزار برای مدیریت responseهایی که status code آن‌ها بین 400 تا 600 هستند، طراحی شده‌است. بر اساس این شماره‌ها، می‌توان خطای خاصی را بازگشت داده و یا کاربر را به یک صفحه یا کنترلر خاصی در برنامه، هدایت کرد.
در حالت عادی ثبت آن
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseStatusCodePages();
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(errorHandlingPath: "/MyControllerName/SomeActionMethodName");
    }
}
تنها یک خروجی متنی را نمایش می‌دهد.


برای نمونه در اینجا مسیری درخواست داده شده‌است که توسط برنامه پردازش نمی‌شود و وجود ندارد.
اگر خواستید تا status code واقعی، به کاربر بازگشت داده شود، اما خروجی نمایش داده شده را سفارشی سازی کنید، می‌توانید از متد UseStatusCodePagesWithReExecute استفاده نمائید:
 app.UseStatusCodePagesWithReExecute("/MyControllerName/SomeActionMethodName/{0}");
در اینجا کاربر هنوز status code مساوی 404 را دریافت می‌کند (مناسب برای موتورهای جستجو)، اما اکشن متد خاصی در برنامه، سبب بازگشت یک View سفارشی به کاربر خواهد شد (بجای نمایش یک متن ساده). پارامتر {0} آن نیز همان شماره status code بازگشتی است.
مطالب
آشنایی با NHibernate - قسمت سوم

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

کلاس Product تعریف شده در فایل جدید Product.cs در پوشه domain برنامه:

namespace NHSample1.Domain
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
public bool Discontinued { get; set; }
}
}
کلاس ProductMapping تعریف شده در فایل جدید ProductMapping.cs (توصیه شده است که به ازای هر کلاس یک فایل جداگانه در نظر گرفته شود)، در پوشه Mappings برنامه:

using FluentNHibernate.Mapping;
using NHSample1.Domain;

namespace NHSample1.Mappings
{
public class ProductMapping : ClassMap<Product>
{
public ProductMapping()
{
Not.LazyLoad();
Id(p => p.Id).GeneratedBy.HiLo("1000");
Map(p => p.Name).Length(50).Not.Nullable();
Map(p => p.UnitPrice).Not.Nullable();
Map(p => p.Discontinued).Not.Nullable();
}
}
}
همانطور که ملاحظه می‌کنید، روش تعریف آن‌ها همانند شیء Customer است که در قسمت‌های قبل بررسی شد و نکته جدیدی ندارد.
آزمون واحد بررسی این نگاشت نیز همانند مثال قبلی است.
کلاس ProductMapping_Fixture را در فایل جدید ProductMapping_Fixture.cs به پروژه UnitTests خود (که ارجاعات آن‌را در قسمت قبل مشخص کردیم) خواهیم افزود:

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

namespace UnitTests
{
[TestFixture]
public class ProductMapping_Fixture : FixtureBase
{
[Test]
public void can_correctly_map_product()
{
new PersistenceSpecification<Product>(Session)
.CheckProperty(p => p.Id, 1001)
.CheckProperty(p => p.Name, "Apples")
.CheckProperty(p => p.UnitPrice, 10.45m)
.CheckProperty(p => p.Discontinued, true)
.VerifyTheMappings();
}
}
}
و پس از اجرای این آزمون واحد، عبارات SQL ایی که به صورت خودکار توسط این ORM جهت بررسی عملیات نگاشت صورت خواهند گرفت به صورت زیر می‌باشند:

ProductMapping_Fixture.can_correctly_map_product : Passed
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 "Product" (Name, UnitPrice, Discontinued, Id) VALUES (@p0, @p1, @p2, @p3);@p0 = 'Apples', @p1 = 10.45, @p2 = True, @p3 = 1001
NHibernate: SELECT product0_.Id as Id1_0_, product0_.Name as Name1_0_, product0_.UnitPrice as UnitPrice1_0_, product0_.Discontinued as Disconti4_1_0_ FROM "Product" product0_ WHERE product0_.Id=@p0;@p0 = 1001

در ادامه تعریف کلاس کارمند، نگاشت و آزمون واحد آن به صورت زیر خواهند بود:

using System;
namespace NHSample1.Domain
{
public class Employee
{
public int Id { set; get; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
}


using NHSample1.Domain;
using FluentNHibernate.Mapping;

namespace NHSample1.Mappings
{
public class EmployeeMapping : ClassMap<Employee>
{
public EmployeeMapping()
{
Not.LazyLoad();
Id(e => e.Id).GeneratedBy.Assigned();
Map(e => e.LastName).Length(50);
Map(e => e.FirstName).Length(50);
}
}
}


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

namespace UnitTests
{
[TestFixture]
public class EmployeeMapping_Fixture : FixtureBase
{
[Test]
public void can_correctly_map_employee()
{
new PersistenceSpecification<Employee>(Session)
.CheckProperty(p => p.Id, 1001)
.CheckProperty(p => p.FirstName, "name1")
.CheckProperty(p => p.LastName, "lname1")
.VerifyTheMappings();
}
}
}
خروجی SQL حاصل از موفقیت آزمون واحد آن:

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 "Employee" (LastName, FirstName, Id) VALUES (@p0, @p1, @p2);@p0 = 'lname1', @p1 = 'name1', @p2 = 1001
NHibernate: SELECT employee0_.Id as Id4_0_, employee0_.LastName as LastName4_0_, employee0_.FirstName as FirstName4_0_ FROM "Employee" employee0_ WHERE employee0_.Id=@p0;@p0 = 1001

همانطور که ملاحظه می‌کنید، این آزمون‌های واحد 4 مرحله را در یک سطر انجام می‌دهند:
الف) ایجاد یک وهله از کلاس Employee
ب) ثبت اطلاعات کارمند در دیتابیس
ج) دریافت اطلاعات کارمند در وهله‌ای جدید از شیء Employee
د) و در پایان بررسی می‌کند که آیا شیء جدید ایجاد شده با شیء اولیه مطابقت دارد یا خیر

اکنون در ادامه پیاده سازی سیستم ثبت سفارشات، به قسمت جالب این مدل می‌رسیم. قسمتی که در آن ارتباطات اشیاء و روابط one-to-many تعریف خواهند شد. تعاریف کلاس‌های OrderItem و OrderItemMapping را به صورت زیر در نظر بگیرید:

کلاس OrderItem تعریف شده در فایل جدید OrderItem.cs واقع شده در پوشه domain پروژه:
که در آن هر سفارش (order) دقیقا از یک محصول (product) تشکیل می‌شود و هر محصول می‌تواند در سفارشات متعدد و مختلفی درخواست شود.

namespace NHSample1.Domain
{
public class OrderItem
{
public int Id { get; set; }
public int Quantity { get; set; }
public Product Product { get; set; }
}
}
کلاس OrderItemMapping تعریف شده در فایل جدید OrderItemMapping.cs :

using FluentNHibernate.Mapping;
using NHSample1.Domain;

namespace NHSample1.Mappings
{
public class OrderItemMapping : ClassMap<OrderItem>
{
public OrderItemMapping()
{
Not.LazyLoad();
Id(oi => oi.Id).GeneratedBy.Assigned();
Map(oi => oi.Quantity).Not.Nullable();
References(oi => oi.Product).Not.Nullable();
}
}
}
نکته جدیدی که در این کلاس نگاشت مطرح شده است، واژه کلیدی References می‌باشد که جهت بیان این ارجاعات و وابستگی‌ها بکار می‌رود. این ارجاع بیانگر یک رابطه many-to-one بین سفارشات و محصولات است. همچنین در ادامه آن Not.Nullable ذکر شده است تا این ارجاع را اجباری نمائید (در غیر اینصورت سفارش غیر معتبر خواهد بود).
نکته‌ی دیگر مهم آن این مورد است که Id در اینجا به صورت یک کلید تعریف نشده است. یک آیتم سفارش داده شده، موجودیت به حساب نیامده و فقط یک شیء مقداری (value object) است و به خودی خود امکان وجود ندارد. هر وهله از آن تنها توسط یک سفارش قابل تعریف است. بنابراین id در اینجا فقط به عنوان یک index می‌تواند مورد استفاده قرار گیرد و فقط توسط شیء Order زمانیکه یک OrderItem به آن اضافه می‌شود، مقدار دهی خواهد شد.

اگر برای این نگاشت نیز آزمون واحد تهیه کنیم، به صورت زیر خواهد بود:

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

namespace UnitTests
{
[TestFixture]
public class OrderItemMapping_Fixture : FixtureBase
{
[Test]
public void can_correctly_map_order_item()
{
var product = new Product
{
Name = "Apples",
UnitPrice = 4.5m,
Discontinued = true
};

new PersistenceSpecification<OrderItem>(Session)
.CheckProperty(p => p.Id, 1)
.CheckProperty(p => p.Quantity, 5)
.CheckReference(p => p.Product, product)
.VerifyTheMappings();
}
}
}

مشکل! این آزمون واحد با شکست مواجه خواهد شد، زیرا هنوز مشخص نکرده‌ایم که دو شیء Product را که در قسمت CheckReference فوق برای این منظور معرفی کرده‌ایم، چگونه باید با هم مقایسه کرد. در مورد مقایسه نوع‌های اولیه و اصلی مانند int و string و امثال آن مشکلی نیست، اما باید منطق مقایسه سایر اشیاء سفارشی خود را با پیاده سازی اینترفیس IEqualityComparer دقیقا مشخص سازیم:

using System.Collections;
using NHSample1.Domain;

namespace UnitTests
{
public class CustomEqualityComparer : IEqualityComparer
{
public bool Equals(object x, object y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;

if (x is Product && y is Product)
return (x as Product).Id == (y as Product).Id;

if (x is Customer && y is Customer)
return (x as Customer).Id == (y as Customer).Id;

if (x is Employee && y is Employee)
return (x as Employee).Id == (y as Employee).Id;

if (x is OrderItem && y is OrderItem)
return (x as OrderItem).Id == (y as OrderItem).Id;


return x.Equals(y);
}

public int GetHashCode(object obj)
{
//شاید وقتی دیگر
return obj.GetHashCode();
}
}
}
در اینجا فقط Id این اشیاء با هم مقایسه شده است. در صورت نیاز تمامی خاصیت‌های این اشیاء را نیز می‌توان با هم مقایسه کرد (یک سری از اشیاء بکار گرفته شده در این کلاس در ادامه بحث معرفی خواهند شد).
سپس برای بکار گیری این کلاس جدید، سطر مربوط به استفاده از PersistenceSpecification به صورت زیر تغییر خواهد کرد:

new PersistenceSpecification<OrderItem>(Session, new CustomEqualityComparer())

پس از این تغییرات و مشخص سازی نحوه‌ی مقایسه دو شیء سفارشی، آزمون واحد ما پاس شده و خروجی SQL تولید شده آن به صورت زیر می‌باشد:

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 "Product" (Name, UnitPrice, Discontinued, Id) VALUES (@p0, @p1, @p2, @p3);@p0 = 'Apples', @p1 = 4.5, @p2 = True, @p3 = 1001
NHibernate: INSERT INTO "OrderItem" (Quantity, Product_id, Id) VALUES (@p0, @p1, @p2);@p0 = 5, @p1 = 1001, @p2 = 1
NHibernate: SELECT orderitem0_.Id as Id0_1_, orderitem0_.Quantity as Quantity0_1_, orderitem0_.Product_id as Product3_0_1_, product1_.Id as Id3_0_, product1_.Name as Name3_0_, product1_.UnitPrice as UnitPrice3_0_, product1_.Discontinued as Disconti4_3_0_ FROM "OrderItem" orderitem0_ inner join "Product" product1_ on orderitem0_.Product_id=product1_.Id WHERE orderitem0_.Id=@p0;@p0 = 1

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

using System;
using System.Collections.Generic;

namespace NHSample1.Domain
{
public class Order
{
public int Id { set; get; }
public DateTime OrderDate { get; set; }
public Employee Employee { get; set; }
public Customer Customer { get; set; }
public IList<OrderItem> OrderItems { get; set; }
}
}
نکته‌ی مهمی که در این کلاس وجود دارد استفاده از IList جهت معرفی مجموعه‌ای از آیتم‌های سفارشی است (بجای List و یا IEnumerable که در صورت استفاده خطای type cast exception در حین نگاشت حاصل می‌شد).

using NHSample1.Domain;
using FluentNHibernate.Mapping;

namespace NHSample1.Mappings
{
public class OrderMapping : ClassMap<Order>
{
public OrderMapping()
{
Not.LazyLoad();
Id(o => o.Id).GeneratedBy.GuidComb();
Map(o => o.OrderDate).Not.Nullable();
References(o => o.Employee).Not.Nullable();
References(o => o.Customer).Not.Nullable();
HasMany(o => o.OrderItems)
.AsList(index => index.Column("ListIndex").Type<int>());
}
}
}
در تعاریف نگاشت این کلاس نیز دو ارجاع به اشیاء کارمند و مشتری وجود دارد که با References مشخص شده‌اند.
قسمت جدید آن HasMany است که جهت تعریف رابطه one-to-many بکار گرفته شده است. یک سفارش رابطه many-to-one با یک مشتری و همچنین کارمندی که این رکورد را ثبت می‌کند، دارد. در اینجا مجموعه آیتم‌های یک سفارش به صورت یک لیست بازگشت داده می‌شود و ایندکس آن به ستونی به نام ListIndex در یک جدول دیتابیس نگاشت خواهد شد. نوع این ستون، int می‌باشد.

using System;
using System.Collections.Generic;
using NUnit.Framework;
using NHSample1.Domain;
using FluentNHibernate.Testing;

namespace UnitTests
{
[TestFixture]
public class OrderMapping_Fixture : FixtureBase
{
[Test]
public void can_correctly_map_an_order()
{
{
var product1 =
new Product
{
Name = "Apples",
UnitPrice = 4.5m,
Discontinued = true
};
var product2 =
new Product
{
Name = "Pears",
UnitPrice = 3.5m,
Discontinued = false
};

Session.Save(product1);
Session.Save(product2);

var items = new List<OrderItem>
{
new OrderItem
{
Id = 1,
Quantity = 100,
Product = product1
},
new OrderItem
{
Id = 2,
Quantity = 200,
Product = product2
}
};

var customer = new Customer
{
FirstName = "Vahid",
LastName = "Nasiri",
AddressLine1 = "Addr1",
AddressLine2 = "Addr2",
PostalCode = "1234",
City = "Tehran",
CountryCode = "IR"
};

var employee =
new Employee
{
FirstName = "name1",
LastName = "lname1"
};



var order = new Order
{
Customer = customer,
Employee = employee,
OrderDate = DateTime.Today,
OrderItems = items
};

new PersistenceSpecification<Order>(Session, new CustomEqualityComparer())
.CheckProperty(o => o.OrderDate, order.OrderDate)
.CheckReference(o => o.Customer, order.Customer)
.CheckReference(o => o.Employee, order.Employee)
.CheckList(o => o.OrderItems, order.OrderItems)
.VerifyTheMappings();
}
}
}
}
همانطور که ملاحظه می‌کنید در این متد آزمون واحد، نیاز به مشخص سازی منطق مقایسه اشیاء سفارش، مشتری و آیتم‌های سفارش داده شده نیز وجود دارد که پیشتر در کلاس CustomEqualityComparer معرفی شدند؛ درغیر اینصورت این آزمون واحد با شکست مواجه می‌شد.
متد آزمون واحد فوق کمی طولانی است؛ زیرا در آن باید تعاریف انواع و اقسام اشیاء مورد استفاده را مشخص نمود (و ارزش کار نیز دقیقا در همینجا مشخص می‌شود که بجای SQL نوشتن، با اشیایی که توسط کامپایلر تحت نظر هستند سر و کار داریم).
تنها نکته جدید آن استفاده از CheckList برای بررسی IList تعریف شده در قسمت قبل است.

خروجی SQL این آزمون واحد پس از اجرا و موفقیت آن به صورت زیر است:

OrderMapping_Fixture.can_correctly_map_an_order : Passed
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: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 3, @p1 = 2
NHibernate: INSERT INTO "Product" (Name, UnitPrice, Discontinued, Id) VALUES (@p0, @p1, @p2, @p3);@p0 = 'Apples', @p1 = 4.5, @p2 = True, @p3 = 1001
NHibernate: INSERT INTO "Product" (Name, UnitPrice, Discontinued, Id) VALUES (@p0, @p1, @p2, @p3);@p0 = 'Pears', @p1 = 3.5, @p2 = False, @p3 = 1002
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 = 2002
NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 4, @p1 = 3
NHibernate: INSERT INTO "Employee" (LastName, FirstName, Id) VALUES (@p0, @p1, @p2);@p0 = 'lname1', @p1 = 'name1', @p2 = 3003
NHibernate: INSERT INTO "OrderItem" (Quantity, Product_id, Id) VALUES (@p0, @p1, @p2);@p0 = 100, @p1 = 1001, @p2 = 1
NHibernate: INSERT INTO "OrderItem" (Quantity, Product_id, Id) VALUES (@p0, @p1, @p2);@p0 = 200, @p1 = 1002, @p2 = 2
NHibernate: INSERT INTO "Order" (OrderDate, Employee_id, Customer_id, Id) VALUES (@p0, @p1, @p2, @p3);@p0 = 2009/10/10 12:00:00 ق.ظ, @p1 = 3003, @p2 = 2002, @p3 = 0
NHibernate: UPDATE "OrderItem" SET Order_id = @p0, ListIndex = @p1 WHERE Id = @p2;@p0 = 0, @p1 = 0, @p2 = 1
NHibernate: UPDATE "OrderItem" SET Order_id = @p0, ListIndex = @p1 WHERE Id = @p2;@p0 = 0, @p1 = 1, @p2 = 2
NHibernate: SELECT order0_.Id as Id1_2_, order0_.OrderDate as OrderDate1_2_, order0_.Employee_id as Employee3_1_2_, order0_.Customer_id as Customer4_1_2_, employee1_.Id as Id4_0_, employee1_.LastName as LastName4_0_, employee1_.FirstName as FirstName4_0_, customer2_.Id as Id2_1_, customer2_.FirstName as FirstName2_1_, customer2_.LastName as LastName2_1_, customer2_.AddressLine1 as AddressL4_2_1_, customer2_.AddressLine2 as AddressL5_2_1_, customer2_.PostalCode as PostalCode2_1_, customer2_.City as City2_1_, customer2_.CountryCode as CountryC8_2_1_ FROM "Order" order0_ inner join "Employee" employee1_ on order0_.Employee_id=employee1_.Id inner join "Customer" customer2_ on order0_.Customer_id=customer2_.Id WHERE order0_.Id=@p0;@p0 = 0
NHibernate: SELECT orderitems0_.Order_id as Order4_2_, orderitems0_.Id as Id2_, orderitems0_.ListIndex as ListIndex2_, orderitems0_.Id as Id0_1_, orderitems0_.Quantity as Quantity0_1_, orderitems0_.Product_id as Product3_0_1_, product1_.Id as Id3_0_, product1_.Name as Name3_0_, product1_.UnitPrice as UnitPrice3_0_, product1_.Discontinued as Disconti4_3_0_ FROM "OrderItem" orderitems0_ inner join "Product" product1_ on orderitems0_.Product_id=product1_.Id WHERE orderitems0_.Order_id=@p0;@p0 = 0

تا اینجای کار تعاریف اشیاء ، نگاشت آن‌ها و همچنین بررسی صحت این نگاشت‌ها به پایان می‌رسد.

نکته:
دیتابیس برنامه را جهت آزمون‌های واحد برنامه، از نوع SQLite ساخته شده در حافظه مشخص کردیم. اگر علاقمند باشید که database schema تولید شده توسط NHibernate را مشاهده نمائید، در متد SetupContext کلاس FixtureBase که در قسمت قبل معرفی شد، سطر آخر را به صورت زیر تغییر دهید، تا اسکریپت دیتابیس نیز به صورت خودکار در خروجی اس کیوال آزمون واحد لحاظ شود (پارامتر دوم آن مشخص می‌کند که schema ساخته شده، نمایش داده شود یا خیر):

SessionSource.BuildSchema(Session, true);
پس از این تغییر و انجام مجدد آزمون واحد، اسکریپت دیتابیس ما به صورت زیر خواهد بود (که جهت ایجاد یک دیتابیس SQLite می‌تواند مورد استفاده قرار گیرد):

drop table if exists "OrderItem"

drop table if exists "Order"

drop table if exists "Customer"

drop table if exists "Product"

drop table if exists "Employee"

drop table if exists hibernate_unique_key

create table "OrderItem" (
Id INTEGER not null,
Quantity INTEGER not null,
Product_id INTEGER not null,
Order_id INTEGER,
ListIndex INTEGER,
primary key (Id)
)

create table "Order" (
Id INTEGER not null,
OrderDate DATETIME not null,
Employee_id INTEGER not null,
Customer_id INTEGER not null,
primary key (Id)
)

create table "Customer" (
Id INTEGER not null,
FirstName TEXT not null,
LastName TEXT not null,
AddressLine1 TEXT not null,
AddressLine2 TEXT,
PostalCode TEXT not null,
City TEXT not null,
CountryCode TEXT not null,
primary key (Id)
)

create table "Product" (
Id INTEGER not null,
Name TEXT not null,
UnitPrice NUMERIC not null,
Discontinued INTEGER not null,
primary key (Id)
)

create table "Employee" (
Id INTEGER not null,
LastName TEXT,
FirstName TEXT,
primary key (Id)
)

create table hibernate_unique_key (
next_hi INTEGER
)
البته اگر مستندات SQLite را مطالعه کرده باشید می‌دانید که مفهوم کلید خارجی در این دیتابیس وجود دارد اما اعمال نمی‌شود! (برای اعمال آن باید تریگر نوشت) به همین جهت در این اسکریپت تولیدی خبری از کلید خارجی نیست.

برای اینکه از دیتابیس اس کیوال سرور استفاده کنیم، در همان متد SetupContext کلاس مذکور، سطر اول را به صورت زیر تغییر دهید (نوع دیتابیس اس کیوال سرور 2008 مشخص شده و سپس رشته اتصالی به دیتابیس ذکر گردیده است):

var cfg = Fluently.Configure().Database(
// SQLiteConfiguration.Standard.ShowSql().InMemory
MsSqlConfiguration
.MsSql2008
.ShowSql()
.ConnectionString("Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true")
);

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

if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK3EF88858466CFBF7]') AND parent_object_id = OBJECT_ID('[OrderItem]'))
alter table [OrderItem] drop constraint FK3EF88858466CFBF7


if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK3EF888589F32DE52]') AND parent_object_id = OBJECT_ID('[OrderItem]'))
alter table [OrderItem] drop constraint FK3EF888589F32DE52


if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK3117099B1EBA72BC]') AND parent_object_id = OBJECT_ID('[Order]'))
alter table [Order] drop constraint FK3117099B1EBA72BC


if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK3117099BB2F9593A]') AND parent_object_id = OBJECT_ID('[Order]'))
alter table [Order] drop constraint FK3117099BB2F9593A


if exists (select * from dbo.sysobjects where id = object_id(N'[OrderItem]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [OrderItem]

if exists (select * from dbo.sysobjects where id = object_id(N'[Order]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [Order]

if exists (select * from dbo.sysobjects where id = object_id(N'[Customer]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [Customer]

if exists (select * from dbo.sysobjects where id = object_id(N'[Product]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [Product]

if exists (select * from dbo.sysobjects where id = object_id(N'[Employee]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [Employee]

if exists (select * from dbo.sysobjects where id = object_id(N'hibernate_unique_key') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table hibernate_unique_key

create table [OrderItem] (
Id INT not null,
Quantity INT not null,
Product_id INT not null,
Order_id INT null,
ListIndex INT null,
primary key (Id)
)

create table [Order] (
Id INT not null,
OrderDate DATETIME not null,
Employee_id INT not null,
Customer_id INT not null,
primary key (Id)
)

create table [Customer] (
Id INT not null,
FirstName NVARCHAR(50) not null,
LastName NVARCHAR(50) not null,
AddressLine1 NVARCHAR(50) not null,
AddressLine2 NVARCHAR(50) null,
PostalCode NVARCHAR(10) not null,
City NVARCHAR(50) not null,
CountryCode NVARCHAR(2) not null,
primary key (Id)
)

create table [Product] (
Id INT not null,
Name NVARCHAR(50) not null,
UnitPrice DECIMAL(19,5) not null,
Discontinued BIT not null,
primary key (Id)
)

create table [Employee] (
Id INT not null,
LastName NVARCHAR(50) null,
FirstName NVARCHAR(50) null,
primary key (Id)
)

alter table [OrderItem]
add constraint FK3EF88858466CFBF7
foreign key (Product_id)
references [Product]

alter table [OrderItem]
add constraint FK3EF888589F32DE52
foreign key (Order_id)
references [Order]

alter table [Order]
add constraint FK3117099B1EBA72BC
foreign key (Employee_id)
references [Employee]

alter table [Order]
add constraint FK3117099BB2F9593A
foreign key (Customer_id)
references [Customer]

create table hibernate_unique_key (
next_hi INT
)
که نکات ذیل در مورد آن جالب توجه است:
الف) جداول مطابق نام کلاس‌های ما تولید شده‌اند.
ب) نام فیلدها دقیقا مطابق نام خواص کلاس‌های ما تشکیل شده‌اند.
ج) Id ها به صورت primary key تعریف شده‌اند (از آنجائیکه ما در هنگام تعریف نگاشت‌ها، آن‌ها را از نوع identity مشخص کرده بودیم).
د) رشته‌ها به نوع nvarchar با اندازه 50 نگاشت شده‌اند.
ه) کلیدهای خارجی بر اساس نام جدول با پسوند _id تشکیل شده‌اند.




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


مطالب
عدم کاهش حجم لاگ فایل SQL Server

در مورد روش‌های کاهش حجم لاگ فایل‌های SQL Server در این مطلب بحث شد.
اما یکی از دیتابیس‌های قدیمی shrink نمی‌شد و پیغام خطای زیر را صادر می‌کرد:

Cannot shrink log file 2 because of minimum log space required.

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

DBCC OPENTRAN
که نتیجه به صورت زیر بود:

Replicated Transaction Information:
Oldest distributed LSN : (0:0:0)
Oldest non-distributed LSN : (5291:25:1)

وجود سطر مربوط به Oldest non-distributed LSN به این معنا است که هنوز یک replication نا تمام بر روی این دیتابیس موجود است. البته چون این دیتابیس از یک سرور دیگر به اینجا منتقل شده بود و هیچ نوع replication ایی هم در این سرور بر روی آن تنظیم نشده بود؛ بنابراین ابتدا این replication حذف شد:
exec sp_removedbreplication 'dbName', 'both';

سپس مجددا دستور زیر جهت مشاهده‌ی وضعیت تراکنش‌های ناتمام صادر شد:
DBCC OPENTRAN

که این‌بار دیگر هیچ خروجی نداشت.
اکنون با استفاده از روش ذکر شده، لاگ فایل 70 گیگابایتی این دیتابیس به سادگی به چند مگابایت shrink شد.

بازخوردهای دوره
تنظیمات امنیتی دسترسی به سرور RavenDB
- می‌شود پورت‌های دسترسی خارجی به یک سرور را با فایروال بست. به این ترتیب فقط برنامه نصب شده در آن سرور امکان اتصال را خواهد داشت (خیلی‌ها با SQL Server هم به همین نحو کار می‌کنند؛ یک برنامه وب و یک برنامه سرور SQL دارند روی یک سرور. برنامه وب سفارشی، لایه اتصال امن به بانک اطلاعاتی است).
- همچنین حالت نصب embedded آن دسترسی از بیرون ندارد و فقط از طریق برنامه قابل استفاده است. 
بازخوردهای دوره
کار با RavenDB از طریق REST API آن
- بله.
- البته می‌شود پورت‌های دسترسی خارجی به یک سرور را با فایروال بست. به این ترتیب فقط برنامه نصب شده در آن سرور امکان اتصال را خواهد داشت (خیلی‌ها با SQL Server هم به همین نحو کار می‌کنند؛ یک برنامه وب و یک برنامه سرور SQL دارند روی یک سرور. برنامه وب سفارشی، لایه اتصال امن به بانک اطلاعاتی است).
- همچنین حالت نصب embedded آن دسترسی از بیرون ندارد و فقط از طریق برنامه قابل استفاده است.