بازخوردهای دوره
بررسی کارآیی و ایندکس گذاری بر روی اسناد XML در SQL Server - قسمت دوم
یک نکته‌ی تکمیلی
از SQL Server 2012 SP1 به بعد، ایندکس جدیدی به نام Selective XML Indexes به مجموعه‌ی ایندکس‌های قابل تعریف بر روی یک ستون XML ایی اضافه شده‌است و این مزایا را به همراه دارد:
- برخلاف ایندکس‌های اولیه و ثانویه بحث شده در مطلب جاری، کل محتوای سند را ایندکس نمی‌کنند. به همین جهت حجم کمتری را اشغال کرده و سرعت Insert و Update را کاهش نمی‌دهند.
- با استفاده از Selective XML Indexes تنها XPathهایی را که مشخص می‌کنید، ایندکس خواهند شد. بنابراین بر اساس کوئری‌های موجود، می‌توان ایندکس‌های بهتری را تعریف کرد.

این نوع ایندکس‌ها به صورت پیش فرض فعال نبوده و نیاز است از طریق رویه ذخیره شده سیستمی sp_db_selective_xml_index، فعال شوند.
 sys.sp_db_selective_xml_index @dbname = 'dbname', @selective_xml_index = 'action: on|off|true|false'
و پس از آن برای تعریف یک ایندکس انتخابی خواهیم داشت:
 create selective xml index index_name
on table_name(column_name)
for (<path>)
قسمت path آن برای مثال در عمل، چنین شکلی را می‌تواند داشته باشد:
 for(
 pathColor = '/Item/Product/Color' as SQL nvarchar(20),
 pathSize = '/Item/Product/Size' as SQL int
)  
مطالب
خواندنی‌ها روز 22 فروردین

سایت iplocationtools بانک اطلاعاتی موقعیت مکانی IP های خود را که ماهیانه نیز به روز خواهد شد، به رایگان برای دریافت قرار داده است. فرمت تعاریف جداول و عبارت insert آن مطابق دستورات بانک اطلاعاتی MySQL است.
تنها بازیگر شاخص یازده سال قبل در زمینه توسعه برنامه‌های مبتنی بر QT ، برنامه KDevelop بوده است. این وضعیت با ارائه Qt Creator‌ بهبود یافته است ...
  • شرکت Red gate که پیشتر برنامه‌ی معروف Reflector را در جهت اهداف تبلیغاتی خود تصاحب کرده بود، اکنون در ادامه‌ی همین سیاست، کتاب Illustrated C# 2008 را نیز به رایگان ارائه داده است.
  • سایت DelphiFeeds.com با طراحی جدید خود چند روزی است که متحول شده است. سایتی شبیه به سایت مهندس خودمان که بعد از عید معلوم نیست چه بلایی سرش آمده است و جای خالی آن احساس می‌شود، هر چند سایت IDevCenter نیز همین کار را به صورت تخصصی‌تری ارائه می‌دهد.
  • Microsoft MED-V 1.0 در جهت توسعه‌ی اهداف مجازی سازی مایکروسافت ارائه شد.

مطالب
فعال و غیر فعال کردن قیود
SQL Server قابلیت فعال(enable) و غیر فعال(disable) کردن دو قید کلید خارجی و check را برای ما مهیا کرده است.
ما می‌توانیم بعد از ساخت جدول و انتشار مقداری داده در آن قیدهایی را ایجاد کنیم. بطور پیشفرض اگر شرط قید ما بر قرار بود قید به طور صحیح ساخته می‌شود و اگر شرط قید ما بر قرار نباشد قید با خطای conflict مواجه خواهد شد.
بطور کلی غیر فعال کردن قیدها کار درستی نیست. ولی در برخی مواقع برای تسریع در اجرای کد می‌توانیم قید را غیر فعال کنیم. بطور مثال اگر یک میلیون داده قرار است در جدول درج شود و مطمئن هستیم که این داده‌ها جامعیت داده‌ها را حفظ می‌کنند آنگاه می‌توانیم قید را برای تسریع در عمل درج بطور موفق غیر فعال کنیم.

فعال و غیر فعال کردن از طریق DDL
با غیر فعال کردن قیود داده‌ها را در وضعیت نامناسبی قرار می‌دهیم ولی همان طور که اشاره شد بطور موفق اشکالی پیش نخواهد آمد.
در ادامه ابتدا طریقه غیرفعال کردن و مجددا فعال کردن قیود را توسط دستور alter table نشان خواهم داد سپس به سراغ امکانات ویزاردی می‌رویم. ابتدا یک جدول تک ستونه ایجاد می‌کنیم:
CREATE TABLE testTable
(column1 integer not  null);

الان هیچ قیدی روی جدول لحاظ نشده است. پس هر داده که در رنج domain ستون باشد را میتوانیم درج کنیم. پس بطور نمونه این داده‌ها را درج میکنیم:
INSERT INTO testTable
VALUES (-10), (0), (10), (20), (30), (40)

حالا تصمیم داریم قیدی روی ستون column1 بگذاریم که توسط آن تنها اعداد مثبت در جدول درج شوند. پس داریم:
ALTER TABLE testTable
WITH CHECK 
ADD CONSTRAINT NoNegative 
CHECK (column1 > 0);
ولی چون داده هایی در جدول از قبل وجود داشته اند که قید ما را نقض کنند این قید ساخته نخواهد شد و با پیغام زیر مواجه خواهیم شد:
The ALTER TABLE statement conflicted with the CHECK constraint "NoNegative".

