مطالب
پیاده سازی پروژه‌ای مبتنی بر CQRS و ES
در قسمت قبلی با معماری CQRS و Event Sourcing بصورت مختصر آشنا شدیم. برای درک بیشتر مطلب پیشین، احتیاج به پیاده سازی آن به صورت عملیاتی و نه فقط تئوری محض میباشد و در این مرحله قصد پیاده سازی این مدل را به ساده‌ترین صورت ممکن داریم.
برای مطالعه‌ی ادامه‌ی این مقاله، نیاز به آشنایی با مباحث مطرح شده در قسمت قبل وجود دارد. پس از توضیحات اضافه بر روی قسمت‌های زیر گذشته و فرض بر آن است که آشنایی با این قسمت‌ها وجود دارد.
از این مدل میتوان در زبان‌های مختلف برنامه نویسی و همچنین سیستم‌های مختلف اعم از وب اپلیکیشن و ... استفاده نمود. همچنین برای استفاده از این مدل نیاز قطعی به استفاده از فریم ورک خاصی نیست. در صورت نیاز میتوانید پیاده سازی سفارشی خاص خود را داشته باشید. اما برای ساده‌تر شدن و هرچه سریعتر شدن مراحل از فریمورک SimpleCqrs استفاده میکنیم. هر چند بر خلاف نامش امکانات فراوانی را در اختیار برنامه نویسان قرار میدهد و حتی در پروژه‌های واقعی نیز میتوان از آن استفاده نمود.
برای سریعتر شدن کار میخواهیم پیاده سازی این مدل را در یک پروژه‌ی Console انجام دهیم و همچنین پس از ایجاد، پکیج‌های زیر را نصب مینماییم:
Unity, SimpleCqrs, SimpleCqrs.Unity
میخواهیم طبق مراحل گفته شده‌ی در قسمت قبل، به پیاده سازی این مدل بپردازیم و هدف، اضافه کردن یک Account به سیستم خواهد بود.
ابتدا باید DomainObject مورد نظر نوشته شود:
using System;
using SimpleCqrs.Domain;

namespace CqrsPattern.Cqrs.Command
{
    public class Account : AggregateRoot
    {
        public Account(Guid id)
        {
            Apply(new AccountCreatedEvent { AggregateRootId = id });
        }

        public void SetName(string firstName, string lastName)
        {
            Apply(new AccountNameSetEvent { FirstName = firstName, LastName = lastName });
        }

        public void OnAccountCreated(AccountCreatedEvent evt)
        {
            Id = evt.AggregateRootId;
        }
    }
}
نکته: میخواهیم عملیات اضافه کردن یک Account، با استفاده از دو event مربوطه به نام AccountCreatedEvent و مقدار دهی آن با استفاده از AccountNameSetEvent انجام شود.
eventهای فوق را در ادامه اضافه خواهیم داد (از توضیحات بیشتر صرفنظر شده و به مقاله‌ی قسمت قبل رجوع شود).
حال احتیاج به پیاده سازی Command مربوطه برای انجام وظیفه‌ی خود داریم که هدف آن، اضافه کردن یک Account  به سیستم مورد نظر میباشد.
فرض کنید برای اضافه شدن Account، پراپرتی‌های FirstName و LastName باید مقدار دهی شوند:
using SimpleCqrs.Commanding;

namespace CqrsPattern.Cqrs.Command
{
    public class CreateAccountCommand : ICommand
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

حال CommandHandler که وظیفه‌ی تفسیر کردن Command مربوطه را به عهده دارد، پیاده سازی خواهد شد:
using System;
using SimpleCqrs.Commanding;
using SimpleCqrs.Domain;

namespace CqrsPattern.Cqrs.Command
{
    public class CreateAccountCommandHandler : CommandHandler<CreateAccountCommand>
    {
        private readonly IDomainRepository repository;

        public CreateAccountCommandHandler(IDomainRepository repository)
        {
            this.repository = repository;
        }

        public override void Handle(CreateAccountCommand command)
        {
            var account = new Account(Guid.NewGuid());
            account.SetName(command.FirstName, command.LastName);

            repository.Save(account);
        }
    }
}
نکته: از طریق account.SetName فراخوانی Event مربوطه انجام شده‌است و همچنین repository.Save به raise کردن EventHandler میپردازد.
event مربوط به اضافه شدن Account را به صورت زیر پیاده سازی مینماییم:
using SimpleCqrs.Eventing;

namespace CqrsPattern.Cqrs.Command
{
    public class AccountCreatedEvent : DomainEvent { }
}
و همچنین event مربوط به مقدار دهی پراپرتی‌ها نیز به صورت زیر خواهد بود:
using SimpleCqrs.Eventing;

namespace CqrsPattern.Cqrs.Command
{
    public class AccountNameSetEvent : DomainEvent
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}
در این بخش، پیاده سازی EventHandler را خواهیم داشت. طبق مطلب پیشین هر Domain باید EventHnadler ی داشته باشد که از Event هایش ارث بری کرده و هر کدام از Event‌ها عملا در قسمت Handle مربوط به خودش پردازش خواهد شد.
using System.Linq;
using SimpleCqrs.Eventing;
using CqrsPattern.Cqrs.Db;

namespace CqrsPattern.Cqrs.Command
{
    public class AccountEventHandler : IHandleDomainEvents<AccountCreatedEvent>,
                                             IHandleDomainEvents<AccountNameSetEvent>
    {
        private readonly FakeAccountTable accountTable;

        public AccountEventHandler(FakeAccountTable accountTable)
        {
            this.accountTable = accountTable;
        }

        public void Handle(AccountCreatedEvent domainEvent)
        {
            accountTable.Add(new FakeAccountTableRow { Id = domainEvent.AggregateRootId });
        }

        public void Handle(AccountNameSetEvent domainEvent)
        {
            var account = accountTable.Single(x => x.Id == domainEvent.AggregateRootId);
            account.Name = domainEvent.FirstName + " " + domainEvent.LastName;
        }
    }
}
نکته: از آنجاییکه پیاده سازی ذخیره کردن Account با استفاده از دو event فوق انجام شده، بعد از Raise شدن EventHandler هر دو متد Handle، وظیفه‌ی Command مربوطه را به عهده دارند (بنابراین وظیفه‌ی هر Command میتواند با استفاده از event‌های مختلفی انجام شود).
برای اینکه نخواهیم وارد فاز‌های مربوط به دیتابیس شویم، موقتا یک db به صورت fake شده را پیاده سازی مینماییم؛ به صورت زیر:
using System.Collections.Generic;

namespace CqrsPattern.Cqrs.Db
{
    public class FakeAccountTable : List<FakeAccountTableRow>
    { }
}
using System;

namespace CqrsPattern.Cqrs.Db
{
    public class FakeAccountTableRow
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
}

و همچنین نیاز به ServiceLocator برای نمونه گرفتن از RunTime ی که از آن ارث بری کرده است داریم (برای سادگی کار از الگوی ServiceLocator استفاده میکنیم، ServiceLocator جز Anti-Pattern  ها محسوب میشود و معمولا در پروژه‌های واقعی از آن استفاده نمیشود)
using SimpleCqrs;
using SimpleCqrs.Unity;

namespace CqrsPattern
{
    public class SampleRunTime : SimpleCqrsRuntime<UnityServiceLocator> { }
}
حال احتیاج به پیاده سازی قسمت Queryداریم به همراه ReadModel و سرویسی برای فراخوانی آن
using System;

namespace CqrsPattern.Cqrs.Query
{
    public class AccountReadModel
    {
        public string Name { get; set; }
        public Guid Id { get; set; }
    }
}
using CqrsPattern.Cqrs.Db;
using System.Collections.Generic;
using System.Linq;

namespace CqrsPattern.Cqrs.Query
{
    public class AccountReportReadService
    {
        private FakeAccountTable fakeAccountDb;

        public AccountReportReadService(FakeAccountTable fakeAccountDb)
        {
            this.fakeAccountDb = fakeAccountDb;
        }

        public IEnumerable<AccountReadModel> GetAccounts()
        {
            return from a in fakeAccountDb
                   select new AccountReadModel { Id = a.Id, Name = a.Name };
        }
    }
}

در قسمت Main نرم افزار نیاز به register کردن FakeTable خود داریم و همانطور که ملاحظه میکنید Command مورد نظر را نمونه سازی کرده و آن را روی CommandBus قرار میدهیم تا مراحل پیاده سازی شده در قسمت‌های فوق انجام شود و همچنین بعد از اتمام command ارسال شده از طریق Service مورد نظر اطلاعات ذخیره شده بازگردانی میشود
using System;
using SimpleCqrs.Commanding;
using CqrsPattern.Cqrs.Query;
using CqrsPattern.Cqrs.Command;

namespace CqrsPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var runtime = new SampleRunTime();

            runtime.Start();

            var fakeAccountTable = new FakeAccountTable();
            runtime.ServiceLocator.Register(fakeAccountTable);
            runtime.ServiceLocator.Register(new AccountReportReadService(fakeAccountTable));
            var commandBus = runtime.ServiceLocator.Resolve<ICommandBus>();

            var cmd = new CreateAccountCommand { FirstName = "Ali", LastName = "Kh" };

            commandBus.Send(cmd);

            var accountReportReadModel = runtime.ServiceLocator.Resolve<AccountReportReadService>();

            Console.WriteLine("Accounts in database");
            Console.WriteLine("####################");
            foreach (var account in accountReportReadModel.GetAccounts())
            {
                Console.WriteLine(" Id: {0} Name: {1}", account.Id, account.Name);
            }

            runtime.Shutdown();

            Console.ReadLine();
        }
    }
}
اینگونه کل عملیات‌های لازم انجام خواهد شد.

خلاصه:
1) Command مربوطه را نمونه سازی کرده و روی CommandBus قرار میدهیم.
2) CommandHandler فراخوانی شده و فانکشن Handle آن باعث نمونه سازی از AggregateRoot میشود.
public override void Handle(CreateAccountCommand command)
        {
            var account = new Account(Guid.NewGuid()); //line 1
            account.SetName(command.FirstName, command.LastName); //line 2
            repository.Save(account); //line 3
        }
در خط نخست Constructor کلاس Account باعث Apply شدن event مربوطه میشود.
public Account(Guid id)
        {
            Apply(new AccountCreatedEvent { AggregateRootId = id });
        }
