نظرات نظرسنجی‌ها
اگر بخواهید کنار دات نت بر روی یک پلتفرم یا زبان دیگری نیز کار کنید کدام را انتخاب می کنید؟
من که میگم زبان C++. از هر جهت عالیه. 
فقط حوصله و وقت میخوادبشینی کار کنی. هرپروژه ای هم میشه باهاش نوشت (اندروید، وب ، ویندوز، پردازش تصویر و یادگیری ماشین و پروگرام میکروکنترل‌ها و IC هاو...)
نظرات مطالب
پیاده سازی Option یا Maybe در #C
سلام. ممنون از مطلب مفید شما.
شاید علت اینکه option در #C وجود نداره اینه که متد ()<Enumerable.Empty<TResult وجود داره و نیازی به این گزینه نیست. ولی در کل، هم فکر کردن در مورد NRE و هم آشنایی بیشتر با زبان #F مفید است. البته با توجه به اینکه این مقاله قبلا نوشته شده، باید بگم الان کامپایلر #C بسیار قویتر شده و تا حد زیادی جاهایی که احتمال وقوع این exception هست رو به شما خبر میده.  
نظرات مطالب
شروع کار با Apache Cordova در ویژوال استودیو #3
با تشکر از مطلب آموزنده شما
Xamarin و Cordova رو نمیشه با هم مقایسه کرد
Xamarin از زبان #C برای موبایل استفاده میکنه که البته زحمت کامپایل Mono بر عهده داره(Mono یک کاماپلر C# برای لینوکس  و MonoToch برای IOS). و البته پولی
Cordova  با استفاده از Javascript و Html5 اینکارو انجا میده
مطالب
رویه های ذخیره شده خوب یا بد؟!

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

Stored Procedure (که از این به بعد برای ایجاز، SP نوشته خواهد شد) هم از قاعده فوق مستثنی نیست و در صورت انتخاب صحیح میتواند به ارائه‌ی محصول نهایی با کیفیت‌تری در زمان کوتاه‌تری کمک کند و در صورت انتخاب ناآگاهانه ممکن است باعث شکست یک پروژه (بخصوص در بلند مدت) شود.


تاریخچه

SQL توسط شرکت IBM در اوایل دهه 70 میلادی ایجاد شد. با اوج گرفتن زبان‌های رویه‌ای، SQL هم چندان از این قافله عقب نماند که منجر به پذیرش SP به عنوان یک استاندارد، در دهه 90 میلادی و پیاده سازی تدریجی آن توسط غول‌های سازنده دیتابیس شد (رجوع فرمایید به ^ و ^). این فاصله 20 ساله باعث غنی‌تر شدن SQL شد و وجود SP - به معنی انتقال مدل برنامه نویسی رویه‌ای به SQL - بخشی از مشکلات قبلی کار با کوئری‌های پشت سر هم و خام را حل کرد. از سال 2000 میلادی به بعد، ORM‌های قدرتمندی از جمله  Hibernate  و پیاده سازی‌های مختلفی از Active Record  و Entity Framework متولد شدند. بنابر این تقدم و تاخّرهای زمانی، بدیهی است اغلب مزایای SP نسبت به Raw SQL Query و اغلب معایب آن نسبت به ORM‌ها باشد. 

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


معایب SP

- دستورات Alter Table ، Add Column و Drop Column  به این سادگی‌ها هم نیستند؛ ممکن است به یکی از جداول دیتابیس دو ستون اضافه یا از آن حذف شوند. مجبوریم تمامی SP‌ها را بخصوص Insert و Update متناظر با جدول را تغییر دهیم که این تغییرات ممکن است بصورت زنجیره‌وار به سایر SP‌ها هم سرایت کند. حال شرایطی را در نظر بگیرید که تعداد SP‌های شما به چند ده و یا حتی به چند صد عدد و بیشتر، رسیده باشد که این به معنی زحمت بیشتر و تغییرات پر هزینه‌تر است.

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

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

- انعطاف پذیری کمتر؛ در یک شرایط ایده آل، عملکرد اپلیکیشن، مستقل از دیتابیس است. اگر نیاز به تغییر دیتابیس، مثلا از اوراکل به Microsoft SQL Server وجود داشته باشد، نیاز به بازنویسی و انتقال فانکشن‌ها و SP ها محتمل است و از آنجائیکه که با وجود استانداردها، دیتابیس‌های مختلف، معمولا در Syntax دستورات، تفاوت‌های فاحشی دارند، هر چه کد بیشتری در SP ها باشد، نیاز به انتقال و تبدیل بیشتری وجود دارد. 