برای ساخت این قید روی این داده‌ها تنها راه استفاده از کلید واژه‌های WITH NOCHECK است یعنی:
ALTER TABLE testTable
WITH NOCHECK 
ADD CONSTRAINT NoNegative 
CHECK (column1 > 0);

و اکنون سعی میکنیم یک مقدار منفی در جدول درج کنیم:
INSERT INTO testTable VALUES (-5)
  /*
 The INSERT statement conflicted with the CHECK constraint "NoNegative".
 */

اما قیدی که ساخته بودیم در جدول در حال اعمال شدن است. برای درج مقدار منفی باید غیر را غیر فعال کنیم.
ALTER TABLE TestTable
NOCHECK CONSTRAINT NoNegative

و حالا مقدار منفی را درج میکنیم. و برای برگرداندن وضعیت NOCHECK به وضعیت CHECK باید از کلید واژه‌های WITH NOCHECK استفاده کنیم. چرا که داده هایی در جدول درج شده اند که قید مورد نظر ما را نقض می‌کنند.
ALTER TABLE TestTable
WITH NOCHECK
CHECK CONSTRAINT NoNegative

فعال و غیر فعال کردن از طریق design
در قسمت object explorer قید مورد نظر را پیدا کرده و روی آن راست کلیک کرده و گزینه Modify را انتخاب کنید. سپس در پنجره باز شده در قسمت Table Designer تغییرات مورد نظر خود را اعمال کنید.


نظرات مطالب
ساخت ربات تلگرامی با #C
1. آپدیتهای دریافتی همیشه فقط شامل Message نمیشه و ممکنه آپدیت دریافتی از نوع CallbackQuery هم باشه
2. کمی بالاتر توضیح داده شده که برای دریافت آپدیت‌های جدید باید پارامتر offset رو هم ارسال کنی. مقدار این پارامتر باید رقم بعدی Id آخرین آپدیت دریافتی باشه یعنی update_id  +1
برای طراحی ربات تلگرام هم بهتر هست که از پکیج‌های آماده استفاده بشه که بالاتر عرض کردم.
یه پروژه کنسول ایجاد کن پکیج telegram.bot رو هم از Nuget به برنامه اضافه کن و کلاس program.cs  رو به صورت زیر پیاده کن
به جای BOT_TOKEN هم توکن ربات خودت رو کپی کن و برنامه رو اجرا کن
using System.Threading.Tasks;
using Telegram.Bot;

namespace Bot.Engine.Console
{
    public class Program
    {
        Api bot;
        string botToken = "BOT_TOKEN";

        public static void Main(string[] args)
        {
            Task.Run(() => RunBot(botToken));

            System.Console.ReadLine();
        }


        /// <summary>
        /// 
        /// </summary>
        public static async Task RunBot(string botToken)
        {
            #region راه اندازی ربات

             bot = new Api(botToken);
            var me = await bot.GetMe();
            if (me != null)
            {
                System.Console.WriteLine("bot started {0}", me.Username);
            }
            else
            {
                System.Console.WriteLine("get bot failed ");
            }

          
            #endregion

            #region شروع گوش دادن به درخواست‌ها var whileCount = 0;
            var offset = 0;

            while (true)
            {
                System.Console.WriteLine("while no {0}", whileCount);

                whileCount += 1;
                try
                {
                    var updates = await bot.GetUpdatesAsync(offset);
                    var updatesCount = updates.Count();
                    System.Console.WriteLine("updates count is {0}", updatesCount);
                    System.Console.WriteLine("================================================================");

                    if (updatesCount > 0)
                    {
                        foreach (var update in updates)
                        {
                            try
                            {
                                offset = update.Id + 1;
                                if (update.Message.Text!=null)
                                {
                                    //echo msg
                                    await bot.SendTextMessageAsync(update.Message.Chat.Id, update.Message.Text);
                                }
                                else
                                {
                                    await bot.SendTextMessageAsync(update.Message.Chat.Id, "لطفا یک پیام متنی بفرستید");
                                }

                            }
                            catch (Exception ex)
                            {
                                bot.SendTextMessage(update.Message.Chat.Id, ex.ToString());
                            }
                        }
                        continue;
                    }


                }
                catch (Exception ex)
                {
                    System.Console.WriteLine("Error Msg = {0}",ex.Message);
                }

            }

            #endregion
        }

    }
}

مطالب
Identity و مباحث مربوطه (قسمت دوم) نحوه بدست آوردن مقادیر Identity

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

1-  استفاده از متغییر سیستمی Identity@@

2- استفاده از تابع () Scope_Identity

3-  استفاده از تابع Ident_Current

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

1- متغییر سیستمی Identity@@ : این متغییر سیستمی حاوی آخرین Identity ایجاد شده به ازای Session جاری شما است. لازم به ذکر است اگر به واسته Insert شما، Identity دیگری در یک حوزه دیگر (مانند یک Trigger) ایجاد شود مقدار موجود در این متغییر حاوی آخرین Identity ایجاد شده است. (یعنی Identity ایجاد شده توسط آن تریگر و نه خود جدول). لازم به ذکر است این موضوع به طور کامل در ادامه مقاله شرح داده شده است.