و در خط دوم account.SetName  برای Apply شدن event مربوط به مقدار دهی property‌ها میباشد.
public void SetName(string firstName, string lastName)
        {
            Apply(new AccountNameSetEvent { FirstName = firstName, LastName = lastName });
        }
و همچنین در خط  سوم و پس از repository.Save باعث میشود event‌های pending شده Raise شده و توسط متد Handle مربوط به EventHandler پردازش شده و عملیات‌های زیر انجام شوند:
public void Handle(AccountCreatedEvent domainEvent)
        {
            accountTable.Add(new FakeAccountTableRow { Id = domainEvent.AggregateRootId });
        }

        public void Handle(AccountNameSetEvent domainEvent)
        {
            var account = accountTable.Single(x => x.Id == domainEvent.AggregateRootId);
            account.Name = domainEvent.FirstName + " " + domainEvent.LastName;
        }
رکورد مورد نظر ثبت شده و event بعدی، پراپرتی‌هایش را مقدار دهی مینماید  و بصورت InMemory درون FakeAccountTable ذخیره میشود (پر واضح است که در یک پروژه‌ی واقعی به جای ذخیره شدن در یک Collection باید درون دیتایس واقعی ذخیره سازی شود).
و پس از اتمام عملیات انجام شده، بصورت زیر در Main برنامه اطلاعات ذخیره شده بازگردانده خواهد شد:
var accountReportReadModel = runtime.ServiceLocator.Resolve<AccountReportReadService>();
var accounts = accountReportReadModel.GetAccounts();

در ادامه برای مطالعه بیشتر میتوان به Scale out کردن این سیستم و استفاده از فریمورک‌های  messaging چون Redis یا Kafka پرداخت و همچنین اعمال Load Balancing را در اینگونه سیستم‌ها انجام داد.
نکته: Cqrs-Pattern را میتوانید از اینجا clone نمایید
مطالب
آشنایی با قابلیت FileStream اس کیوال سرور 2008 - قسمت سوم

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

alter table tbl_files set(filestream_on ='default')

go
alter table tbl_files
add

[systemfile] varbinary(max) filestream null ,
FileId uniqueidentifier not null rowguidcol unique default (newid())
go

در ادامه جدول tblFiles قسمت قبل را در نظر بگیرید:

CREATE TABLE [tblFiles](
[FileId] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[Title] [nvarchar](255) NOT NULL,
[SystemFile] [varbinary](max) FILESTREAM NULL,
UNIQUE NONCLUSTERED
(
[FileId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] FILESTREAM_ON [fsg1]

ALTER TABLE [dbo].[tblFiles] ADD DEFAULT (newid()) FOR [FileId]
GO

نحوه‌ی افزودن رکوردی جدید به جدول tblFiles :

INSERT INTO [tblFiles]
(
[Title],
[SystemFile]
)
VALUES
(
'file-1',
CAST('data data data' AS VARBINARY(MAX))
)
در اینجا سعی کرده‌ایم یک رشته ساده را در فیلدی از نوع فایل استریم ذخیره کنیم که روش کار به صورت فوق است. از آنجائیکه مقدار پیش فرض FileId را هنگام تعریف جدول به NEWID تنظیم کرده‌ایم، نیازی به ذکر آن نیست و به صورت خودکار محاسبه و ذخیره خواهد شد.
اگر کنجکاو باشید که این فایل اکنون کجا ذخیره شده و نحوه‌ی مدیریت آن توسط اس کیوال سرور به چه صورتی است، فقط کافی است به مسیری که هنگام افزودن گروه فایل‌ها و فایل مربوطه در تنظیمات خواص دیتابیس در قسمت قبل مشخص کردیم، مراجعه کرد (شکل زیر).



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

using System;
using System.IO;
using System.Data.SqlClient;
using System.Data;

namespace FileStreamTest
{
class CFS
{
/// <summary>
/// افزودن رکورد به جدول حاوی ستونی از نوع فایل استریم
/// </summary>
/// <param name="filePath">مسیر فایل</param>
/// <param name="title">عنوانی دلخواه</param>
public static void AddNewRecord(string filePath, string title)
{
//آیا فایل وجود دارد؟
if (!File.Exists(filePath))
throw new FileNotFoundException(
"لطفا مسیر فایل معتبری را مشخص نمائید", filePath);

//خواندن اطلاعات فایل در آرایه‌ای از بایت‌ها
byte[] buffer = File.ReadAllBytes(filePath);

using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();

//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//ساخت عبارت افزودن پارامتری
using (SqlCommand objSqlCmd = new SqlCommand(
"INSERT INTO [tblFiles]([Title],[SystemFile]) VALUES(@title , @file)",
objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.Text;

//تعریف وضعیت پارامترها و مقدار دهی آن‌ها
objSqlCmd.Parameters.AddWithValue("@title", title);
objSqlCmd.Parameters.AddWithValue("@file", buffer);

//اجرای فرامین
objSqlCmd.ExecuteNonQuery();
}

//پایان تراکنش
objSqlTran.Commit();
}
}
}

/// <summary>
/// دریافت اطلاعات فایل ذخیره شده به صورت آرایه‌ای از بایت‌ها
/// </summary>
/// <param name="fileId">کلید مورد استفاده</param>
/// <returns></returns>
public static byte[] GetDataFromDb(string fileId)
{
byte[] data = null;

using (SqlConnection objConn = new SqlConnection())
{
//کوئری اس کیوال پارامتری جهت دریافت محتویات فایل
string cmdText = "SELECT SystemFile FROM tblFiles WHERE FileId=@id";
using (SqlCommand objCmd = new SqlCommand(cmdText, objConn))
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objConn.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objConn.Open();

//تنظیم کردن وضعیت و مقدار پارامتر تعریف شده در کوئری
objCmd.Parameters.AddWithValue("@id", fileId);

//اجرای فرامین و دریافت فایل
using (SqlDataReader objread = objCmd.ExecuteReader())
{
if (objread != null)
if (objread.Read())
{
if (objread["SystemFile"] != DBNull.Value)
data = (byte[])objread["SystemFile"];
}
}
}
}

return data;
}
}
}

مثالی در مورد روش استفاده از کلاس فوق :

using System.IO;

namespace FileStreamTest
{
class Program
{
static void Main(string[] args)
{
CFS.AddNewRecord(@"C:\filest05.PNG", "test1");

//آی دی رکورد ذخیره شده در دیتابیس برای مثال
byte[] data = CFS.GetDataFromDb("BB848D45-382C-4D95-BF4E-52C3509407D4");
if (data != null)
{
File.WriteAllBytes(@"C:\tst.PNG", data);
}
}
}
}
روش فوق با روش متداول افزودن یک فایل به دیتابیس اس کیوال سرور هیچ تفاوتی ندارد و این‌جا هم بدون مشکل کار می‌کند. اطلاعات نهایی به صورت فایل‌هایی بر روی سیستم که توسط اس کیوال سرور مدیریت خواهند شد و با جدول شما یکپارچه‌اند، ذخیره می‌شوند.

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

CREATE PROCEDURE [AddFile](@Title NVARCHAR(255), @filepath VARCHAR(MAX) OUTPUT)
AS
BEGIN
SET NOCOUNT ON;

DECLARE @ID UNIQUEIDENTIFIER
SET @ID = NEWID()

INSERT INTO [tblFiles]
(
[FileId],
[title],
[SystemFile]
)
VALUES
(
@ID,
@Title,
CAST('' AS VARBINARY(MAX))
)

SELECT @filepath = SystemFile.PathName()
FROM tblFiles
WHERE FileId = @ID
END
GO

CREATE PROCEDURE [GetFilePath](@Id VARCHAR(50))
AS
BEGIN
SET NOCOUNT ON;

SELECT SystemFile.PathName()
FROM tblFiles
WHERE FileId = @ID
END
در رویه ذخیره شده AddFile ، ابتدا رکوردی بر اساس عنوان دلخواه ورودی با یک فایل خالی ایجاد می‌شود. سپس مسیر سیستمی این فایل را در آرگومان خروجی filepath قرار می‌دهیم. SystemFile.PathName از اس کیوال سرور 2008 جهت فیلدهای فایل استریم به اس کیوال سرور اضافه شده است. از این مسیر در برنامه خود جهت نوشتن بایت‌های فایل مورد نظر در آن توسط شیء SqlFileStream استفاده خواهیم کرد.
رویه ذخیره شده GetFilePath نیز تنها مسیر سیستمی فایل استریم ذخیره شده را بر می‌گرداند.
به این ترتیب کدهای برنامه به صورت زیر تغییر خواهند کرد:

using System.Data.SqlClient;
using System.Data;
using System.Data.SqlTypes;
using System.IO;