- عدم وجود بازخورد مناسب؛ بسیاری از اوقات در صورت بروز اشکالی در حین اجرای یک SP، فقط با یک متن ساده بصورت Table has no rows   و یا  error مواجه میشویم. چنین خطاهایی هنگام دیباگ اصلا خوشایند نیستند. MS SQL در این بین بازخورد‌های مناسبی را ارائه میکند. اگر تجربه کار با سایر دیتابیس‌ها را داشته باشید، اهمیت بازخورد‌های مناسب، ملموس‌تر خواهد بود.

- کد نویسی سخت‌تر؛ نوشتن کد SQL  معمولا در همان IDE  اپلیکیشن انجام نمیشود. جابجایی مداوم بین دو IDE ، دیباگ و کد نویسی از طریق دو اینترفیس مجزا، اصلا ایده‌ال نیست. 

- SP  منطق را بیش از حد پنهان میکند؛ حتی با دانستن نام صحیح یک SP، باز هم تصویری از پارامتر‌های ارسالی به آن و نتیجه برگشتی نخواهیم داشت. نمیدانیم نتیجه حاصل از اجرای SP ما مقداری را برمیگرداند یا خیر؟ در صورت وجود برگشتی، یک Cursor است یا یک مقدار؟ اگر Cursor است شامل چه ستون‌هایی است؟

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

INSERT INTO User_Table(Id,Username,Password,FirstName,SureName,PhoneNumber,x,Email)
VALUES (1,'VahidN','123456','Vahid','Nasiri','09120000000','vahid_xxx@example.com')

و معادل آن در یک ORM  فرضی:

public void Insert(User user)
{
  _users.Insert(user);
  db.Save();
}

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

- نبود Query Chaining؛ یکی از ویژگی‌های جذاب ORM‌‌های امروزی، امکان تشکیل یک کوئری با قابلیت خوانایی بالا و افزودن شرط‌های بیشتر از طریق  الگوی builder است. قطعه کد زیر یک SP برای جستجوی داینامیک نام و نام خانوادگی در یک جدول فرضی به اسم Users است:

public ICollection<User> GetUsers(string firstName,string lastName,Func<User, bool> orderBy)
{
    var query = _users.where(u => u.LastName.StartsWith(lastName));
    query = query.where(u => u.FirstName.StartsWith(firstName));
    query = query.OrderBy(orderBy);
    return  query.ToList();
}

در مقایسه با معادل SP آن:

CREATE PROCEDURE DynamicWhere 
    @LastName varchar(50) = null,
    @FirstName varchar(50) = null,
    @Orderby varchar(50) = null
AS
BEGIN
    DECLARE @where nvarchar(max)
    SELECT @where = '1 = 1'
 
    IF @LastName IS NOT NULL
        SELECT @Where = @Where + " AND A.LastName LIKE @LastName + '%'"
 
    IF @FirstName IS NOT NULL
        SELECT @Where = @Where + " AND A.FirstName LIKE @FirstName + '%'"
 
    DECLARE @orderBySql nvarchar(max)
    SELECT @orderBySql = CASE
        WHEN @OrderBy = "LastName" THEN "A.LastName"
        ELSE @OrderBy = "FirstName" THEN "A.FirstName"
    END
 
    DECLARE @sql nvarchar(max)
    SELECT @sql = "
    SELECT A.Id , A.AccountNoId, A.LastName, A.FirstName, A.PostingDt, 
    A.BillingAmount
    FROM Users 
    WHERE " + @where + " 
    ORDER BY " + @orderBySql
 
    exec sp_executesql @sql,  N'@LastName varchar(50), @FirstName varchar(50)
        @LastName, @FirstName
END

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

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

  • شیءگرایی و امکانات آن که در SP موجود نیست و در مورد قبلی معایب، به آن مختصرا اشاره شد. در نظر بگیرید اگر SQL زبانی شیء گرا بود و مجهز به ارث بری و کپسوله سازی بود، چقدر قابلیت نگهداری آن بالاتر میرفت و حجم کد‌های نوشته شده میتوانست کمتر باشند.
  • نداشتن Lazy Loading که باعث مصرف زیاد حافظه میشود.
  • نداشتن intellisense حین فراخوانی‌ها.
  • نداشتن Navigation Property که باعث join نویسی‌های زیاد خواهد شد.
  • SQL در مقایسه با یک زبان مدرن ناقص بنظر میرسد و این نوشتن کد آن را سخت‌تر میکند.‌
  • نداشتن امکان تغییر منطقی نام جداول و ستون ها
  • مدیریت تراکنش‌ها بصورت دستی، حال آنکه با الگوی Unit Of Work  این مشکل در یک ORM قدرتمند مثل EF حل شده است.