2- استفاده از تابع()Scope_Identity : با استفاده از این تابع می‌توانیم آخرین Identify ایجا دشده به ازای Session جاری را بدست آوریم. لازم به ذکر است مقادیر Identity ایجاد شده توسط سایر حوزه‌ها تاثیر در مقدار بازگشتی توسط این تابع ندارد. در ادامه مقاله این موضوع به طور کامل بررسی شده است.

3- استفاده از تابع ident_Current : این تابع آخرین مقدار Identity موجود در یک جدول را نمایش می‌دهد. ذکر این نکته ضروری است که Identity ایجاد شده توسط سایر Sessionها هم روی خروجی این تابع تاثیرگذار است. چون این تابع آخرین Identity موجود در جدول را به شما نمایش می‌دهد و نه Identity ایجاد شده به ازای یکSession  را.

برای بدست آوردن یک Identity کافی است که پس از درج رکورد در جدول مورد نظر متغییر سیستمی @@Identity و یا توابع Scope_Identity و یا Ident_Current را همانند مثال زیر Select کنید.  

USE TEMPDB
GO
IF OBJECT_ID(N'Employees', N'U') IS NOT NULL 
    DROP TABLE Employees1;
GO
CREATE TABLE Employees
(
ID int IDENTITY,
FirstName NVARCHAR(50),
LastName NVARCHAR(50)
)
GO
INSERT INTO Employees (FirstName,LastName) VALUES (N'مسعود',N'طاهری')
GO
SELECT @@IDENTITY AS [@@IDENTITY]
SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()]
SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')]
GO

خروجی دستورات بالا پس از درج رکورد مورد نظر به صورت زیر است. 

اما ممکن است از خودتان این سوال را بپرسید که آیا این توابع در سطح شبکه آخرین مقدار Identity درج شده توسط سایر Sessionها را نمایش می‌دهند و یا Session جاری را؟ (منظور Sessionی که درخواست مقدار موجود  در identity را نموده است).

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

1-ایجاد جدول Employees1

 USE TEMPDB GO
IF OBJECT_ID(N'Employees1', N'U') IS NOT NULL 
    DROP TABLE Employees1;
GO
CREATE TABLE Employees1
(
ID int IDENTITY(1,1),
FirstName NVARCHAR(50),
LastName NVARCHAR(50)
)
GO

همانطور که مشاهده می‌کنید مقدار شروع برای Identity برابر 1 و گام افزایش هم برابر 1 در نظر گرفته شده است(Identity(1,1)) .
2-در Sessionی جدید دستورات زیر را اجرا نمایید. (درج رکورد جدید در جدول Employees1 و واکشی مقدار Identity)
 USE tempdb
GO
INSERT INTO Employees1(FirstName,LastName) VALUES (N'فرید',N'طاهری')
GO
SELECT @@IDENTITY AS [@@IDENTITY]
SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()]
SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')]
GO

همانگونه که ملاحضه می‌کنید @@Identity، Scope_Identity() و Ident_Current هر سه مقدار Identity (عدد 1) ایجاد شده بوسیله دستور Insert را به شما نمایش می‌دهند.

1-   و در انتها در یک Session دیگر دستورات زیر را اجرا نمایید.(واکشی مقدار Identity

USE tempdb
GO
SELECT @@IDENTITY AS [@@IDENTITY]
SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()]
SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')]
GO

همانطور که مشاهده می‌کنید در این Seesion ما از SQL خواسته‌ایم آخرین مقدار Identity را به ما نشان داده شود. باید به این نکته توجه کنید با توجه به اینکه در این Session عملیات درجی هنوز انجام نگرفته است که ما Identity ایجاد شده را مشاهده نماییم. بنابراین صرفاً تابع Iden_Current مقدار Identity موجود در جدول را به ما نمایش می‌دهد.

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

@@Idnetity و Scope_Identity :  Identity ایجاد به ازای Session جاری را نمایش داده و به مقادیر تولید شده توسط سایر Sessionهای دیگر دسترسی ندارد.

Ident_Current : آخرین Identity موجود در جدول را به شما نمایش می‌دهد. بنابراین باید این نکته را در نظر داشته باشید که Identityها ایجاد شده توسط سایر Sessionها روی مقدار بازگشتی این تابع تاثیرگدار است. 

اما یکی دیگر از مباحث مهم درباره Identity تاثیر Scope بر مقدار Identity است (یعنی چه!) . برای اینکه با مفهوم این موضوع آشنا شوید اسکریپت‌های مربوط به مثال زیر را بدقت اجرا کنید.

1-   ایجاد جدول Employees1 

USE TEMPDB
GO
IF OBJECT_ID(N'Employees1', N'U') IS NOT NULL 
    DROP TABLE Employees1;
GO
CREATE TABLE Employees1
(
ID int IDENTITY(1,1),
FirstName NVARCHAR(50),
LastName NVARCHAR(50)
)
GO

همانطور که مشاهده می‌کنید مقدار شروع برای Identity برابر 1 و گام افزایش هم برابر 1 در نظر گرفته شده است(Identity(1,1)) .

2-   ایجاد جدول Employees2 

USE TEMPDB
GO
IF OBJECT_ID(N'Employees2', N'U') IS NOT NULL 
    DROP TABLE Employees2;
GO
CREATE TABLE Employees2
(
ID int IDENTITY(100,1),
FirstName NVARCHAR(50),
LastName NVARCHAR(50)
)
GO


همانطور که مشاهده می‌کنید مقدار شروع برای Identity برابر 100 و گام افزایش هم برابر 1 در نظر گرفته شده است(Identity(100,1)).