namespace FileStreamTest
{
class CFSqlFileStream
{
/// <summary>
/// افزودن رکورد به جدول حاوی ستونی از نوع فایل استریم
/// </summary>
/// <param name="filePath">مسیر فایل</param>
/// <param name="title">عنوانی دلخواه</param>
public static void AddNewRecord(string filePath, string title)
{
//آیا فایل وجود دارد؟
if (!File.Exists(filePath))
throw new FileNotFoundException(
"لطفا مسیر فایل معتبری را مشخص نمائید", filePath);

//خواندن اطلاعات فایل در آرایه‌ای از بایت‌ها
byte[] buffer = File.ReadAllBytes(filePath);

using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();

//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//استفاده از رویه ذخیره شده افزودن فایل
using (SqlCommand objSqlCmd = new SqlCommand(
"AddFile", objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.StoredProcedure;

//مشخص ساختن وضعیت و مقدار پارامتر عنوان
SqlParameter objSqlParam1 = new SqlParameter("@Title", SqlDbType.NVarChar, 255);
objSqlParam1.Value = title;

//مشخص ساختن پارامتر خروجی رویه ذخیره شده
SqlParameter objSqlParamOutput = new SqlParameter("@filepath", SqlDbType.VarChar, -1);
objSqlParamOutput.Direction = ParameterDirection.Output;

//افزودن پارامترها به شیء کامند
objSqlCmd.Parameters.Add(objSqlParam1);
objSqlCmd.Parameters.Add(objSqlParamOutput);

//اجرای رویه ذخیره شده
objSqlCmd.ExecuteNonQuery();

//و سپس دریافت خروجی آن
string Path = objSqlCmd.Parameters["@filepath"].Value.ToString();

//زمینه تراکنش فایل استریم موجود را دریافت کرده و از آن برای نوشتن محتویات فایل استفاده خواهیم کرد
//این مورد نیز یکی از تازه‌های اس کیوال سرور 2008 است
using (SqlCommand objCmd = new SqlCommand(
"SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()", objSqlCon, objSqlTran))
{
byte[] objContext = (byte[])objCmd.ExecuteScalar();
using (SqlFileStream objSqlFileStream =
new SqlFileStream(Path, objContext, FileAccess.Write))
{
objSqlFileStream.Write(buffer, 0, buffer.Length);
}
}
}

objSqlTran.Commit();
}
}
}

/// <summary>
/// دریافت اطلاعات فایل ذخیره شده به صورت آرایه‌ای از بایت‌ها
/// </summary>
/// <param name="fileId">کلید مورد استفاده</param>
/// <returns></returns>
public static byte[] GetDataFromDb(string fileId)
{
byte[] buffer = null;

using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();

//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//استفاده از رویه ذخیره شده دریافت مسیر فایل
using (SqlCommand objSqlCmd =
new SqlCommand("GetFilePath", objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.StoredProcedure;

//مشخص ساختن پارامتر ورودی رویه ذخیره شده و مقدار دهی آن
SqlParameter objSqlParam1 = new SqlParameter("@ID", SqlDbType.VarChar, 50);
objSqlParam1.Value = fileId;
objSqlCmd.Parameters.Add(objSqlParam1);

//اجرای رویه ذخیره شده و دریافت مسیر سیستمی فایل استریم
string path = string.Empty;
using (SqlDataReader sdr = objSqlCmd.ExecuteReader())
{
sdr.Read();
path = sdr[0].ToString();
}

//زمینه تراکنش فایل استریم موجود را دریافت کرده و از آن برای خواندن محتویات فایل استفاده خواهیم کرد
//این مورد نیز یکی از تازه‌های اس کیوال سرور 2008 است
using (SqlCommand objCmd = new SqlCommand(
"SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()", objSqlCon, objSqlTran))
{
byte[] objContext = (byte[])objCmd.ExecuteScalar();

using (SqlFileStream objSqlFileStream =
new SqlFileStream(path, objContext, FileAccess.Read))
{
buffer = new byte[(int)objSqlFileStream.Length];
objSqlFileStream.Read(buffer, 0, buffer.Length);
}
}
}

objSqlTran.Commit();
}
}

return buffer;
}
}
}
در پایان برای تکمیل بحث می‌توان به مقاله‌ی مرجع زیر مراجعه کرد:
FILESTREAM Storage in SQL Server 2008

مطالب دوره‌ها
بررسی مثال‌ها و جزئیات بیشتر تولید کدهای پویا توسط Reflection.Emit
نحوه معرفی متغیرهای محلی در Reflection.Emit

ابتدا مثال کامل ذیل را درنظر بگیرید:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static int Calculate(int a, int b, int c)
        {
            var result = a * b;
            return result - c;
        }

        static void Main(string[] args)
        {
            //روش متداول
            Console.WriteLine(Calculate(10, 2, 3));

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "CalculateMethod",
                                        returnType: typeof(int),
                                        parameterTypes: new[] { typeof(int), typeof(int), typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();

            il.Emit(opcode: OpCodes.Ldarg_0); // بارگذاری اولین آرگومان بر روی پشته ارزیابی 
            il.Emit(opcode: OpCodes.Ldarg_1); // بارگذاری دومین آرگومان بر روی پشته ارزیابی 
            il.Emit(opcode: OpCodes.Mul); // انجام عملیات ضرب
            il.Emit(opcode: OpCodes.Stloc_0); // ذخیره سازی نتیجه عملیات ضرب در یک متغیر محلی
            il.Emit(opcode: OpCodes.Ldloc_0); // متغیر محلی را بر روی پشته ارزیابی قرار می‌دهد تا در عملیات بعدی قابل استفاده باشد
            il.Emit(opcode: OpCodes.Ldarg_2); // آرگومان سوم را بر روی پشته ارزیابی قرار می‌دهد
            il.Emit(opcode: OpCodes.Sub); // انجام عملیات تفریق
            il.Emit(opcode: OpCodes.Ret); // بازگشت نتیجه

            //فراخوانی متد پویا
            var method = (Func<int, int, int, int>)myMethod.CreateDelegate(typeof(Func<int, int, int, int>));
            Console.WriteLine(method(10, 2, 3));

        }
    }
}
در این مثال سعی کرده‌ایم معادل متد Calculate را که در ابتدای برنامه ملاحظه می‌کنید، با کدهای IL تولید کنیم. روش کار مانند قسمت قبل است. ابتدا وهله‌ی جدیدی را از کلاس DynamicMethod جهت معرفی امضای متد پویای خود ایجاد می‌کنیم. در اینجا نوع خروجی را int و نوع سه پارامتر آن‌را به نحوی که مشخص شده است توسط آرایه‌ای از typeهای int معرفی خواهیم کرد. سپس محل قرارگیری کد تولیدی پویا مشخص می‌شود.
در ادامه توسط ILGenerator، آرگومان‌های دریافتی بارگذاری شده، در هم ضرب می‌شوند. سپس نتیجه در یک متغیر محلی ذخیره شده و سپس از آرگومان سوم کسر می‌گردد. در آخر هم این نتیجه بازگشت داده خواهد شد.
در اینجا روش سومی را برای کار با متدهای پویا مشاهده می‌کنید. بجای تعریف یک delegate به صورت صریح همانند قسمت قبل، از یک Func یا حتی Action نیز بنابر امضای متد مد نظر، می‌توان استفاده کرد. در اینجا از یک Func که سه پارامتر int را قبول کرده و خروجی int نیز دارد، استفاده شده است.
اگر برنامه را اجرا کنید ... کرش خواهد کرد! با استثنای ذیل:
 System.InvalidProgramException was unhandled
Message=Common Language Runtime detected an invalid program.
علت اینجا است که در حین کار با System.Reflection.Emit، نیاز است نوع متغیر محلی مورد استفاده را نیز مشخص نمائیم. اینکار را توسط فراخوانی متد DeclareLocal که باید پس از فراخوانی GetILGenerator، درج گردد، می‌توان انجام داد:
 il.DeclareLocal(typeof(int));
با این تغییر، برنامه بدون مشکل اجرا خواهد شد.


نحوه تعریف برچسب‌ها در Reflection.Emit

در ادامه قصد داریم یک مثال پیشرفته‌تر را بررسی کنیم.
        static int Calculate(int x)
        {
            int result = 0;
            for (int i = 0; i < 10; i++)
            {
                result += i * x;
            }
            return result;
        }
در اینجا می‌خواهیم کدهای معادل متد محاسباتی فوق را توسط امکانات System.Reflection.Emit و کدهای IL تولید کنیم.
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static int Calculate(int x)
        {
            int result = 0;
            for (int i = 0; i < 10; i++)
            {
                result += i * x;
            }
            return result;
        }

        static void Main(string[] args)
        {
            //روش متداول
            Console.WriteLine(Calculate(10));

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "CalculateMethod",
                                        returnType: typeof(int), // خروجی متد عدد صحیح است
                                        parameterTypes: new[] { typeof(int) }, // یک پارامتر عدد صحیح دارد
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();

            // از برچسب‌ها برای انتقال کنترل استفاده می‌شود
            // در اینجا به دو برچسب برای تعریف ابتدای حلقه
            // و همچنین برای پرش به جایی که متد خاتمه می‌یابد نیاز داریم
            var loopStart = il.DefineLabel();
            var methodEnd = il.DefineLabel();

            // variable 0; result = 0
            il.DeclareLocal(typeof(int)); //  برای تعریف متغیر محلی نتیجه عملیات
            il.Emit(OpCodes.Ldc_I4_0); // عدد ثابت صفر را بر روی پشته ارزیابی قرار می‌دهد
            il.Emit(OpCodes.Stloc_0); // و نهایتا این عدد ثابت به متغیر محلی انتساب داده خواهد شد

            // variable 1; i = 0
            il.DeclareLocal(typeof(int)); // در اینجا کار تعریف و مقدار دهی متغیر حلقه انجام می‌شود
            il.Emit(OpCodes.Ldc_I4_0); // عدد ثابت صفر را بر روی پشته ارزیابی قرار می‌دهد
            il.Emit(OpCodes.Stloc_1); // و نهایتا این عدد ثابت به متغیر حلقه در ایندکس یک انتساب داده خواهد شد

            // در اینجا کار تعریف بدنه حلقه شروع می‌شود
            il.MarkLabel(loopStart); // شروع حلقه را علامتگذاری می‌کنیم تا بعدا بتوانیم به این نقطه پرش نمائیم
            il.Emit(OpCodes.Ldloc_1); // در ادامه می‌خواهیم بررسی کنیم که آیا مقدار متغیر حلقه از عدد 10 کوچکتر است یا خیر
            il.Emit(OpCodes.Ldc_I4, 10); // عدد ثابت ده را بر روی پشته ارزیابی قرار می‌دهد
            // برای انجام بررسی‌های تساوی یا کوچکتر یا بزرگتر نیاز است ابتدا دو متغیر مدنظر بر روی پشته قرار گیرند
            il.Emit(OpCodes.Bge, methodEnd);  // اگر اینطور نیست و مقدار متغیر از 10 کمتر نیست، کنترل برنامه را به انتهای متد هدایت خواهیم کرد

            // i * x
            il.Emit(OpCodes.Ldloc_1); // مقدار متغیر حلقه را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Ldarg_0); // مقدار اولین آرگومان متد را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Mul); // انجام عملیات ضرب
            // نتیجه این عملیات اکنون بر روی پشته قرار گرفته است

            // result += 
            il.Emit(OpCodes.Ldloc_0); // متغیر نتیجه را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Add); // اکنون عملیات جمع بر روی نتیجه ضرب قسمت قبل که بر روی پشته قرار دارد و همچنین متغیر نتیجه انجام می‌شود
            il.Emit(OpCodes.Stloc_0); // ذخیره سازی نتیجه در متغیر محلی

            // i++
            // در اینجا کار افزایش متغیر حلقه انجام می‌شود
            il.Emit(OpCodes.Ldloc_1); // مقدار متغیر حلقه بر روی پشته قرار می‌گیرد
            il.Emit(OpCodes.Ldc_I4_1); // عدد ثابت یک بر روی پشته قرار می‌گیرد
            il.Emit(OpCodes.Add); // سپس این دو عدد بارگذاری شده با هم جمع خواهند شد
            il.Emit(OpCodes.Stloc_1); // نتیجه در متغیر حلقه ذخیره خواهد شد

            // مرحله بعد شبیه سازی حلقه با پرش به ابتدای برچسب آن است
            il.Emit(OpCodes.Br, loopStart);

            //در اینجا انتهای متد علامتگذاری شده است
            il.MarkLabel(methodEnd);
            il.Emit(OpCodes.Ldloc_0); // مقدار نتیجه بر روی پشته قرار داده شده
            il.Emit(OpCodes.Ret); // و بازگشت داده می‌شود

            //فراخوانی متد پویا
            var method = (Func<int, int>)myMethod.CreateDelegate(typeof(Func<int, int>));
            Console.WriteLine(method(10));
        }
    }
}
کد کامل معادل را به همراه کامنت گذاری سطر به سطر آن، ملاحظه می‌کنید. در اینجا نکته‌های جدید، نحوه تعریف برچسب‌ها و انتقال کنترل برنامه به آن‌ها هستند؛ جهت شبیه سازی حلقه و همچنین خاتمه آن و انتقال کنترل به انتهای متد.