- زمان بر بودن نوشتن SP؛ گاهی نوشتن یک تابع در یک ORM یا بعضا نوشتن یک کوئری SQL کوتاه در یک رشته متنی، ساده‌تر از نوشتن کد SP است. آیا برای هر وظیفه کوچک در دیتابیس، نوشتن یک SP ضروری است؟


مزایای SP :

- کمتر کردن Round Trips در شبکه و متعاقبا کاهش ترافیک شبکه؛ اگر از یک فراخوانی استفاده کنیم، کاهش Round Trip‌ها تاثیر چندانی نخواهد داشت. همچنین ارسال یک کوئری کامل، نسبت به ارسال فقط اسم SP و پارامتر‌های آن، پهنای باند بیشتری اِشغال میکند. البته در یک شبکه با سرعت قابل قبول، بعید است این دو مزیت محسوس باشند؛ اما به هر حال برای موارد خاص، دو مزیت محسوب میشوند. نکته دیگر آنکه بدلیل Pre-Compiled بودن SP‌ها و همچنین کَش شدن Execution Plan آنها، اندکی با سرعت بالاتری اجرا میشوند.

- امکان چک کردن سینتکس قبل از اجرای آن؛ در مقایسه با Raw Query مزیت محسوب میشود.

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

- کمک به ایجاد یک پَک؛ در یک زیر سیستم با نیازمندی مشخص که اعمال تغییرات در آن محتمل نمیباشد نیز SP میتواند یک گزینه مناسب به حساب آید. مثلا یک سیستم Membership را در نظر بگیرید که در پروژه‌های مختلف شما مورد استفاده قرار خواهد گرفت. برای مثال میشود یک سیستم Membership  سفارشی را با امکان  Hash  پسورد و  رمز کردن داده‌های حساس،  به کمک SP و Function ‌های مناسب فراهم کرد و در واقع بین Application Login  و Data Logic تمایز قائل شد. شخصا معماری Restful را به این روش هم ترجیح میدهم. 

بهرمند شدن از امکانات بومی SQL ؛ به عنوان نمونه برای ترانهاده کردن خروجی یک کوئری میتوان از فانکشن  Pivot  استفاده کرد. یا فانکشن‌های تحلیلی  Lead  و  Lag  (لینک مستندات اوراکل این دو فانکشن به ترتیب در ^ و ^ ) که بنظر نمیرسد هنوز معادل مستقیمی درORM  ها  داشته باشند. 

تسلط و کنترل بیشتر و دقیقتر بر کوئری نهایی؛ گفته میشود SP و عبارات SQL در دیتابیس، حکم assembly را در سایر زبان‌ها دارند. بنابراین با SP میتوان عبارات SQL و نحوه اجرای آن را در دیتابیس، بطور کامل تحت فرمان داشت. این در حالی است که هر یک از ORM‌ها دستورات زبان برنامه نویسی مبداء را به یک عبارت SQL ترجمه میکنند که این عبارت چندان تحت کنترل برنامه نویس نیست و بیشتر به مدل کاری ORM بستگی دارد. 

امکان join بین دو یا چند دیتابیس مجزا؛ حال آنکه امکان join بین دو Context در ORM ‌ها وجود ندارد. بعلاوه اگر دو دیتابیس مدنظر ما روی دو سرور مجزا باشند، با SP و  کانفیگ Linked Server  کماکان میشود کوئری join  دار نوشت.

برای عملیات‌های Batch مناسب‌تر است؛ در مقام مقایسه با ORM ‌ها که با تکنیک‌های مختلفی سعی در افزایش سرعت عملیات Batch، بخصوص Insert و Update را دارند، SP  با سرعت قابل قبول‌تری اجرا میشود.

عدم نیاز به یادگیری سینتکس و ابزاری جدید؛ موارد بسیاری وجود دارند که فرصت یادگیری تکنولوژی جدیدی مثل یک ORM و یا SQL Bulk و حتی کتابخانه‌های ثالث مبتنی بر این ابزارها  وجود ندارند و ممکن است مجبور شوید برای باقی ماندن در بازار رقابتی، از دانسته‌های قبلی خود استفاده کنید .