3- ایجاد یک Trigger به ازای جدول Employees1 

USE tempdb
GO
CREATE TRIGGER Employees1_Insert ON Employees1 FOR INSERT 
AS
BEGIN
   INSERT Employees2(FirstName,LastName)
SELECT FirstName,LastName FROM INSERTED 
END;
GO

Trigger ایجاد شده به ازای جدول Employees1 به ازای عملیات Insert اجرا می‌شود. همچنین مقادیر درج شده در جدول Employees1 بوسیله جدول Inserted در دسترس است. لازم به ذکر است جدول Inserted یک جدول موقت بوده که توسط Trigger ایجاد شده و داخل خود آن معتبر است.

هدف ما از ایجاد این Trigger تهیه یک کپی از رکوردهایی که در جدول Employees1 درج می‌شوند است. این کپی قرار است با استفاده از دستور Insert…Select در جدول Employees2 ایجاد گردد. 


4-   درج یک رکورد در جدول Employees1 و واکشی مقدار Identity 

USE tempdb
GO
INSERT INTO Employees1(FirstName,LastName) VALUES (N'مسعود',N'طاهری')
GO
SELECT @@IDENTITY AS [@@IDENTITY]
SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()]
SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')]
SELECT IDENT_CURRENT('Employees2') AS [IDENT_CURRENT('Employees2')]
GO

مقادیر استخراج شده به ازای Identity به شرح زیر است

1-   @@Identity : پس از درج رکورد در جدول Employees1 متغییر سیستمی @@Identity مقدار 100 را نمایش داده است دلیل این موضوع بر می‌گردد به Trigger موجود در جدول Employees1.  

با توجه به اینکه جدول Employees1 دارای یک فیلد Identity بوده است هنگام درج رکورد در جدول مقدار @@Identity=1 است اما چون این جدول دارای Triggerی است که این Trigger خود با جدولی دیگری درگیر است که دارای Identity است مقدار متغییر @@identity=100 خواهد شد.

2- Scope_Identity() : مقدار نمایش داده شده توسط تابع Scope_Identity() برابر با مقدار Identity تخصیص (عدد 1) داده شده به ازای رکورد شما می‌باشد که این موضوع در اغلب موارد مد نظر برنامه‌نویسان می‌باشد.

3- Ident_Current(‘Employees1’) : مقدار نمایش شده توسط تابع Ident_Current آخرین مقدار Identity (عدد 1) موجود در جدول Employees1 است.

4- Ident_Current(‘Employees2’) : مقدار نمایش شده توسط تابع Ident_Current آخرین مقدار Identity (عدد 100) موجود در جدول Employees2 است.


چند نکته مهم

1-  مقدار بازگردانده شده توسط تابع Ident_Current آخرین مقدار Identity موجود در جدول مورد نظر شما بوده است و عملیات درج سایر کاربران در این مقدار تاثیر گذار است.

2-   برای بدست آوردن مقدار Identity درست بهتر است از تابع Scope_Identity() استفاده نماییم. معمولاً در بیشتر مواقع مقدار بازگردانده شده توسط این تابع مد نظر برنامه نویسان است.

3-   EntityFramework و Nhibernate هم برای بدست آوردن Identity از تابع Scope_Identity استفاده می‌کند.  

نظرات مطالب
خودکار کردن تعاریف DbSetها در EF Code first
سلام
من یه مشکلی دارم توی این قسمت.
توی پروژه از خودکار سازی نگاشت‌ها و همین بخش استفاده کردم که در نهایت پیعام خطای زیر رو میده:
The entity type User is not part of the model for the current context.
قسمت نگاشت‌ها همانند همین که تو سایت گفتین استفاده کردم. پروژه شامل سه بخش Domain و Model  و Console هست برای تست این دو بخش. 
قسمت خودکار سازی نگاشت‌ها بدون خود کارسازی تعاریف DbSet‌ها به درستی کار میکنه، وقتی این بخش رو پیاده میکنم پیغام خطای بالا رو میده تو متد Seed واسه دیتاهای پیش فرض User.
namespace Test.Domain
{
    public abstract class BaseEntity
    {
        public int Id { set; get; }
    }
}

namespace Test.Domain.Entities
{
    public class User : BaseEntity
    {
        public int UserNumber { get; set; }
        public string Name { get; set; }
    }
}

namespace Test.Domain.Mappings
{
    public class UserMap : EntityTypeConfiguration<User>
    {
        public UserMap()
        {
            this.HasKey(x => x.UserNumber);
            this.Property(x => x.Name).HasMaxLength(450).IsRequired();
        }
    }
} 

   loadEntities(asm, modelBuilder, "Test.Domain");

مطالب
توزیع پروژه‌های ASP.NET MVC بدون ارائه فایل‌های View آن
پروژه دیگری از آقای David Ebbo (عضو تیم ASP.NET که پیشتر با پروژه T4 MVC آن در این سایت آشنا شده‌اید)، جهت کامپایل کامل فایل‌های View و ارائه پروژه نهایی ASP.NET MVC بدون نیاز به ارائه پوشه Views آن به نام Razor Generator وجود دارد که در ادامه خلاصه‌ای از نحوه استفاده از آن‌را مرور خواهیم کرد.

الف) ابتدا افزونه Razor Generator را از اینجا دریافت و نصب کنید.
ب) سپس به پروژه MVC خود بسته NuGet زیر را اضافه نمائید:
 PM> Install-Package RazorGenerator.Mvc