فراخوانی متدها توسط کدهای پویای Reflection.Emit

در ادامه کدهای کامل یک مثال متد پویا را که متد print را فراخوانی می‌کند، ملاحظه می‌کنید:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        public static void print(int i)
        {
            Console.WriteLine("i: {0}", i);
        }

        static void Main(string[] args)
        {
            //روش متداول
            print(10);

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "myMethod",
                                        returnType: typeof(void),
                                        parameterTypes: null, // پارامتری ندارد
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();
            il.Emit(OpCodes.Ldc_I4, 10); // عدد ثابت 10 را بر روی پشته قرار می‌دهد
            // اکنون این مقدار بر روی پشته است و از آن می‌توان برای فراخوانی متد پرینت استفاده کرد
            il.Emit(OpCodes.Call, typeof(Program).GetMethod("print"));
            il.Emit(OpCodes.Ret);


            //فراخوانی متد پویا
            var method = (Action)myMethod.CreateDelegate(typeof(Action));
            method();
        }
    }
}
در اینجا از OpCode مخصوص فراخوانی متدها به نام Call که در قسمت‌های قبل در مورد آن بحث شد، استفاده گردیده است. برای اینکه امضای دقیقی را در اختیار آن قرار دهیم، می‌توان از Reflection استفاده کرد که نمونه‌ای از آن‌را در اینجا ملاحظه می‌کنید.
به علاوه چون خروجی امضای متد ما از نوع void است، اینبار delegate تعریف شده را از نوع Action تعریف کرده‌ایم و نه از نوع Func.


فراخوانی متدهای پویای Reflection.Emit توسط سایر متدهای پویای Reflection.Emit

فراخوانی یک متد پویای مشخص از طریق متد‌های پویای دیگر نیز همانند مثال قبل است:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static void Main(string[] args)
        {
            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "mulMethod",
                                        returnType: typeof(int),
                                        parameterTypes: new[] { typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0); // اولین آرگومان متد را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Ldc_I4, 42); // عدد ثابت 42 را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Mul); // ضرب این دو در هم
            il.Emit(OpCodes.Ret); // بازگشت نتیجه

            //فراخوانی متد پویا
            var method = (Func<int, int>)myMethod.CreateDelegate(typeof(Func<int, int>));
            Console.WriteLine(method(10));

            // فراخوانی متد پویای فوق در یک متد پویای دیگر
            var callerMethod = new DynamicMethod(
                                        name: "callerMethod",
                                        returnType: typeof(int),
                                        parameterTypes: new[] { typeof(int), typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var callerMethodIL = callerMethod.GetILGenerator();
            callerMethodIL.Emit(OpCodes.Ldarg_0); // پارامتر اول متد را بر روی پشته قرار می‌دهد
            callerMethodIL.Emit(OpCodes.Ldarg_1); // پارامتر دوم متد را بر روی پشته قرار می‌دهد
            callerMethodIL.Emit(OpCodes.Mul); // ضرب این دو در هم
            //حاصل ضرب اکنون بر روی پشته است که در فراخوانی بعدی استفاده می‌شود
            callerMethodIL.Emit(OpCodes.Call, myMethod); // فراخوانی یک متد پویای دیگر
            callerMethodIL.Emit(OpCodes.Ret);

            //فراخوانی متد پویای جدید
            var method2 = (Func<int, int, int>)callerMethod.CreateDelegate(typeof(Func<int, int, int>));
            Console.WriteLine(method2(10, 2));
        }
    }
}
در مثال فوق ابتدا یک متد پویای ضرب را تعریف کرده‌ایم که عددی صحیح را دریافت و آن‌را در 42 ضرب می‌کند و نتیجه را بازگشت می‌دهد.
سپس متد پویای دومی تعریف شده است که دو عدد صحیح را دریافت و این دو را در هم ضرب کرده و سپس نتیجه را به عنوان پارامتر به متد پویای اول ارسال می‌کند.
هنگام فراخوانی OpCodes.Call، پارامتر دوم باید از نوع MethodInfo باشد. نوع یک DynamicMethod نیز همان MethodInfo است. بنابراین برای فراخوانی آن، کار خاصی نباید انجام شود و صرفا ذکر نام متغیر مرتبط با مد پویای مدنظر کفایت می‌کند.
مطالب
استفاده از خواص راهبری در EF Code first جهت ساده سازی کوئر‌ی‌ها
گاهی از اوقات یافتن معادل LINQ کوئری‌های SQLایی که پیشتر به سادگی و بر اساس ممارست، در کسری از دقیقه نوشته می‌شدند، آنچنان ساده نیست. برای مثال فرض کنید یک سری پروژه وجود دارند که به ازای هر پروژه، تعدادی بازخورد ثبت شده است. هر بازخورد نیز دارای وضعیت‌هایی مانند «در حال انجام» و «انجام شد» است. می‌خواهیم کوئری LINQ سازگار با EF ایی را تهیه کنیم که تعداد موارد «در حال انجام» را نمایش دهد.
بر این اساس، کلاس‌های مدل دومین مساله به صورت زیر خواهند بود:
    public class Project
    {
        public int Id { set; get; }
        public string Name { set; get; }

        public virtual ICollection<ProjectIssue> ProjectIssues { set; get; }
    }

    public class ProjectIssue
    {
        public int Id { set; get; }
        public string Body { set; get; }

        [ForeignKey("ProjectStatusId")]
        public virtual ProjectIssueStatus ProjectIssueStatus { set; get; }
        public int ProjectStatusId { set; get; }

        [ForeignKey("ProjectId")]
        public virtual Project Project { set; get; }
        public int ProjectId { set; get; }
    }

    public class ProjectIssueStatus
    {
        public int Id { set; get; }
        public string Name { set; get; }

        public virtual ICollection<ProjectIssue> ProjectIssues { set; get; }
    }
یک پروژه می‌تواند تعدادی Issue ثبت شده داشته باشد. هر Issue نیز دارای وضعیتی مشخص است.
اگر EF Code first را وادار به تهیه جداول و روابط معادل کلاس‌های فوق کنیم:
    public class MyContext : DbContext
    {
        public DbSet<ProjectIssueStatus> ProjectStatus { get; set; }
        public DbSet<ProjectIssue> ProjectIssues { get; set; }
        public DbSet<Project> Projects { get; set; }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            var project1 = new Project { Name = "پروژه جدید" };
            context.Projects.Add(project1);

            var stat1 = new ProjectIssueStatus { Name = "درحال انجام" };
            var stat2 = new ProjectIssueStatus { Name = "انجام شد" };
            context.ProjectStatus.Add(stat1);
            context.ProjectStatus.Add(stat2);

            var issue1 = new ProjectIssue
            {
                Body = "تغییر قلم گزارش",
                ProjectIssueStatus = stat1,
                Project = project1
            };
            var issue2 = new ProjectIssue
            {
                Body = "تغییر لوگوی گزارش",
                ProjectIssueStatus = stat1,
                Project = project1
            };
            context.ProjectIssues.Add(issue1);
            context.ProjectIssues.Add(issue2);

            base.Seed(context);
        }
    }
 به شکل زیر خواهیم رسید:


سابقا برای یافتن تعداد متناظر با هر IssueStatus خیلی سریع می‌شد چنین کوئری را نوشت:


اما اکنون معادل آن با EF Code first چیست؟
    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());

            using (var ctx = new MyContext())
            {
                var projectId = 1;
                var list = ctx.ProjectStatus.Select(x => new
                                                    {
                                                        Id = x.Id,
                                                        Name = x.Name,
                                                        Count = x.ProjectIssues.Count(p => p.ProjectId == projectId)
                                                    }).ToList();
                foreach (var item in list)
                    Console.WriteLine("{0}:{1}",item.Name, item.Count);                
            }
        }
    }
بله. همانطور که ملاحظه می‌کنید در اینجا به کوئری بسیار ساده و واضحی با کمک استفاده از navigation properties (خواص راهبری مانند ProjectIssues) تعریف شده رسیده‌ایم. خروجی SQL تولید شده توسط EF نیز به صورت زیر است:
SELECT [Project1].[Id] AS [Id],
       [Project1].[Name] AS [Name],
       [Project1].[C1] AS [C1]
FROM   (
           SELECT [Extent1].[Id] AS [Id],
                  [Extent1].[Name] AS [Name],
                  (
                      SELECT COUNT(1) AS [A1]
                      FROM   [dbo].[ProjectIssues] AS [Extent2]
                      WHERE  ([Extent1].[Id] = [Extent2].[ProjectStatusId])
                             AND ([Extent2].[ProjectId] = 1 /*@p__linq__0*/)
                  ) AS [C1]
           FROM   [dbo].[ProjectIssueStatus] AS [Extent1]
       ) AS [Project1]