تخصصی‌تر کردن وظایف؛ برنامه نویس‌های دیتابیس به صورت تخصصی اقدام به تحلیل روابط و ایندکس‌ها میکنند، دیتابیس را ایجاد و نرمال سازی مینمایند، SP های متناسب را میسازند و به بهترین شکل Optimize و در آخر تست میکنند.

- امنیت به نسبت بالاتر؛ میتوان مجوز اجرای SP را به یک کاربر اعطا کرد، بدون آنکه مجوز دسترسی به جداول مورد استفاده در آن SP را داد. همچنین نسبت به کوئری‌های پارامتری نشده، SQL ارجیحت دارند چون احتمال آسیب پذیری در مقابل SQL Injection را کمتر میکنند.


نتیجه‌گیری

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

مطالب
QueryOver در NHibernate و تفاوت‌های آن با LINQ to NH

در NHibernate چندین و چند روش، جهت تهیه کوئری‌ها وجود دارد که QueryOver یکی از آن‌ها است (+). QueryOver نسبت به LINQ to NH سازگاری بهتری با ساز و کار درونی NHibernate دارد؛ برای مثال امکان یکپارچگی آن با سطح دوم کش. هر چند ظاهر QueryOver با LINQ یکی است، اما در عمل متفاوتند و راه و روش خاص خودش را طلب می‌کند. برای مثال در LINQ to NH می‌تواند نوشت x.Property.Contains اما در QueryOver متدی به نام contains قابل استفاده نیست (هر چند در Intellisense ظاهر می‌شود اما عملا تعریف نشده است و نباید آن‌را با LINQ اشتباه گرفت) و سعی در استفاده از آن‌ها به استثناهای زیر ختم می‌شوند:
Unrecognised method call: System.String:Boolean StartsWith(System.String)
Unrecognised method call: System.String:Boolean Contains(System.String)
برای مثال کلاس زیر را در نظر بگیرید؛ کوئری‌های مطلب جاری بر این اساس تهیه خواهند شد:
using NHibernate.Validator.Constraints;

namespace NH3Test.MappingDefinitions.Domain
{
public class Account
{
public virtual int Id { get; set; }

[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام باید بین 3 و 120 کاراکتر باشد")]
public virtual string Name { get; set; }

[NotNull]
public virtual int Balance { set; get; }
}
}

1) یافتن رکوردهایی که در یک مجموعه‌ی مشخص قرار دارند. برای مثال balance آن‌ها مساوی 10 و 12 است:
var list = new[]  { 12,10};
var resultList = session.QueryOver<Account>()
.WhereRestrictionOn(p => p.Balance)
.IsIn(list)
.List();

SELECT
this_.AccountId as AccountId0_0_,
this_.Name as Name0_0_,
this_.Balance as Balance0_0_
FROM
Accounts this_
WHERE
this_.Balance in (
@p0 /* = 10 */, @p1 /* = 12 */
)

2) پیاده سازی همان متد Contains ذکر شده، در QueryOver:
var accountsContianX = session.QueryOver<Account>()
.WhereRestrictionOn(x => x.Name)
.IsLike("X", NHibernate.Criterion.MatchMode.Anywhere)
.List();

SELECT
this_.AccountId as AccountId0_0_,
this_.Name as Name0_0_,
this_.Balance as Balance0_0_
FROM
Accounts this_
WHERE
this_.Name like @p0 /* = %X% */

در اینجا بر اساس مقادیر مختلف MatchMode می‌توان متدهای StartsWith (MatchMode.Start) ، EndsWith (MatchMode.End) ، Equals (MatchMode.Exact) را نیز تهیه نمود.

انجام مثال دوم راه ساده‌تری نیز دارد. قسمت WhereRestrictionOn و IsLike به صورت یک سری extension متد ویژه در فضای نام NHibernate.Criterion تعریف شده‌اند. ابتدا این فضای نام را به کلاس جاری افزوده و سپس می‌توان نوشت :
using NHibernate.Criterion;
...
var accountsContianX = session.QueryOver<Account>()
.Where(x => x.Name.IsLike("%X%"))
.List();

این فضای نام شامل چهار extension method به نام‌های IsLike ، IsInsensitiveLike ، IsIn و IsBetween است.


چگونه extension method سفارشی خود را تهیه کنیم؟