در این حالت باید پروژه پیش فرض، همان وب سایت MVC شما انتخاب گردد:


با اضافه کردن این بسته NuGet تغییرات زیر به پروژه جاری اعمال خواهند شد:
  - ارجاعی به اسمبلی RazorGenerator.Mvc.dll به پروژه اضافه خواهد شد.
  - در پوشه App_Start، فایلی به نام RazorGeneratorMvcStart.cs اضافه گردیده و کار تنظیم موتور View مخصوص کار با Viewهای کامپایل شده را انجام می‌دهد.

ج) پس از نصب بسته NuGet یاد شده در همان خط فرمان پاورشل نوگت دستور زیر را صادر کنید:
 PM> Enable-RazorGenerator
و ... همین!

پس از انجام اینکار، دو کار صورت خواهد گرفت:
  - برای تمام Viewهای برنامه، فایل cs متناظری تولید می‌شود که ذیل فایل‌های View قابل مشاهده است.
  - گزینه Custom tool این Viewها نیز به RazorGenerator تنظیم می‌شود.


بدیهی است اگر از دستور Enable-RazorGenerator استفاده نکنید، نیاز خواهید داشت تا تنظیم گزینه Custom tool به RazorGenerator کلیه Viewها را دستی انجام داده و اگر فایل cs متناظر با View تولید نشد روی فایل view کلیک راست کرده و گزینه run custom tool را انتخاب کنید. اما دستور Enable-RazorGenerator کار را بسیار ساده می‌کند.

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

یک سری قابلیت‌های دیگر نیز به همراه این پروژه است مانند انتقال Viewها به یک اسمبلی دیگر و یا استفاده از MSBuild برای انجام عملیات که می‌توانید آن‌ها را در Wiki پروژه Razor Generator مطالعه کنید. انتقال Viewها به یک اسمبلی دیگر هرچند در این روش کاملا ممکن شده و کار می‌کند اما صفحه dialog افزودن یک view جدید مهیا در کلیک راست بر روی اکشن متدهای یک کنترلر را غیرفعال می‌کند که در عمل آنچنان جالب نیست.


یک نکته مهم:
اگر در آینده بسته NuGet و افزونه یاد شده را به روز کردید نیاز است دستور زیر را اجرا کنید:
 PM> Redo-RazorGenerator
به این ترتیب بر اساس ساختار و کدهای جدید RazorGenerator، کلیه فایل‌های cs تولید شده مجددا به روز و تولید خواهند شد.


فایل‌های Helper قرار گرفته در پوشه App_Code

اگر HTML Helperهای خود را توسط فایل‌های Razor قرار گرفته در پوشه App_Code تولید می‌کنید، پس از اجرای دستور Enable-RazorGenerator، برای این موارد نیز فایل‌های cs متناظری تولید می‌شود. با این تفاوت که Build Action آن‌ها بر روی Compile قرار ندارند که این مورد را باید دستی تنظیم کنید. همچنین حین استفاده از این توابع کمکی نیاز است فضای نام مرتبط را نیز در ابتدای فایل View خود ذکر کنید مثلا:
 @using MvcViewGenTest2.app_code
البته با استفاده از Razor Generaor دیگر نیازی به استفاده از پوشه App_Code نخواهد بود؛ از این جهت که کار کامپایل خودکار، به زمان اجرا موکول نخواهد شد. بنابراین اینبار در هر جایی که علاقمند بودید می‌توانید این فایل‌های کمکی را تولید و کامپایل کنید. فقط ذکر فضای نام مرتبط را در ابتدای View خود فراموش نکنید.


حذف فایل RazorGeneratorMvcStart.cs
 اگر علاقمند به استفاده از فایل پیش فرض RazorGeneratorMvcStart.cs نیستید و می‌خواهید موتورهای View اضافی را حذف کنید، ابتدا فایل RazorGeneratorMvcStart.cs را حذف کرده و سپس در فایل global.asax.cs تغییر زیر را اعمال نمائید:
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.WebPages;
using RazorGenerator.Mvc;

namespace MvcViewGenTest2
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            // Adding PrecompiledMvcEngine
            var engine = new PrecompiledMvcEngine(typeof(MvcApplication).Assembly);
            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(engine);
            VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
        }
    }
}
مطالب
افزودن یک DataType جدید برای نگه‌داری تاریخ خورشیدی - 1

ثبت و نگه‌داری تاریخ خورشیدی در SQL Server از دیرباز یکی از نگرانی‌های برنامه‌نویسان و طراحان پایگاه داده‌ها بوده است. در این نوشتار، راه‌کار تعریف یک DataType در SQL Server 2012 به روش CLR آموزش داده خواهد شد.

در ویژوال استودیو یک پروژه‌ی جدید از نوع SQL Server Database Project به شکل زیر ایجاد کنید: 

نام پروژه را به یاد تقویم خیام، prgJalaliDate می‌گذارم. در Solution Explorer روی نام پروژه راست‌کلیک کرده، سپس روی Add New Item کلیک کنید. در پنجره‌ی بازشده مطابق شکل SQL CLR C# User Defined Type را برگزینید؛ سپس نام JalaliDateType را برای آن انتخاب کنید.
 

 متن موجود در صفحه‌ی بازشده را کاملاً حذف کرده و با کد زیر جای‌گزین کنید.

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

using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