مطالب
برنامه نویسی موازی بخش دوم (محافظت از مقادیر مشترک)
 در بخش قبلی، مروری کلی بر مفاهیم اصلی برنامه نویسی موازی، از جمله شرایط و نکات استفاده از آن را بررسی کردیم. در انتهای بخش اول عنوان کردیم که در روند برنامه نویسی موازی، اگر دو یا چند Thread به طور مشترک به داده‌ای دسترسی داشته باشند، امکان بروز Race condition وجود خواهد داشت. پس باید کد خود را Thread Safe کنیم. می‌توان برای کنترل رفتارهای عجیب اشیاء در محیط‌های Multi Thread، عنوان Thread Safety را بکار برد.

به طور کلی ۴ روش در #C برای ایجاد Thread Safety وجود دارند:


1- Lock/Monitor
این دو روش یکسان هستند و مانند هم عمل می‌کنند. در واقع در ابتدا روش Monitor وجود داشته و بعد روش lock برای کوتاهی syntax، به صورت بلاکی به #C افزوده شده‌است. این روش تنهای بر روی Thread‌های داخلی App Domain کنترل دارد (اجازه ورود یک Thread) و نمی‌تواند بر روی Thread‌های خارج از این حوزه در محیط‌های Multi Thread محدودیتی اعمال نماید. منظور از Thread‌های داخلی، Thread هایی هستند که داخل Application ما ایجاد شده‌اند.