بهترین کار این است که به سورس NHibernate ، فایل‌های RestrictionsExtensions.cs و ExpressionProcessor.cs که تعاریف متد IsLike در آن‌ها وجود دارد مراجعه کرد. در اینجا می‌توان با نحوه‌ی تعریف و سپس ثبت آن در رجیستری extension methods مرتبط با QueryOver توسط متد عمومی RegisterCustomMethodCall آشنا شد. در ادامه سه کار را می‌توان انجام داد:
-متد مورد نظر را در کدهای خود (نه کدهای اصلی NH) اضافه کرده و سپس با فراخوانی RegisterCustomMethodCall آن‌را قابل استفاده نمائید.
-متد خود را به سورس اصلی NH اضافه کرده و کامپایل کنید.
-متد خود را به سورس اصلی NH اضافه کرده و کامپایل کنید (بهتر است همان روش نامگذاری بکار گرفته شده در فایل‌های ذکر شده رعایت شود). یک تست هم برای آن بنویسید (تست نویسی هم یک سری اصولی دارد (+)). سپس یک patch از آن روی آن ساخته (+) و برای تیم NH ارسال نمائید (تا جایی که دقت کردم از کلیه ارسال‌هایی که آزمون واحد نداشته باشند، صرفنظر می‌شود).

مثال:
می‌خواهیم extension متد جدیدی به نام Year را به QueryOver اضافه کنیم. این متد را هم بر اساس توابع توکار بانک‌های اطلاعاتی، تهیه خواهیم نمود. لیست کامل این نوع متدهای بومی SQL را در فایل Dialect.cs سورس‌های NH می‌توان یافت (البته به صورت پیش فرض از متد extract برای جداسازی قسمت‌های مختلف تاریخ استفاده می‌کند. این متد در فایل‌های Dialect مربوط به بانک‌های اطلاعاتی مختلف، متفاوت است و برحسب بانک اطلاعاتی جاری به صورت خودکار تغییر خواهد کرد).
using System;
using System.Linq.Expressions;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Impl;

namespace NH3Test.ConsoleApplication
{
public static class MyQueryOverExts
{
public static bool YearIs(this DateTime projection, int year)
{
throw new Exception("Not to be used directly - use inside QueryOver expression");
}

public static ICriterion ProcessAnsiYear(MethodCallExpression methodCallExpression)
{
string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
object value = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
return Restrictions.Eq(
Projections.SqlFunction("year", NHibernateUtil.DateTime, Projections.Property(property)),
value);
}
}

public class QueryOverExtsRegistry
{
public static void RegistrMyQueryOverExts()
{
ExpressionProcessor.RegisterCustomMethodCall(
() => MyQueryOverExts.YearIs(DateTime.Now, 0),
MyQueryOverExts.ProcessAnsiYear);
}
}
}

اکنون برای استفاده خواهیم داشت:
QueryOverExtsRegistry.RegistrMyQueryOverExts(); //یکبار در ابتدای اجرای برنامه باید ثبت شود
...
var data = session.QueryOver<Account>()
.Where(x => x.AddDate.YearIs(2010))
.List();

برای مثال اگر بانک اطلاعاتی انتخابی از نوع SQLite باشد، خروجی SQL مرتبط به شکل زیر خواهد بود:
SELECT
this_.AccountId as AccountId0_0_,
this_.Name as Name0_0_,
this_.Balance as Balance0_0_,
this_.AddDate as AddDate0_0_
FROM
Accounts this_
WHERE
strftime("%Y", this_.AddDate) = @p0 /* =2010 */


هر چند ما تابع year را در متد ProcessAnsiYear ثبت کرده‌ایم اما بر اساس فایل SQLiteDialect.cs ، تعاریف مرتبط و مخصوص این بانک اطلاعاتی (مانند متد strftime فوق) به صورت خودکار دریافت می‌گردد و کد ما مستقل از نوع بانک اطلاعاتی خواهد بود.


نکته جالب!
LINQ to NH هم قابل بسط است؛ کاری که در ORM های دیگر به این سادگی نیست. چند مثال در این زمینه:
چگونه تابع سفارشی SQL Server خود را به صورت یک extension method تعریف و استفاده کنیم: (+) ، یک نمونه دیگر: (+) و نمونه‌ای دیگر: (+).