[Serializable()]
[SqlUserDefinedType(Format.Native)]
public struct JalaliDate : INullable
{
    private Int16 m_Year;
    private byte m_Month;
    private byte m_Day;
    private byte m_Hour;
    private byte m_Minute;
    private byte m_Second;
    private bool is_Null;


    public Int16 Year
    {
        get
        {
            return (this.m_Year);
        }
        set
        {
            m_Year = value;
        }
    }

    public byte Month
    {
        get
        {
            return (this.m_Month);
        }
        set
        {
            m_Month = value;
        }
    }

    public byte Day
    {
        get
        {
            return (this.m_Day);
        }
        set
        {
            m_Day = value;
        }
    }

    public byte Hour
    {
        get
        {
            return (this.m_Hour);
        }
        set
        {
            m_Hour = value;
        }
    }

    public byte Minute
    {
        get
        {
            return (this.m_Minute);
        }
        set
        {
            m_Minute = value;
        }
    }

    public byte Second
    {
        get
        {
            return (this.m_Second);
        }
        set
        {
            m_Second = value;
        }
    }

    public bool IsNull
    {
        get
        {
            return is_Null;
        }
    }

    public static JalaliDate Null
    {
        get
        {
            JalaliDate jl = new JalaliDate();
            jl.is_Null = true;
            return (jl);
        }
    }


    public override string ToString()
    {
        if (this.IsNull)
        {
            return "NULL";
        }
        else
        {
            return this.m_Year.ToString("D4") + "/" + this.m_Month.ToString("D2") + "/" + this.m_Day.ToString("D2") + " " + this.Hour.ToString("D2") + ":" + this.Minute.ToString("D2") + ":" + this.Second.ToString("D2");
        }
    }


    public static JalaliDate Parse(SqlString s)
    {
        if (s.IsNull)
        {
            return Null;
        }

        System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar();
        string str = Convert.ToString(s);
        string[] JDate = str.Split(' ')[0].Split('/');

        JalaliDate jl = new JalaliDate();

        jl.Year = Convert.ToInt16(JDate[0]);
        byte MonthsInYear = (byte)pers.GetMonthsInYear(jl.Year);
        jl.Month = (byte.Parse(JDate[1]) <= MonthsInYear ? (byte.Parse(JDate[1]) > 0 ? byte.Parse(JDate[1]) : (byte)1) : MonthsInYear);
        byte DaysInMonth = (byte)pers.GetDaysInMonth(jl.Year, jl.Month); ;
        jl.Day = (byte.Parse(JDate[2]) <= DaysInMonth ? (byte.Parse(JDate[2]) > 0 ? byte.Parse(JDate[2]) : (byte)1) : DaysInMonth);
        if (str.Split(' ').Length > 1)
        {
            string[] JTime = str.Split(' ')[1].Split(':');
            jl.Hour = (JTime.Length >= 1 ? (byte.Parse(JTime[0]) < 23 && byte.Parse(JTime[0]) >= (byte)0 ? byte.Parse(JTime[0]) : (byte)0) : (byte)0);
            jl.Minute = (JTime.Length >= 2 ? (byte.Parse(JTime[1]) < 59 && byte.Parse(JTime[1]) >= (byte)0 ? byte.Parse(JTime[1]) : (byte)0) : (byte)0);
            jl.Second = (JTime.Length >= 3 ? (byte.Parse(JTime[2]) < 59 && byte.Parse(JTime[2]) >= (byte)0 ? byte.Parse(JTime[2]) : (byte)0) : (byte)0);
        }
        else { jl.Hour = 0; jl.Minute = 0; jl.Second = 0; }

        return (jl);
    }

    public SqlString GetDate()
    {
        return this.m_Year.ToString("D4") + "/" + this.m_Month.ToString("D2") + "/" + this.m_Day.ToString("D2");
    }

    public SqlString GetTime()
    {
        return this.Hour.ToString("D2") + ":" + this.Minute.ToString("D2") + ":" + this.Second.ToString("D2");
    }

    public SqlDateTime ToGregorianTime()
    {
        System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar();
        return SqlDateTime.Parse(pers.ToDateTime(this.Year, this.Month, this.Day, this.Hour, this.Minute, this.Second, 0).ToString());
    }

    public SqlString JalaliDateAdd(SqlString interval, int increment)
    {
        System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar();
        DateTime dt = pers.ToDateTime(this.Year, this.Month, this.Day, this.Hour, this.Minute, this.Second, 0);
        string CInterval = interval.ToString();
        bool isConvert = true;
        switch (CInterval)
        {
            case "Year":
                dt = pers.AddYears(dt, increment);
                break;
            case "Month":
                dt = pers.AddMonths(dt, increment);
                break;
            case "Day":
                dt = pers.AddDays(dt, increment);
                break;
            case "Hour":
                dt = pers.AddHours(dt, increment);
                break;
            case "Minute":
                dt = pers.AddMinutes(dt, increment);
                break;
            case "Second":
                dt = pers.AddSeconds(dt, increment);
                break;
            default:
                isConvert = false;
                break;
        }

        if (isConvert == true)
        {
            this.Year = (Int16)pers.GetYear(dt);
            this.Month = (byte)pers.GetMonth(dt);
            this.Day = (byte)pers.GetDayOfMonth(dt);
            this.Hour = (byte)pers.GetHour(dt);
            this.Minute = (byte)pers.GetMinute(dt);
            this.Second = (byte)pers.GetSecond(dt);
        }


        return this.m_Year.ToString("D4") + "/" + this.m_Month.ToString("D2") + "/" + this.m_Day.ToString("D2") + " " + this.Hour.ToString("D2") + ":" + this.Minute.ToString("D2") + ":" + this.Second.ToString("D2");
    }
}