به تکه کد زیر توجه کنید:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

 class Program
    {
        static int a = 0;
        static int b = 0;
        static Random random = new Random();
        
        static void Main(string[] args)
        {

            Thread obj = new Thread(Division);
            obj.Start();

            Division();
        }

        static void Division()
        {

            for (int i = 0; i <= 500; i++)
            {

                try
                {
                   
                        //Choosing random numbers between 1 to 5
                        a = random.Next(1, 10);
                        b = random.Next(1, 10);


                        //Dividing
                        double ans = a / b;


                        //Reset Variables
                        a = 0;
                        b = 0;

                        Console.WriteLine("Answer : {0} --> {1}", i, ans);
                    
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
    }

همانطور که در کد بالا ملاحظه می‌کنید، متد Division به صورت Thread Safe پیاده سازی نشده‌است! اما مشکل کجاست!؟

با برسی این متد و عملکرد آن متوجه می‌شویم که این متد در یک چرخه‌ی تکرار ۵۰۰ مرتبه‌ای، دو عدد تصادفی را در بازه‌ی ۱ تا ۱۰، انتخاب کرده و آن‌ها را بر هم تقسیم و متغیر‌های تصادفی را با مقدار ۰ پر می‌کند. همین عمل Reset Variable در این متد، باعث بروز خطا در محیط Multi Thread خواهد شد. بدین صورت که اگر این متد مانند مثال بالا توسط دو Thread مجزا فراخوانی شود، یکبار توسط New Thread و بلافاصله در Thread اصلی Application، احتمال این وجود خواهد داشت که در Thread دوم، بعد از انتخاب دو مقدار تصادفی و درست قبل از عملیات تقسیم، به طور همزمان Thread اول عملیات Reset Variable را انجام دهد که باعث بروز خطای تقسیم بر ۰ در Thread دوم می‌شود. این همان مشکلی است که گاها یافتن آن از طریق Debug بسیار دشوار خواهد بود.
اما با تغییر کد به شکل زیر
class Program
    {
        static int a = 0;
        static int b = 0;
        static Random random = new Random();
        static readonly object _object = new object();
        static void Main(string[] args)
        {

            Thread obj = new Thread(Division);
            obj.Start();

            Division();
        }

        static void Division()
        {

            for (int i = 0; i <= 500; i++)
            {

                try
                {
                    Monitor.Enter(_object);
                   
                        //Choosing random numbers between 1 to 5
                        a = random.Next(1, 10);
                        b = random.Next(1, 10);


                        //Dividing
                        double ans = a / b;


                        //Reset Variables
                        a = 0;
                        b = 0;

                        Console.WriteLine("Answer : {0} --> {1}", i, ans);
                    Monitor.Exit(_object);

                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
    }

مادامی که یک Thread در حالت انتخاب اعداد تصادفی تا تقسیم و اعلام نتیجه می‌باشد، به Thread‌های داخلی دیگر، اجازه‌ی ورود به این بخش که تحت کنترل Monitor می‌باشد داده نخواهد شد. همانطور که گفته شده، بازه‌ی تحت کنترل مانیتور میتواند با بلاک Lock(object) جایگزین شود. شیء object یک شیء مشترک (static) میان تمام اشیاء است برای کنترل ورود Thread‌ها و قفل گزاری مشترک بین این اشیاء.

2- Mutex:
این نوع قفل گزاری به منظور محافظت منابع مشترک برای جلوگیری از ورود Thread‌های بیرونی استفاده می‌شود. منظور از Thread‌های بیرونی Thread‌های یک کامپیوتر است. همچنین می‌توان از Mutex بجای lock نیز استفاده کرد؛ اما به دلیل هدف کاری Mutex، باید هزینه‌ی بیشتری (تقریبا 50 برابر کندتر از Lock) پرداخت کرد.
 static void Main()
  { 
    using (var mutex = new Mutex (false, "dotnettips.info Demo"))
    {
     
      if (!mutex.WaitOne (TimeSpan.FromSeconds (3), false))
      {
        Console.WriteLine ("Another app instance is running. Bye!");
        return;
      }
      RunProgram();
    }
  }
 
  static void RunProgram()
  {
    Console.WriteLine ("Running. Press Enter to exit");
    Console.ReadLine();
  }
در مثال بالا از یک Mutex نام دار استفاده شده است که به ما این امکان را می‌دهد تا به صورت Computer-Wide روی Thread‌ها ایجاد محدودیت نماییم. اگر متد بالا را در دو ترمینال اجرا کنید، نسخه‌ی دوم اجرا نخواهد شد. البته این نکته را در نظر داشته باشید که این امکان در سیتم عامل‌های مبتنی بر Linux غیرفعال است .
Mutex دارای دو متد مهم است :

۱- WaiteOne : شروع Blocking با این متد خواهد بود و اگر بتواند عملیات blocking را انجام دهد مقدار True را باز می‌گرداند. این متد دارای دو ورودی دیگر نیز هست که در مقالات بعدی به طور مفصل به آن‌ها اشاره خواهد شد. اما بطور خلاصه می‌توان اینگونه عنوان نمود که یک پارامتر زمان وجود دارد که مدت زمان انتظار برای Blocking را مشخص می‌کند و پارامتر Boolean دیگری که در حالت synchronization مورد استفاده قرار می‌گیرد و خروج و یا عدم خروج از دامنه synchronization را مشخص می‌کند.

۲- ReleaseMutex : شروع آزاد سازی انحصار، با این متد انجام می‌شود.

هیچگاه نباید یک Mutex را در کد رها کرد؛ زیرا باعث به‌وجود آمدن خطاهایی در کد خواهد شد. روش‌هایی برای رها سازی وجود دارد مانند Dispose کردن Mutex و یا استفاده از متد ReleaseMutex. قبل از خروج از کد باید دقت داشت در بخش هایی از کد که از این نوع قفل گزاری استفاده شده‌است، حتما باید مکانیسم‌های Exception Handling و یا Disposing را برای مدیریت Mutex ایجاد شده اعمال کرد.

3 -Semaphore 
یک نسخه پیشرفته‌تر از Mutex است که می‌تواند برای Thread‌های داخلی و یا خارجی استفاده شود و روی آنها اعمال محدودیت کند. همچنین می‌تواند اجازه‌ی ورود یک تا چند Thread را به بخشی از کد، برای محافظت از منابع بدهد. Semaphore نیز مانند Mutex دارای متد‌های Wait و Release است. یک Semaphore با ظرفیت ورود یک Thread در لحظه همان Mutex است. همچنین از Semaphore‌‌ها می‌توان در متدهای Async نیز استفاده کرد.

4- SemaphoreSlim
در واقع یک نسخه‌ی پیشرفته از Monitor و یک نسخه‌ی سبک وزن از Semaphore است و به همان شکل به شما اجازه‌ی محدودیت گزاری فقط بر روی Thread‌های داخلی را می‌دهد. اما بجای اجازه‌ی ورود فقط یک Thread، به شما این امکان را می‌دهد که اجازه‌ی ورود همزمان یک یا چند Thread را به انتخاب خود بدهید.

هزینه‌ی اعمال محدودیت (قفل گزاری) روی Thread ها
به طور کل هزینه‌ی قفل گزاری بر روی Thread‌ها بالاست. اما در صورت نیاز باید انتخاب درستی از بین موارد عنوان شده را انتخاب نمود. lock/Monitor و SemaphoreSlim دارای کمترین هزینه و Mutex و Semaphore دارای بیشترین هزینه و سربار هستند. اگر در Application‌های بزرگ از Mutex و Semaphore به درستی استفاده نشود، به جد باعث کندی خواهد شد.

در بخش بعدی مقاله، Double-checked locking را مورد بررسی قرار خواهیم داد.
مطالب
ویژگی های کمتر استفاده شده در NET. - بخش هفتم

DebuggerStepThroughAttribute

ویژگی DebuggerStepThroughAttribute باعث می‌شود که در زمان دیباگ کردن کد، با کلید F11، متدهایی که این ویژگی را دارند، بدون رفتن به داخل متد (همانند دیباگ با کلید F10 عمل می‌کند، به جز زمانی که در داخل متد break point گذاشته باشید) ، تنها اجرا می‌شوند.
به مثال زیر توجه کنید:
class Program
{
    public static void Main(string[] args)
    {
        DebuggerStepThroughMethod1();
    }

    [DebuggerStepThrough]
    public static void DebuggerStepThroughMethod1()
    {
        Console.WriteLine( "Method 1" );
        DebuggerStepThroughMethod2();
    }

    [DebuggerStepThrough]
    public static void DebuggerStepThroughMethod2()
    {
        Console.WriteLine( "Method 2" );
    }
}
و نتیجه دیباگ با استفاده از F11 به صورت زیر می‌شود:

همانطور که مشاهده می‌کنید برنامه را با کلید F11 اجرا کردم. بعد از ورود به Method1، با زدن کلید F11 دستور بعدی، break point درون Method2 است.

ConditionalAttribute

شما با استفاده از Conditional می توانید اجرای یک متد را به شناساننده پیش پردازشی ( pre-processing identifier ) وابسته کنید. ConditionalAttribute می‌تواند بر روی یک کلاس یا یک متد بکار برده شود.
class Program
{
    public static void Main(string[] args)
    {
        DebugMode();
    }

    [Conditional("DEBUG")]
    public static void DebugMode()
    {
        Console.WriteLine( "Debug mode" );
    }
}
در صورتی که مثال بالا را در حالت Debug اجرا کنید، خروجی کنسول پیام Debug mode است و در صورتی که در حالت Release اجرا کنید، متد DebugMode اجرا نخواهد شد.
نکته: شما می‌توانید با استفاده از دستور define# (در بیرون از فضای نام) مقدار سفارشی خود را تعریف کنید.
#define ReleaseMode


Flags Enum Attribute

ویژگی Flags برای پوشش فیلدهای بیتی و انجام مقایسه بیتی استفاده می‌شود. از این ویژگی باید برای زمانیکه یک داده شمارشی می‌تواند چندین مقدار را به صورت همزمان داشته باشد، استفاده کرد.
[System.Flags]
public enum Permission
{
    View = 1,
    Insert = 2,
    Update = 4,
    Delete = 8
}
این نکته خیلی مهم است که Flags به صورت خودکار، مقادیر enum را به توان دو نمی‌رساند و شما باید به صورت دستی این مقادیر را تعیین کنید. در صورتیکه مقادیر عددی را تعیین نکنید، enum در عملیات بیتی به درستی کار نخواهد کرد، چرا که مقدار enum از 0 شروع می‌شود و افزایش پیدا می‌کند.  
public static void Main( string[] args )
{
    var permission = ( Permission.View | Permission.Insert ).ToString();
    Console.WriteLine( permission ); // Displays ‘View, Insert’

    var userPermission = Permission.View | Permission.Insert | Permission.Update | Permission.Delete;
    // To retrieve the value from property you can do this
    if ( ( userPermission & Permission.Delete ) == Permission.Delete )
    {
        Console.WriteLine( "کاربر دارای مجوز دسترسی به عملیات حذف می‌باشد" );
    }

    // In .NET 4 and later
    Console.WriteLine( userPermission.HasFlag( Permission.Delete )
                            ? "کاربر دارای مجوز دسترسی به عملیات حذف می‌باشد"
                            : "کاربر مجوز دسترسی به عملیات حذف را ندارد");
}

نکته: در صورتیکه مقداری را برای enum تعریف کرده باشید، نمی‌توانید آن را با مقدار 0 مشخص کنید (در زمانی که ویژگی flags را بر روی enum اضافه کرده باشید)، چرا که با استفاده از عملیات بیتی AND نمی‌توانید دارا بودن آن مقدار را تست کنید و همیشه نتیجه صفر خواهد بود.


Dynamically Compile and Execute C# Code

CodeDOM

با استفاده از CodeDOM می‌توانید یک سورس کد را به صورت یک فایل اسمبلی کامپایل و ذخیره کنید.
public static void Main( string[] args )
{
    var sourceCode = @"class DotNetTips
                        {
                            public void Print()
                            {
                                System.Console.WriteLine("".Net Tips"");
                            }
                        }";
    var compiledAssembly = CompileSourceCodeDom( sourceCode );
    ExecuteFromAssembly( compiledAssembly );
}

static Assembly CompileSourceCodeDom( string sourceCode )
{
    CodeDomProvider csharpCodeProvider = new CSharpCodeProvider();
    var cp = new CompilerParameters
                {
                    GenerateExecutable = false
                };
    cp.ReferencedAssemblies.Add( "System.dll" );
    var cr = csharpCodeProvider.CompileAssemblyFromSource( cp,
                                                            sourceCode );
    return cr.CompiledAssembly;
}
همانطور که در مثال بالا مشاهده می‌کنید، متغیر sourceCode حاوی کد مربوط به یک کلاس #C می‌باشد که یک متد Print در آن تعریف شده است.


Roslyn

سکوی کامپایلر دات نت " Roslyn "،  کامپایلرهای متن باز #C و  VB.NET را به همراه APIهای تجزیه و تحلیل کد ارائه کرده است که با استفاده از این APIها می‌توان ابزارهای آنالیز کد جهت استفاده در ویژوال استودیو را ایجاد کرد.

برای استفاده از Roslyn باید این کتابخانه را نصب کنید

Install-Package Microsoft.CodeAnalysis

حال مثال قبل را با استفاده از Roslyn بازنویسی می‌کنیم:

public static void Main(string[] args)
{
    var sourceCode = @"class DotNetTips
                        {
                            public void Print()
                            {
                                System.Console.WriteLine("".Net Tips"");
                            }
                        }";
    var compiledAssembly = CompileSourceRoslyn( sourceCode );
    ExecuteFromAssembly( compiledAssembly );
}

private static Assembly CompileSourceRoslyn(string sourceCode)
{
    using ( var memoryStream = new MemoryStream() )
    {
        var assemblyFileName = string.Concat( Guid.NewGuid().ToString(),
                                                ".dll" );
        var compilation = CSharpCompilation.Create( assemblyFileName,
                                                    new[]
                                                    {
                                                        CSharpSyntaxTree.ParseText( sourceCode )
                                                    },
                                                    new[]
                                                    {
                                                        MetadataReference.CreateFromFile( typeof( object ).Assembly.Location )
                                                    },
                                                    new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary ) );
        compilation.Emit( memoryStream );
        var assembly = Assembly.Load( memoryStream.GetBuffer() );
        return assembly;
    }
}

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

static void ExecuteFromAssembly( Assembly assembly )
{
    var helloKittyPrinterType = assembly.GetType( "DotNetTips" );
    var printMethod = helloKittyPrinterType.GetMethod( "Print" );
    var kitty = assembly.CreateInstance( "DotNetTips" );
    printMethod.Invoke( kitty,
                        BindingFlags.InvokeMethod,
                        null,
                        null,
                        CultureInfo.CurrentCulture );
}


مطالب
چگونه نرم افزارهای تحت وب سریعتری داشته باشیم؟ قسمت پنجم
15.استفاده از using
اگر از objectهایی استفاده می‌کنید که interface مربوط به IDisposable را پیاده سازی کرده اند، حتما از عبارت using استفاده کنید. استفاده از دستور using باعث می‌شود زمانی که دیگر نیازی به object شما نباشد، به صورت خودکار از حافظه حذف شود و در روال جمع آوری زباله (GC) قرار گیرد. این عمل باعث حداقل رسیدن احتمال نشت حافظه در نرم افزار شما می‌شود. برای مثال:
using System;
using System.Text;

class Program
{
    static void Main()
    {
// Use using statement with class that implements Dispose.
using (SystemResource resource = new SystemResource())
{
    Console.WriteLine(1);
}
Console.WriteLine(2);
    }
}

class SystemResource : IDisposable
{
    public void Dispose()
    {
// The implementation of this method not described here.
// ... For now, just report the call.
Console.WriteLine(0);
    }
}
برای اطلاعات بیشتر می‌توانید از این مقاله  استفاده کنید.

16.اطلاعات ارسالی توسط شبکه را به حداقل برسانید
حجم اطلاعات ارسالی به شبکه را به حداقل برسانید. ارسال اطلاعات در شبکه به معنی گذر اطلاعات شما از 7 لایه مختلف شبکه در رایانه شما، گذر از media شبکه، گذر مجدد از 7 لایه شبکه در رایانه مقصد می‌باشد. به این معنی که هرچه اطلاعات بیشتری در شبکه ارسال کنید، سربار بیشتری متوجه سیستم شما خواهد بود. برای رفع این مشکل از فشرده سازهای css و javascript استفاده کنید. این فشرده سازها فواصل خالی، دستورات اضافی و... را از کد شما حذف و حجم آن را به حداقل می‌رسانند.
کم کردن تعداد درخواست‌ها و در نتیجه کم کردن تعداد فایل‌های ارسالی از سرور به کاربر نیز حربه ای در این زمینه می‌باشد.
برای مقایسه فشرده سازها به صورت آنلاین و استفاده از بهترین آنها (متناسب کد شما) می‌توانید از این سایت  استفاده کنید.
امکانات توکاری هم وجود دارد که در زمان اجرای برنامه css و javascript شما را فشرده و تلفیق می‌کند ولی با توجه به اینکه برای سرور در هر مرتبه فراخوانی سربار دارد (حتی در صورت کش کردن) اکیدا توصیه می‌شود از فشرده سازها قبل از اجرای برنامه (Pre-Compressed) به جای فشرده سازهای زمان اجرا (Run-time Compression)استفاده کنید.
مطالب
تغییرات اعمال شده در C++11 قسمت اول (enum)
نوع شمارشی enum
نوع شمارشی، یک نوع صحیح است و شامل لیستی از ثوابت می‌باشد که توسط برنامه نویس مشخص می‌گردد . انواع شمارشی برای تولید کد  خودمستند  به کار می‌روند یعنی کدی که به راحتی قابل درک باشد و نیاز به توضیحات اضافه نداشته باشد. زیرا به راحتی توسط نام ، نوع کاربرد و محدوده مقادیرشان قابل درک می‌باشند . مقادیر نوع شمارشی منحصربه فرد می‌باشند (unique) و شامل مقادیر تکراری نمی‌باشند در غیر این صورت  کامپایلر خطای مربوطه را هشدار میدهد . نحوه تعریف نوع شمارشی :
enum typename{enumerator-list}
enum کلمه کلیدی ست ، typename  نام نوع جدید است که برنامه نویس مشخص میکند و enumerator-list مجموعه مقادیری ست که این نوع جدید می‌تواند داشته باشد بعنوان مثال :
enum Day{SAT,SUN,MON,TUE,WED,THU,FRI}
اکنون Day  یک نوع جدید است و متغیرهایی که از این نوع تعریف می‌شوند میتوانند یکی از مقادیر مجموعه فوق را دارا باشند .
Day  day1,day2; 
day1 = SAT; 
day2 = SUN;
مقادیرSAT و SUN و MON  هر چند که به همین شکل بکار میروند ولی در رایانه به شکل اعداد صحیح  0 , 1 , 2 , ... ذخیره می‌شوند . به همین دلیل است که به هر یک از مقادیر SAT و SUN و ...  یک شـمارشـگر می‌گویند . وقتی فهرست شمارشگرهای یک نوع تعریف شد به طور خودکار مقادیر 0 و 1 و ... به ترتیب به آنها اختصاص داده میشود . می‌توان مقادیر صحیح دلخواهی به شمارشگرها نسبت داد به طور مثال :
enum Day{SAT=1,SUN=2,MON=4,TUE=8,WED=16,THU=32,FRI=64}
اگر چند شمارشگر مقدار دهی شده باشند آنگاه شمارشگرهایی که مقدار دهی نشده اند ، مقادیر متوالی بعدی را خواهند گرفت .
enum Day{SAT=1,SUN,MON,TUE,WED,THU,FRI}
دستور بالا مقادیر 1 تا 7 را بترتیب به شمارشگرها اختصاص میدهد .
میتوان به شمارشگرها مقادیر یکسانی نسبت داد
enum Answer{NO=0,FALSE=0,YES=1,TRUE=1,OK=1}
ولی نمی‌توان نامهای یکسانی را در نظر گرفت  ! تعریف زیر بدلیل استفاده مجدد از شمارشگر YES با خطای کامپایلر مواجه می‌شویم .
enum Answer{NO=0,FALSE=0,YES=1,YES=2,OK=1}
چند دلیل استفاده از نوع شمارشی عبارت است از :
1- enum سبب میشود که شما مقادیر مجاز و قابل انتظار را به متغیرهایتان نسبت دهید .
2- enum  اجازه میدهد با استفاده از نام به مقدار دستیابی پیدا کنید پس کدهایتان خواناتر میشود .
3- با استفاده از enum  تایپ کدهایتان سریع میشود زیرا IntelliSense در مورد انتخاب گزینه مناسب شما را یاری میدهد .

چند تعریف از enum :
enum Color{RED,GREEN,BLUE,BLACK,ORANGE} 
enum Time{SECOND,MINUTE,HOUR} 
enum Date{DAY,MONTH,YEAR} 
enum Language{C,DELPHI,JAVA,PERL} 
enum Gender{MALE,FEMALE}
تا اینجا خلاصه ای از enum و مفهوم آن داشتیم

اما تغییراتی که در  c++11 اعمال شده : Type-Safe Enumerations 

فرض کنید دو enum  تعریف کرده اید و به شکل زیر می‌باشد
enum Suit {Clubs, Diamonds, Hearts, Spades};
enum Jewels {Diamonds, Emeralds, Opals, Rubies, Sapphires};
اگر این دستورات را کامپایل کنید با خطا مواجه می‌شوید چون در هر دو enum  شمارشگر Diamonds تعریف شده است . کامپایلر اجازه تعریف جدیدی از یک شمارشگر در enum  دیگری نمیدهد هر چند برخی اوقات مانند مثال بالا نیازمند تعریف یک شمارشگر در چند enum  بر حسب نیاز میباشیم .
برای تعریف جدیدی که در  c++11 داده شده کلمه کلیدی  class  بعد از کلمه enum  مورد استفاده قرار میگیرد . به طور مثال تعریف دو enum  پیشین که با خطا مواجه میشد بصورت زیر تعریف میشود و از کامپایلر خطایی دریافت نمیکنیم .
enum class Suit {Clubs, Diamonds, Hearts, Spades};
enum class Jewels {Diamonds, Emeralds, Opals, Rubies, Sapphires};
همچنین استفاده از enum در گذشته و تبدیل آن به شکل زیر بود :
enum Suit {Clubs, Diamonds, Hearts, Spades};
Suit var1 = Clubs;
int var2= Clubs;
یک متغیر از نوع Suit بنام var1  تعریف میکنیم و شمارشگر Clubs را به آن نسبت میدهیم ، خط بعد متغیری از نوع int تعریف نمودیم و مقدار شمارشگر Clubs که 0 می‌باشد را به آن نسبت دادیم . اما اگر تعریف enum  را با قوائد C++11  در نظر بگیریم این نسبت دادنها باعث خطای کامپایلر میشود و برای نسبت دادن صحیح باید به شکل زیر عمل نمود .
enum class Jewels {Diamonds, Emeralds, Opals, Rubies, Sapphires};
Jewels typeJewel = Jewels::Emeralds;
int suitValue = static_cast<int>(typeJewel);
همانطور که مشاهده میکنید ، Type-Safe یودن enum  را نسبت به تعریف گذشته آن مشخص می‌باشد .
یک مثال کلی و جامع‌تر :
// Demonstrating type-safe and non-type-safe enumerations
#include <iostream>
using std::cout;
using std::endl;
// You can define enumerations at global scope
//enum Jewels {Diamonds, Emeralds, Rubies}; // Uncomment this for an error
enum Suit : long {Clubs, Diamonds, Hearts, Spades};
int main()
{
// Using the old enumeration type...
Suit suit = Clubs; // You can use enumerator names directly
Suit another = Suit::Diamonds; // or you can qualify them
// Automatic conversion from enumeration type to integer
cout << "suit value: " << suit << endl;
cout << "Add 10 to another: " << another + 10 << endl;
// Using type-safe enumerations...
enum class Color : char {Red, Orange, Yellow, Green, Blue, Indigo, Violet};
Color skyColor(Color::Blue); // You must qualify enumerator names
// Color grassColor(Green); // Uncomment for an error
// No auto conversion to numeric type
cout << endl
<< "Sky color value: "<< static_cast<long>(skyColor) << endl;
//cout << skyColor + 10L << endl; // Uncomment for an error
cout << "Incremented sky color: "
<< static_cast<long>(skyColor) + 10L // OK with explicit cast
<< endl;
return 0;
}
مطالب
بهبود کارآیی نمایش لیست‌ها در Blazor با استفاده از دایرکتیو key@
اگر پیشتر با React کار کرده باشید، احتمالا چنین پیام خطایی را دریافت کرده‌اید:


در اینجا React عنوان می‌کند که هر عنصر از لیستی را که در حال نمایش آن هستید، باید به همراه یک key، ارائه دهید. اما ... این key چیست؟
زمانیکه حالت کامپوننتی تغییر می‌کند (شیء یا اشیایی که به عناصر UI متصل هستند، تغییر می‌کنند)، React، درخت جدیدی از اشیایی را که باید رندر شوند، تولید می‌کند. اکنون React باید محاسبه کند که چه عناصری نسبت به درخت فعلی که در حال نمایش است، تغییر کرده‌اند تا فقط آن‌ها را به DOM اصلی اعمال کند؛ تا این تغییرات به کاربر نمایش داده شوند و ... این کار را هم به خوبی انجام می‌دهد. پس مشکل با لیست‌ها چیست که نیاز به key دارند؟
فرض کنید رندر لیستی، خروجی زیر را تولید می‌کند:
<li>Item 1</li>
<li>Item 2</li>
اکنون یک المان را به انتهای این لیست اضافه می‌کنیم:
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
در این حالت React به خوبی تشخیص می‌دهد که المان سومی به لیست اضافه شده‌است و فقط آن‌را رندر می‌کند؛ بجای اینکه کل لیست را مجددا رندر کند. اما اگر نحوه‌ی اضافه شدن المان چهارمی به لیست جدید، به صورت زیر باشد:
<li>Item 0</li> // <= New item
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
یعنی این المان در ابتدای لیست، اضافه شده باشد، اینبار المان اول لیست سه‌تایی قبلی را با المان اول لیست چهارتایی جدید مقایسه می‌کند (مقایسه‌ی بر اساس ایندکس). چون این دو یکی نیستند، کل لیست جدید را مجددا رندر خواهد کرد؛ و در این حالت دیگر نمی‌تواند تشخیص دهد که المان‌هایی در این لیست هستند که با قبل تفاوتی ندارند.
راه‌حل React برای تشخیص منحصربفرد بودن المان‌های یک لیست و یا آرایه، استفاده از خاصیت key است:
<li key={0}>Item 0</li> // <= New item
<li key={1}>Item 1</li>
<li key={2}>Item 2</li>
<li key={3}>Item 3</li>
در اینجا هر آیتم لیست را با یک key منحصربفرد مشخص می‌کنیم. در این حالت React دقیقا می‌تواند محاسبه کند، عنصری که در آرایه‌ی در حال رندر تغییر کرده‌است، کدام است و فقط آن‌را در DOM نهایی به روز رسانی می‌کند؛ و نه اینکه کل لیست را مجددا رندر کند.

این نکات چه ربطی به Blazor دارند؟!
واقعیت این است که Blazor، همان نسخه‌ی مایکروسافتی React است (!) و این خاصیت key، در Blazor نیز تحت عنوان key directive@ وجود دارد و دقیقا مفهوم آن نیز با توضیحاتی که در مورد React داده شد، یکی است.
زمانیکه Blazor صفحه‌ای را رندر می‌کند، ابتدا یک DOM مشخصی را تولید خواهد کد. سپس با تغییر State یک کامپوننت، DOM جدیدی را محاسبه کرده و آن‌را با DOM فعلی مقایسه می‌کند و در نهایت diff تولیدی را به DOM موجود، در جهت نمایش تغییرات، اعمال خواهد کرد.
بنابراین الگوریتم diff باید اضافات، به روز رسانی‌ها و حذف‌های صورت گرفته‌ی در UI را تشخیص داده و فقط قسمت‌های تغییر یافته را جهت به روز رسانی بهینه‌ی UI، به آن اعمال کند. این الگوریتم diff به صورت پیش‌فرض از ایندکس المان‌ها برای مقایسه‌ی آن‌ها استفاده می‌کند. هرچند این روش در بسیاری از حالات، بهینه‌ترین روش است، اما در مورد لیست‌ها خیر؛ که توضیحات آن‌را با مثالی در مورد React، در ابتدای بحث بررسی کردیم. برای مثال اگر شیءای به انتهای لیست اضافه شود، هر المانی را که پس از این ایندکس قرار گرفته باشد، تغییر یافته درنظر گرفته و آن‌را مجددا رندر می‌کند. به همین جهت است که اگر المانی به ابتدای یک لیست اضافه شود، اینبار کل لیست را مجددا رندر می‌کند (چون تمام ایندکس‌های اشیاء موجود در لیست، تغییر کرده‌اند)؛ صرفنظر از اینکه عناصری از این لیست، پیشتر در UI رندر شده‌اند و نیازی به رندر مجدد، ندارند.


یک مثال: بررسی نحوه‌ی رندر لیستی از اشیاء در Blazor

در اینجا کدهای کامل کامپوننتی را مشاهده می‌کنید که یک لیست ساده را در ابتدا رندر کرده و هر بار که بر روی دکمه‌ی افزودن یک شخص کلیک می‌شود، شخص جدیدی را به ابتدای لیست اضافه می‌کند:
@page "/"

<button class="btn btn-primary" @onclick="addPerson">Add Person</button>

<ul class="mt-5">
    @foreach (var person in People)
    {
        <li>@person.Id, @person.Name</li>
    }
</ul>

@code{

    List<Person> People = new()
    {
        new() { Id = 1, Name = "User 1" },
        new() { Id = 2, Name = "User 2" },
    };
    int LastId = 2;

    void addPerson()
    {
        People.Insert(0, new() { Id = ++LastId, Name = $"User {LastId}" });
    }

    class Person
    {
        public int Id { set; get; }
        public string Name { set; get; }
    }
}
در این حالت اگر به لیست المان‌های نمایش داده شده‌ی در ابزار‌های توسعه دهندگان مرورگر مراجعه کنیم، با هر بار کلیک بر روی دکمه افزودن یک شخص جدید، محتوای li‌های نمایش داده شده، ابتدا به رنگ صورتی در آمده و سپس عادی می‌شوند. این تغییر رنگ، به معنای عناصری هستند که هم اکنون مجددا رندر شده‌اند و در UI نهایی تغییر کرده‌اند:


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


تغییر الگوریتم diff محاسبه‌ی تغییرات UI

الگوریتم پیش‌فرض diff، از ایندکس‌های عناصر برای یافتن تغییرات، استفاده می‌کند. با استفاده از دایرکتیو ویژه‌ی key@ می‌توان ایندکس‌های پیش‌فرض را با مقادیری منحصربفرد، بازنویسی کرد، تا اینبار Blazor دقیقا بداند که کدام آیتم، جدید است و کدام‌ها نیازی به رندر مجدد ندارند:
<ul class="mt-5">
    @foreach (var person in People)
    {
        <li @key="person.Id">@person.Id, @person.Name</li>
    }
</ul>
در اینجا تنها تغییر صورت گرفته، اضافه کردن دایرکتیو key@ به هر li در حال رندر است که اینبار مقدار آن، دیگر ایندکس پیش‌فرض عناصر نبوده، بلکه کلید منحصربفرد آن‌ها است.


اگر به تصویر فوق دقت کنید، اینبار فقط li جدیدی که اضافه شده‌است، ابتدا با رنگ صورتی نمایش داده می‌شود و محتوای داخل سایر li ها، دست نخورده باقی مانده‌است؛ یعنی مجددا رندر نشده‌اند.
مطالب دوره‌ها
به روز رسانی خواص راهبری و مجموعه‌های Entity Framework توسط AutoMapper
فرض کنید مدل‌های بانک اطلاعاتی ما چنین ساختاری را دارند:
public abstract class BaseEntity
{
    public int Id { set; get; }
}

public class User : BaseEntity
{
        public string Name { set; get; }
 
        public virtual ICollection<Advertisement> Advertisements { get; set; }
}

public class Advertisement : BaseEntity
{
    public string Title { get; set; }
    public string Description { get; set; }
 
    [ForeignKey("UserId")]
    public virtual User User { get; set; }
    public int UserId { get; set; }
}
و همچنین مدل‌های رابط کاربری یا ViewModel‌های برنامه نیز به صورت ذیل تعریف شده‌اند:
public class AdvertisementViewModel
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int UserId { get; set; }
}
 
public class UserViewModel
{
    public int Id { set; get; }
    public string Name { set; get; }
    public List<AdvertisementViewModel> Advertisements { get; set; }
}


به روز رسانی خواص راهبری Entity framework توسط AutoMapper

در کلاس‌های فوق، یک کاربر، تعدادی تبلیغات را می‌تواند ثبت کند. در این حالت اگر بخواهیم خاصیت User کلاس Advertisement را توسط AutoMapper به روز کنیم، با رعایت دو نکته، اینکار به سادگی انجام خواهد شد:
الف) همانطور که در کلاس Advertisement جهت تعریف کلید خارجی مشخص است، UserId نیز علاوه بر User ذکر شده‌است. این مورد کار نگاشت UserId اطلاعات دریافتی از کاربر را ساده کرده و در این حالت نیازی به یافتن اصل User این UserId از بانک اطلاعاتی نخواهد بود.
ب) چون در اطلاعات دریافتی از کاربر تنها Id او را داریم و نه کل شیء مرتبط را، بنابراین باید به AutoMapper اعلام کنیم تا از این خاصیت صرفنظر کند که اینکار توسط متد Ignore به نحو ذیل قابل انجام است:
this.CreateMap<AdvertisementViewModel, Advertisement>()
      .ForMember(advertisement => advertisement.Description, opt => opt.Ignore())
      .ForMember(advertisement => advertisement.User, opt => opt.Ignore());


به روز رسانی مجموعه‌های Entity Framework توسط AutoMapper

فرض کنید چنین اطلاعاتی از کاربر و رابط کاربری برنامه دریافت شده است:
var uiUser1 = new UserViewModel
{
    Id = 1,
    Name = "user 1",
    Advertisements = new List<AdvertisementViewModel>
    {
        new AdvertisementViewModel
        {
            Id = 1,
            Title = "Adv 1",
            UserId = 1
        },
        new AdvertisementViewModel
        {
            Id = 2,
            Title = "Adv 2",
            UserId = 1
        }
    }
};
اکنون می‌خواهیم معادل این رکورد را از بانک اطلاعاتی یافته و سپس اطلاعات آن‌را بر اساس اطلاعات UI به روز کنیم. شاید در نگاه اول چنین روشی پیشنهاد شود:
 var dbUser1 = ctx.Users.Include(user => user.Advertisements).First(x => x.Id == uiUser1.Id);
Mapper.Map(source: uiUser1, destination: dbUser1);
ابتدا کاربری را که Id آن مساوی uiUser1.Id است، یافته و سپس به AutoMapper اعلام می‌کنیم تا تمام اطلاعات آن‌را به صورت یکجا به روز کند. این نگاشت را نیز برای آن تعریف خواهیم کرد:
 this.CreateMap<UserViewModel, User>()
در یک چنین حالتی، ابتدا شیء user 1 از بانک اطلاعاتی دریافت شده (و با توجه به وجود Include، تمام تبلیغات او نیز دریافت می‌شوند)، سپس ... دو رکورد دریافتی از کاربر، کاملا جایگزین اطلاعات موجود می‌شوند. این جایگزینی سبب تخریب پروکسی‌های EF می‌گردند. برای مثال اگر پیشتر تبلیغی با Id=1 در بانک اطلاعاتی وجود داشته، اکنون با نمونه‌ی جدیدی جایگزین می‌شود که سیستم Tracking و ردیابی EF اطلاعاتی در مورد آن ندارد. به همین جهت اگر در این حالت ctx.SaveChanges فراخوانی شود، عملیات ثبت و یا به روز رسانی با شکست مواجه خواهد شد.

علت را در این دو تصویر بهتر می‌توان مشاهده کرد:



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


راه حل:

در این مورد خاص باید به AutoMapper اعلام کنیم تا کاری با لیست تبلیغات کاربر دریافت شده‌ی از بانک اطلاعاتی نداشته باشد و آن‌را راسا جایگزین نکند:
this.CreateMap<UserViewModel, User>().ForMember(user => user.Advertisements, opt => opt.Ignore());
در اینجا متد Ignore را بر روی لیست تبلیغات کاربر بانک اطلاعاتی فراخوانی کرده‌ایم، تا اطلاعات آن پس از اولین نگاشت انجام شده‌ی توسط AutoMapper دست نخورده باقی بماند.
سپس کار ثبت یا به روز رسانی را به صورت نیمه خودکار مدیریت می‌کنیم:
using (var ctx = new MyContext())
{
    var dbUser1 = ctx.Users.Include(user => user.Advertisements).First(x => x.Id == uiUser1.Id);
    Mapper.Map(source: uiUser1, destination: dbUser1);
 
    foreach (var uiUserAdvertisement in uiUser1.Advertisements)
    {
        var dbUserAdvertisement = dbUser1.Advertisements.FirstOrDefault(ad => ad.Id == uiUserAdvertisement.Id);
        if (dbUserAdvertisement == null)
        {
            // Add new record
            var advertisement = Mapper.Map<AdvertisementViewModel, Advertisement>(uiUserAdvertisement);
            dbUser1.Advertisements.Add(advertisement);
        }
        else
        {
            // Update the existing record
            Mapper.Map(uiUserAdvertisement, dbUserAdvertisement);
        }
    }
 
    ctx.SaveChanges();
}
- در اینجا ابتدا db user معادل اطلاعات ui user از بانک اطلاعاتی، به همراه لیست تبلیغات او دریافت می‌شود و اطلاعات ابتدایی او نگاشت خواهند شد.
- سپس بر روی اطلاعات تبلیغات دریافتی از کاربر، یک حلقه را تشکیل خواهیم داد. در اینجا هربار بررسی می‌کنیم که آیا معادل این تبلیغ هم اکنون به شیء db user متصل است یا خیر؟ اگر متصل نبود، یعنی یک رکورد جدید است و باید Add شود. اگر متصل بود صرفا باید به روز رسانی صورت گیرد.
- برای حالت ایجاد شیء جدید بانک اطلاعاتی، بر اساس uiUserAdvertisement دریافتی، می‌توان از متد Mapper.Map استفاده کرد؛ خروجی این متد، یک شیء جدید تبلیغ است.
- برای حالت به روز رسانی اطلاعات db user موجود، بر اساس اطلاعات ارسالی کاربر نیز می‌توان از متد Mapper.Map کمک گرفت.


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


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
AM_Sample04.zip