مطالب
معماری module based در TypeScript قسمت اول
در صورت استفاده از TypeScript، قطعا با module‌ها و هدف استفاده‌ی از آن‌ها آشنایی دارید. در این مقاله می‌خواهیم با متداول‌ترین روش‌های بسته بندی آن‌ها آشنا شده و به صورت عملیاتی آن را پیاده نماییم. اولین روش commonjs میباشد. از آنجایی که این روش بیشتر برای برنامه‌های خارج از مرورگر میباشد، به همین قدر معرفی آن بسنده میکنیم. اما دو روش مهم دیگری که در typeScript برای ماژول‌ها اهمیت فراوانی دارند:

1) AMD یا Asynchronous Module Definition
2) در این روش از امکانات توکار کامپایلر typescript برای combine کردن فایل‌ها استفاده میشود

ابتدا AMD را بررسی مینماییم
این روش بدین صورت عمل میکند که هر ماژول، در صورتیکه بطور صریح نیاز به ماژول دیگری داشته باشد، آن را import کرده و به صورت async آن ماژول درخواستی را دریافت مینماید. بهترین ابزار برای انجام اینکار requirejs میباشد و در وبسایت رسمی آن، خودش را اینگونه معرفی کرده است:
RequireJS is a JavaScript file and module loader ، ما عملیات load کردن فایل‌ها را به requirejs واگذار میکنیم.
حال میخواهیم به پیاده سازی AMD با استفاده از typescript و البته requirejs بپردازیم.
بنده برای اینکار از IDE محبوب خود visual studio استفاده خواهم کرد. قطعا شما از IDE/Text editor مورد نظر خود میتوانید استفاده نمایید (البته با کمی تغییرات جزیی در محتوای آموزشی این مقاله).
ابتدا یک پروژه‌ی asp.net از نوع empty را بدون هیچ وابستگی ایجاد کرده و index.html را بدان اضافه مینماییم:

نام پروژه‌ی من AMD و فایل index.html بدان اضافه شده است. فرض کنید یک پوشه‌ی جدید را به نام modules به آن اضافه میکنیم و در آن دو فایل typescript ی را به نام‌های module1.ts و module2.ts، اضافه میکنیم.

محتویات module1 را اینگونه مینویسیم:

export module module1 {
    export abstract class firstCls {
        static f1(str: string) {
            console.log(str);
        }
    }
}

و همچنین module2 به شکل زیر خواهد بود:

import * as Amd from 'module1';

module module2 {
    export class secondCls {
        f2(str: string) {
            Amd.module1.firstCls.f1(str);
        }
    }
}

new module2.secondCls().f2(`amd work's`);

(دقت کنید بعد از کامپایل شدن لفظ import تبدیل به define میشود)

از طریق add - new item فایل tsconfig.json را به مسیر اصلی پروژه اضافه کنید. در صورتی که آن را پیدا نکردید، به صورت دستی فایل آن را ساخته و محتویات زیر را در آن کپی نمایید:

{
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": false,
    "target": "es6",
    "module": "amd"
  },
  "exclude": [
    "node_modules"
  ]
}
exclude مسیرهایی هستند که قرار نیست کامپایل شوند. target نوع کد تولید شده را مشخص مینماید (در صورتیکه مرورگر شما از es6 پشتیبانی نمیکند، میتوانید target را به es5 تغییر دهید) و module نیز از نوع amd تعریف میشود. کاری که قرار است انجام دهیم این است که به محض فراخوانی module2 به صورت async ، ماژول module1 را load کرده تا بتوان از آن بهره برد. متاسفانه در حال حاضر هیچ مرورگری به صورت توکار این ویژگی را پشتیبانی نمیکند! به همین دلیل فعلا مجبور به استفاده از ابزار‌های third party همچون requirejs هستیم.

در ادامه به root پروژه رفته و دستور npm init را ارسال کرده تا فایل package.json تولید شود. همچنین برای requirejs نیز دستور زیر را ارسال مینماییم:

npm install requirejs --save-dev

حال requirejs به پروژه‌ی شما اضافه شده است.


برای مدیریت کردن فراخوانی initial module در پوشه‌ی modules که قبلا ساخته بودیم فایل main.js راساخته و کد‌های زیر را بدان اضافه مینماییم.

(لازم به ذکر است این فایل را میتوانیم با استفاده از typescript نوشته و  requirejs definitely typed  را به پروژه اضافه کرده و از مزایای intellisense بودن بهره ببریم)

کد‌های زیر را درون main.js مینویسیم:

require(['modules/module2.js'], modules_module2());

function modules_module2() {
    //additionals config goes here
}