از منوهای بالا روی منوی Bulild و سپس گزینه‌ی Publish prgJalaliDate کلیک کتید:

در پنجره‌ی بازشده روی دکمه‌ی Edit کلیک کنید سپس تنظیمات مربوط به اتصال به پایگاه داده را انجام دهید.

روی دکمه‌ی OK کلیک کنید و سپس در پنجره‌ی اولیه، روی دکمه‌ی Publish کلیک کتید:

به همین سادگی، DataType مربوطه در SQL Server 2012 ساخته می‌شود. خبر خوش این‌که شما می‌توانید با راست‌کلیک روی نام پروژه و انتخاب گزینه‌ی Properties در قسمت Project Setting تنظیمات مربوط به نگارش SQL Server را انجام دهید. (از نگارش 2005 به بعد در VS 2012 پشتیبانی می‌شود.)


اکنون زمان آن رسیده است که DataType ایجادشده را در SQL Server 2012 بیازماییم. SQL Server را باز کنید و دستور زیر را در آن اجرا کتید.

USE Northwind

GO

CREATE TABLE dbo.TestTable
(
Id int NOT NULL IDENTITY (1, 1),
TestDate dbo.JalaliDate NULL
)  ON [PRIMARY]
GO
همین‌طور که مشاهده می‌کنید؛ امکان به‌کارگیری DataType تعریف‌شده وجود دارد. 
اکنون چند رکورد درون این جدول درج می‌کنیم:
Insert into TestTable (TestDate) Values ('1392/02/09'),('1392/02/09 22:40'),('1392/12/30 22:40')
پس از اجرای این دستور خطای زیر در پایین صفحه‌ی SQL Server نمایان می‌شود:

این خطا به این خاطر است که CLR را در SQL Server  فعال نکرده ایم. جهت فعال‌کردن CLR دستور زیر را اجرا کنید:
sp_configure 'clr enabled', 1
Reconfigure
بار دیگر دستور درج را اجرا می‌کنیم:
Insert into TestTable (TestDate) Values ('1392/02/09'),('1392/02/09 22:40'),('1392/12/30 22:40')
ملاحظه می‌کنید که داده‌ها در جدول مربوطه ذخیره شده است. در رکورد نخست چون ساعت، دقیقه و ثانیه تعریف نشده است؛ به طور هوشمند صفر درج شده است. در رکورد دوم، ساعت و دقیقه مقدار دارد ولی ثانیه صفر ثبت شده است. و در رکورد سوم چون سال 1392 کبیسه نیست؛ به صورت هوشمند آخرین روز ماه به جای روز ثبت شده است. هرچند می‌توان با دست‌کاری در توابع سی‌شارپ، این قوانین را عوض کرد.

اکنون زمان آن رسیده است که توسط یک پرس‌وجو، همه‌ی توابعی که در سی‌شارپ برای این نوع داده نوشتیم، بیازماییم. پرس‌وجوی زیر را اجرا کنید:
Select TestDate.ToString() as JalaliDateTime,
          TestDate.GetDate() as JalaliDate, TestDate.GetTime() as JalaliTime,
          TestDate.ToGregorianTime() as GregorianTime,
          TestDate.JalaliDateAdd('Day',1) JalaliTomorrow,
          TestDate.Month as JalaliMonth from TestTable
خروجی این پرس‌وجو به شکل زیر خواهد بود:

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

نیازی به گفتن نیست که می‌توانید به سادگی از توابع مربوط به DateTime در SQL Server بهره ببرید. برای مثال برای به دست آوردن فاصله‌ی میان دو روز از پرس‌وجوی زیر استفاده کنید:
Declare @a JalaliDate  = '1392/02/07 00:00:00'
Declare @b JalaliDate = '1392/02/05 00:00:00'

SELECT DATEDIFF("DAY",@b.ToGregorianTime(),@a.ToGregorianTime()) AS DiffDate

شاد و پیروز باشید.
مطالب دوره‌ها
جلوگیری از deadlock در برنامه‌های async
توضیح مطلب جاری نیاز به یک مثال دارد. به همین جهت یک برنامه‌ی WinForms یا WPF را آغاز کنید (تفاوتی نمی‌کند). سپس یک دکمه و یک برچسب را در صفحه قرار دهید. در ادامه کدهای فرم را به نحو ذیل تغییر دهید.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json.Linq;

namespace Async13
{
    public static class JsonExt
    {
        public static async Task<JObject> GetJsonAsync(this Uri uri)
        {
            using (var client = new HttpClient())
            {
                var jsonString = await client.GetStringAsync(uri);
                return JObject.Parse(jsonString);
            }
        }
    }

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnGo_Click(object sender, EventArgs e)
        {
            var url =
                "http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo";
            txtResult.Text = new Uri(url).GetJsonAsync().Result.ToString();
        }
    }
}
این کدها برای کامپایل نیاز به نصب بسته‌ی
 PM> Install-Package Newtonsoft.Json
و همچنین افزودن ارجاعی به اسمبلی استاندارد System.Net.Http نیز دارند.
در اینجا قصد داریم اطلاعات JSON دریافتی را در یک TextBox نمایش دهیم. کاری که انجام شده، فراخوانی متد async ایی است به نام GetJsonAsync و سپس استفاده از خاصیت Result این Task برای صبر کردن تا پایان عملیات.
اگر برنامه را اجرا کنید و بر روی دکمه‌ی دریافت اطلاعات کلیک نمائید، برنامه قفل خواهد کرد. چرا؟
البته تفاوتی هم نمی‌کند که این یک برنامه‌ی دسکتاپ است یا یک برنامه‌ی وب. در هر دو حالت یک deadlock کامل را مشاهده خواهید کرد.


علت بروز deadlock در کدهای async چیست؟

همواره نتیجه‌ی await، در context فراخوان آن بازگشت داده می‌شود. اگر برنامه‌ی دسکتاپ است، این context همان ترد اصلی UI برنامه می‌باشد و اگر برنامه‌ی وب است، این context، زمینه‌ی درخواست در حال پردازش می‌باشد.
خاصیت Result و یا استفاده از متد Wait یک Task، به صورت همزمان عمل می‌کنند و نه غیرهمزمان. متد GetJsonAsync یک Task ناتمام را که فراخوان آن باید جهت پایان‌اش صبر کند، بازگشت می‌دهد. سپس در همینجا کد فراخوان، تردجاری را توسط فراخوانی خاصیت Result قفل می‌کند. متد GetJsonAsync منتظر خواهد ایستاد تا این ترد آزاد شده و بتواند به کارش که بازگردان نتیجه‌ی عملیات به context جاری است، ادامه دهد.
به عبارتی، کدهای async منتظر پایان کار Result هستند تا نتیجه را بازگردانند. در همین لحظه کدهای همزمان برنامه نیز منتظر کدهای async هستند تا خاتمه یابند. نتیجه‌ی کار یک deadlock است.


روش‌های جلوگیری از deadlock در کدهای async؟

الف) در مورد متد ConfigureAwait در قسمت‌های قبل بحث شد و به عنوان یک best practice مطرح است:
    public static class JsonExt
    {
        public static async Task<JObject> GetJsonAsync(this Uri uri)
        {
            using (var client = new HttpClient())
            {
                var jsonString = await client.GetStringAsync(uri).ConfigureAwait(continueOnCapturedContext: false);
                return JObject.Parse(jsonString);
            }
        }
    }
با استفاده از ConfigureAwait false سبب خواهیم شد تا نتیجه‌ی عملیات به context جاری بازگشت داده نشود و نتیجه بر روی thread pool thread ادامه یابد. با اعمال این تغییر، کدهای متد btnGo_Click بدون مشکل اجرا خواهند شد.

ب) راه حل دوم، عدم استفاده از خواص و متدهای همزمان با متدهای غیر همزمان است:
        private async void btnGo_Click(object sender, EventArgs e)
        {
            var url =
                "http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo";
            var data = await new Uri(url).GetJsonAsync();
            txtResult.Text = data.ToString();
        }
ابتدا امضای متد رویدادگردان را اندکی تغییر داده و واژه‌ی کلیدی async را به آن اضافه می‌کنیم. سپس از await برای صبر کردن تا پایان عملیات متد GetJsonAsync استفاده خواهیم کرد. صبر کردنی که در اینجا انجام شده، یک asynchronous waits است؛ برخلاف روش همزمان استفاده از خاصیت Result یا متد Wait.


خلاصه‌ی بحث
Await را با متدهای همزمان Wait یا خاصیت Result بلاک نکنید. در غیراینصورت در ترد اجرا کننده‌ی دستورات، یک deadlock رخ‌خواهد داد؛ زیرا نتیجه‌ی await باید به context جاری بازگشت داده شود اما این context توسط خواص یا متدهای همزمان فراخوانی شده بعدی، قفل شده‌است.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 19 - بومی سازی
در حالت کلی همانطور که عنوان شد، برای تغییر فرهنگ ترد جاری از یکی از سه روش کوکی، کوئری استرینگ و هدر زبان استفاده می‌شود. این سه تامین کننده، تامین کننده‌های پیش فرض هستند.
اگر می‌خواهید برای مثال از روش کوکی ذکر شده (در متد public IActionResult SetFaLanguage ابتدای بحث) استفاده نکنید، می‌توان تامین کنند‌ه‌ی چهارمی را هم تدارک دید:
    public class FaRequestCultureProvider : RequestCultureProvider
    {
        public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
        {
            return Task.FromResult(new ProviderCultureResult("fa-IR"));
        }
    }
و بعد آن‌را به ابتدای لیست تامین کننده‌ها (insert 0 ذیل) اضافه کرد:
        public void Configure(IApplicationBuilder app)
        {
            var requestLocalizationOptions = new RequestLocalizationOptions
            {
                DefaultRequestCulture = new RequestCulture(new CultureInfo("fa-IR")),
                SupportedCultures = new[]
                {
                    new CultureInfo("en-US"),
                    new CultureInfo("fa-IR")
                },
                SupportedUICultures = new[]
                {
                    new CultureInfo("en-US"),
                    new CultureInfo("fa-IR")
                }
            };
            requestLocalizationOptions.RequestCultureProviders.Insert(0, new FaRequestCultureProvider());
            app.UseRequestLocalization(requestLocalizationOptions);
به این صورت فرهنگ پیش فرض ترد جاری به fa-IR استاندارد تنظیم می‌شود. برای حالت غیر استاندارد از همان اکشن فیلتر استفاده کنید.