از آنجاییکه ممکن است تعداد وابستگی فایل‌ها زیاد باشد و ترتیب load شدن آن‌ها نیز اهمیت داشته باشد، در این قسمت میتوان configهای بیشتری را همچون sequence در load کردن فایل‌ها، تعریف کرد که میتوانید در وبسایت رسمی requirejs آن را مطالعه بفرمایید.


حال فایل index.html را باز کرده و config برای فراخوانی requirejs, main.js را مینویسیم؛ به صورت زیر:

<h1>Hello requirejs and amd modules</h1>

<!--src means require js address-->
<!--data-main means initial require config-->

<script src="node_modules/requirejs/require.js" data-main="modules/main.js"></script>

<script></script>

پر واضح است که src آدرس فایل require.js و همچنین data-main آدرس initial require config پروژه را مشخص می‌کند.

اکنون پروژه را run کرده و می‌بینید که فایل‌های مورد نیاز به صورت async برای ما load میشوند. اگر از مرورگر کروم استفاده می‌نمایید، بدین صورت میتوانید network و همچنین console را مشاهد نمایید:


مشاهد میکنید که فایل‌های مورد نیاز load شده‌اند و همچنین amd work's در console نمایش داده شده است.

requirejs بدین صورت عمل میکند: بعد از یافتن هر فایل، با استفاده از regex کل فایل را بررسی کرده و به دنبال وابستگی‌های آن فایل میگردد (منظور همان import‌ها میباشد و آن فایل به صورت async لود میشود) و در فایل‌های بعدی نیز همین روال ادامه خواهد یافت. هر چند راهکارهایی برای بهبود کارآیی در آن اندیشیده شده است؛ بدین صورت که اساس کارش با استفاده از singleton میباشد و بعد از require کردن فایلی، هر دفعه که فراخوانی میشود، نیاز به پردازش مجدد ندارد. با این وجود ممکن است در بعضی مواقع و مخصوصا با اشتباهات سهوی برنامه نویسان از کارآیی نرم افزار مطبوع شما بکاهد.


کد‌های این برنامه را میتوانید از اینجا دریافت نمایید (ضمن اینکه وابستگی‌های اضافه‌تری مانند پوشه‌ی node_modules حذف شده‌اند؛ بنابراین npm install فراموش نشود)

دانلود AMD.zip  


در قسمت بعد به امکانات توکار کامپایلر typescript برای معماری ماژول‌ها میپردازیم

نظرات مطالب
بررسی تغییرات Blazor 8x - قسمت چهارم - معرفی فرم‌های جدید تعاملی

یک نکته‌ی تکمیلی: نحوه‌ی نامگذاری ویژه‌ی عناصر در فرم‌های جدید Blazor SSR

اگر با نگارش‌های دیگر Blazor کار کرده باشید، عموما یک EditForm را به صفحه اضافه کرده و چند المان را به آن اضافه می‌کنیم و ... کار می‌کند. حتی اگر کامپوننت‌های سفارشی را هم بر این مبنا تهیه کنیم ... بازهم بدون نکته‌ی خاصی کار می‌کنند. اما ... در برنامه‌های Blazor SSR اینطور نیست! زمانیکه برای مثال مدل فرم را به این صورت تعریف می‌کنیم:

[SupplyParameterFromForm]
public OrderPlace? MyModel { get; set; }

و آن‌را به نحو متداولی در صفحه نمایش می‌دهیم:

<InputText @bind-Value="MyModel.City"/>

اگر به المان رندر شده‌ی در مرورگر مراجعه کنیم، ویژگی name حاصل، با MyModel.City مقدار دهی شده‌است و ... این موضوع درج نام خاصیت مدل (و یا اصطلاحا Html Field Prefix)، برای Blazor SSR بسیار مهم است! تاحدی که اگر از آن آگاه نباشید، ممکن است ساعتی را مشغول دیباگ برنامه شوید که چرا، مقدار نالی را دریافت کرده‌اید و یا عناصر تعریف شده‌ی در کامپوننت‌های سفارشی، کار نمی‌کنند و مقدار نمی‌گیرند!

متاسفانه API بازگشت نام کامل عناصری که توسط Blazor SSR تولید می‌شود، عمومی نیست و internal است. اگر از کامپوننت‌های استاندارد خود Blazor استفاده می‌کنید، نیازی نیست تا به این موضوع فکر کنید و مدیریت آن خودکار است؛ اما همینکه قصد تولید کامپوننت‌های سفارشی مخصوص SSR را داشته باشید، اولین مشکلی را که با آن مواجه خواهید شد، دقیقا همین مساله‌ی تولید صحیح HtmlFieldPrefix‌ها است.

برای رفع این مشکل و دسترسی به API پشت صحنه‌ی تولید نام فیلدها در Blazor SSR، می‌توان از کامپوننت پایه‌ی InputBase خود Blazor ارث‌بری کرد و به این ترتیب به خاصیت جدید NameAttributeValue آن دسترسی یافت (این خاصیت به دات‌نت 8 و مخصوص Blazor SSR، اضافه شده‌است) که اینکار در کلاس BlazorHtmlField انجام شده‌است. روش استفاده‌ی از آن هم به صورت زیر است:

private BlazorHtmlField<T?> ValueField
        => new(ValueExpression ?? throw new InvalidOperationException(message: "Please use @bind-Value here."));

[Parameter] public T? Value { set; get; }

[Parameter] public EventCallback<T?> ValueChanged { get; set; }

[Parameter] public Expression<Func<T?>> ValueExpression { get; set; } = default!;

زمانیکه می‌خواهیم در یک کامپوننت سفارشی، خاصیتی bind پذیر را طراحی کنیم، روش کار آن، مانند مثال فوق است که به همراه یک خاصیت، یک EventCallback و یک Expression است تا اعتبارسنجی و انقیاد دوطرفه را فعال کند. اما ... اگر همین Value را مستقیما در فیلدهای کامپوننت استفاده کنیم ... مقدار نمی‌گیرد؛ چون به همراه نام کامل خاصیت بایند شده‌ی به آن نیست. برای مثال بجای MyModel.City فقط City درج می‌شود (که به علت نداشتن .MyModel، سیستم binding از مقدار آن صرفنظر می‌کند). اکنون با استفاده از BlazorHtmlField فوق، می‌توان به نام کامل تولیدی توسط Blazor SSR دسترسی یافت و از آن استفاده کرد:

<input type="text" dir="ltr"
        name="@ValueField.HtmlFieldName" 
        id="@ValueField.HtmlFieldName" />

HtmlFieldName ای که در اینجا درج می‌شود، توسط خود Blazor محاسبه شده و با انتظارات موتور binding آن تطابق دارد و دیگر به خواص بایند شده‌ای که مقدار نمی‌گیرند، نخواهیم رسید.

نظرات مطالب
پیاده سازی عملیات CRUD با استفاده از پروتکل OData
در یک نگاه کلی، ASP.NET Web API OData بر روی ASP.NET Web API سوار شده است و مزیت‌های پایه آن را به صورت کامل دارد، شامل Routing، داشتن Action Filterها، احراز هویت و ... همچنین استاندارد OData بر روی استاندارد Rest سوار شده است و مزیت‌های پایه ای آن را دارا می‌باشد. اما در کنار اینها OData ویژگی هایی دارد، مانند batch request که در پست‌های بعدی توضیح خواهم داد. و همچنین با داشتن Metadata و استفاده از یک OData Client که در JavaScript | C# | Objective-C و ... کتاب خانه‌های مختلفی برای آن‌ها وجود دارد، شما می‌توانید به سادگی Proxy سمت کلاینت رو Generate کرده و متدها را فراخوانی کنید. همچنین سازگاری استاندارد دیتا گرید ها، کمبو باکس‌ها و کنترل‌های گوناگون از فریم ورک‌های مختلف از دیگر ویژگی‌های OData است. بخاطر مبتنی بر Rest بودن Web API OData امکان فراخوانی آن با jQuery نیز وجود دارد؛ اما استفاده از OData Client‌ها می‌تواند سادگی کار را به شدت بالا ببرد. سازگاری با Owin و امکان اجرای آن بر روی ASP.NET Core نیز از دیگر موارد مهمی است که باید به آن اشاره کنم. در ضمن این استاندارد محدود به مایکروسافت نبوده و در سایر زبان‌ها و پلتفرم‌ها نیز شناخته شده می‌باشد. برای مثال امکان نوشتن یک OData Server در جاوا یا جاوا اسکریپت نیز وجود دارد.
نظرات اشتراک‌ها
نگارش نهایی SQL Server 2016 منتشر شد
مانند نگارش‌های سال‌های قبل است. بر اساس کلید درج شده‌ی در فایل ISO، نوع نگارش مشخص می‌شود؛ یا می‌توانید این کلید را خودتان در نگارش 180 روزه وارد کنید. در اصل هیچ تفاوتی ندارند.