اشتراک‌ها
فریم ورک هوش مصنوعی و یادگیری ماشین

ML.NET  یک framework سورس باز با قابلیت پشتیبانی از پلتفرم‌های مختلف جهت تهیه برنامه‌های مجهز به هوش مصنوعی و یادگیری ماشین ، مخصوص برنامه نویسان دات نت 

فریم ورک هوش مصنوعی و یادگیری ماشین
نظرات مطالب
تفاوت‌های یک برنامه نویس کارمند با یک برنامه نویس علاقمند
shr6557 این چه حرفیه میزنی؟
خانوما خیلی برنامه نویسای خوبی هستن...
من خودم یه نفر رو میشناسم که 1 ساله دات نت کار میکنه... هفته پیش روش اتصال به دیتابیس با SQLConnection رو کشف کرد.
نظرات نظرسنجی‌ها
در محیط کاری از کدام سورس کنترل استفاده می‌کنید؟
سوم ... جامعه آماری برنامه نویس‌های دات نت احتمالا مطرح است و گرنه شاید git سورس کنترلر محبوب پروژه لینوکس باشه اما خوب ... الزاما به جامعه آماری ما شاید مرتبط نشه.
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه
جهت اطلاع
پروژه‌ی DNTIdentity به ASP.NET Core 2.1 ارتقاء داده شد.
- مشاهده‌ی لیست کامل تغییرات 

برای اجرای آن فقط کافی است:
- ابتدا SDK جدید را نصب کنید.
- سپس مجوز SSL آن‌را تبدیل به مجوز امن و قابل اطمینان کنید.
- در ادامه به پوشه‌ی DataLayer مراجعه کرده و ابتدا دستور dotnet restore را صادر کنید. بعد از آن دو فایل cmd موجود در آن‌را اجرا کنید. فایل اول مهاجرت‌ها را تولید می‌کند و فایل دوم، آن‌ها را به بانک اطلاعاتی از نوع LocalDB اعمال خواهد کرد. بانک اطلاعاتی تولید شده را در پوشه‌ی wwwroot/App_Data می‌توانید مشاهده کنید.
- در آخر به پوشه‌ی اصلی برنامه مراجعه کرده و دو فایل bat موجود در آن‌را اجرا کنید. اولی وابستگی‌ها را بازیابی می‌کند و دومی برنامه را کامپایل کرده و سپس بر روی پورت SSL 5001 ارائه می‌دهد که بلافاصله در مرورگر قابل مشاهده خواهد بود.

برای اجرای این مراحل نیاز به IDE خاصی ندارید. همینقدر که SDK جدید را نصب کرده باشید، کافی است.  
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه
جهت اطلاع
پروژه‌ی DNTIdentity به ASP.NET Core 2.0 ارتقاء داده شد.
- مشاهده‌ی لیست کامل تغییرات

برای اجرای آن فقط کافی است
- ابتدا SDK جدید را نصب کنید.
- سپس به پوشه‌ی DataLayer مراجعه کرده و ابتدا دستور dotnet restore را صادر کنید. بعد از آن دو فایل cmd موجود در آن‌را اجرا کنید. فایل اول مهاجرت‌ها را تولید می‌کند و فایل دوم، آن‌ها را به بانک اطلاعاتی از نوع LocalDB اعمال خواهد کرد. بانک اطلاعاتی تولید شده را در پوشه‌ی wwwroot/App_Data می‌توانید مشاهده کنید.
- در آخر به پوشه‌ی اصلی برنامه مراجعه کرده و دو فایل bat موجود در آن‌را اجرا کنید. اولی وابستگی‌ها را بازیابی می‌کند و دومی برنامه را کامپایل کرده و سپس بر روی پورت 5000 ارائه می‌دهد که بلافاصله در مرورگر قابل مشاهده خواهد بود.

برای اجرای این مراحل نیاز به IDE خاصی ندارید. همینقدر که SDK جدید را نصب کرده باشید، کافی است. 
مطالب
بررسی کارآیی کوئری‌ها در SQL Server - قسمت هفتم - بررسی عملگر Nested loop‌ در یک Query Plan
دراین قسمت قصد داریم عملگر nested loop حاصل از نوشتن جوین‌ها را دقیق‌تر بررسی کنیم. یک حلقه‌ی تو در تو، از هر ردیف ورودی (دیتاست خارجی) برای یافتن ردیف‌هایی (دیتاست درونی) که نوع جوین را برآورده می‌کنند، استفاده می‌کند.


بررسی مفهوم دیتاست خارجی و درونی

 ابتدا در management studio از منوی Query، گزینه‌ی Include actual execution plan را انتخاب می‌کنیم. سپس کوئری‌های زیر را اجرا می‌کنیم:
USE [WideWorldImporters];
GO

SET STATISTICS IO ON;
GO


/*
What's are the inner and outer
data sets?
*/
SELECT
    [ol].[OrderLineID],
    [o].[CustomerID]
FROM [Sales].[OrderLines] [ol]
    INNER JOIN [Sales].[Orders] [o]
    ON [ol].[OrderID] = [o].[OrderID]
WHERE [o].[CustomerID] = 185;
GO
این کوئری یک جوین بین جداول OrderLines و Orders را تشکیل داده‌است؛ به همراه کوئری پلن زیر:


در اینجا دیتاست خارجی، همان index seek بالایی است که بر روی جدول Orders انجام شده‌است. اولین ردیف بازگشت داده شده‌ی توسط آن به همراه OrderID مربوطه را به حلقه‌ی تو در توی Inner Join ارسال می‌کند. سپس index seek دوم بر روی جدول OrderLines‌، بر اساس OrderID دیتاست خارجی، ردیف مرتبطی را در صورت وجود یافته و به حلقه‌ی تو در توی Inner Join بازگشت می‌دهد که در نهایت به select ارسال می‌شود و این عملیات به همین ترتیب ادامه پیدا می‌کند. این خلاصه‌ی کاری است که یک حلقه‌ی تو در تو انجام می‌دهد.

سؤال: اگر جای این دیتاست‌ها را عوض کنیم چه اتفاقی رخ خواهد داد؟
در کوئری زیر توسط گزینه‌ی FORCE ORDER سبب شده‌ایم تا جای دیتاست‌های OUTER/INNER تغییر کند (البته این query hint، کاربرد عملی ندارد و صرفا جهت نمایش دیتاست‌ها از آن استفاده کرده‌ایم):
SELECT
    [ol].[OrderLineID],
    [o].[CustomerID]
FROM [Sales].[OrderLines] [ol]
    INNER JOIN [Sales].[Orders] [o]
    ON [ol].[OrderID] = [o].[OrderID]
WHERE [o].[CustomerID] = 185
OPTION (FORCE ORDER);
اینبار در کوئری پلن تولید شده، index seek بالایی بر روی جدول OrderLines، دیتاست خارجی را تشکیل می‌دهد و index seek دوم بر روی جدول Orders، دیتاست درونی را:



یک نکته: در این تصاویر بجای nested loop، از عملگر Hash Match استفاده شده‌است. اگر بخواهیم بهینه سازی کوئری را وادار کنیم تا از nested loop استفاده کند، می‌توان کوئری فوق را توسط یک INNER LOOP JOIN به صورت زیر نوشت:
SELECT
    [ol].[OrderLineID],
    [o].[CustomerID]
FROM [Sales].[OrderLines] [ol]
    INNER LOOP JOIN [Sales].[Orders] [o]
    ON [ol].[OrderID] = [o].[OrderID]
WHERE [o].[CustomerID] = 185
OPTION (FORCE ORDER);
GO
که یک چنین کوئری پلنی را تولید می‌کند:


همانطور که مشاهده می‌کنید اینبار به علت بالا رفتن تعداد ردیف‌هایی که باید پردازش کند، به یک پلن بسیار غیر بهینه رسیده‌است که برای بهبود آن مجبور شده‌است Parallelism را نیز فعال کند.

در این حالت اگر هر سه کوئری فوق را با هم اجرا کنیم، تا بتوانیم هزینه‌ی آن‌ها را در کوئری پلن نهایی تولید شده، با یکدیگر مقایسه کنیم، هزینه‌ی کوئری اول صفر درصد، کوئری دوم 1 درصد و کوئری سوم 99 درصد نسبت به کل batch محاسبه می‌شود. علت آن را نیز در برگه‌ی messages، با مشاهده‌ی logical reads 477304 مربوط به کوئری سوم می‌توان مشاهده کرد که نسبت به سایر کوئری‌ها بسیار بیشتر است. بنابراین بهتر است در کار بهینه ساز کوئری‌ها به صورت دستی دخالت نکنیم!


بهبود کارآیی یک کوئری، با حذف حلقه‌ی تو در توی کوئری پلن آن در حالت Key lookup

کوئری زیر را با فرض انتخاب گزینه‌ی Include actual execution plan در منوی کوئری، اجرا می‌کنیم:
SELECT
    [ContactPersonID],
    [OrderDate],
    [CustomerPurchaseOrderNumber]
FROM [Sales].[Orders]
WHERE [ContactPersonID] = 3144;
این کوئری هرچند به همراه یک جوین نیست، اما دارای کوئری پلنی دارای یک nested loop است:


ایندکس‌هایی که در این کوئری پلن استفاده شده‌اند، شامل موارد پیش‌فرض زیر هستند؛ یکی بر روی OrderID که کلید اصلی جدول است، تشکیل شده و دیگری بر روی ContactPersonID که در قسمت where کوئری فوق مورد استفاده قرار گرفته‌است:
ALTER TABLE [Sales].[Orders] ADD  CONSTRAINT [PK_Sales_Orders] PRIMARY KEY CLUSTERED 
(
[OrderID] ASC
)

GO

CREATE NONCLUSTERED INDEX [FK_Sales_Orders_ContactPersonID] ON [Sales].[Orders]
(
[ContactPersonID] ASC
)
علت وجود عملگر key lookup بر روی ایندکس PK_Sales_Orders در اینجا این است که ایندکس FK_Sales_Orders_ContactPersonID، ستون‌های کوئری نوشته شده را include نکرده‌است. به همین جهت مجبور شده‌است آن‌ها را از clustered index تعریف شده دریافت کند.
برای بهبود این وضعیت، NONCLUSTERED INDEX تعریف شده را به صورت زیر تغییر می‌دهیم تا ستون‌های OrderDate و CustomerPurchaseOrderNumber را INCLUDE کند:
CREATE NONCLUSTERED INDEX [FK_Sales_Orders_ContactPersonID]
ON [Sales].[Orders] (
[ContactPersonID] ASC
)
INCLUDE (
[OrderDate], [CustomerPurchaseOrderNumber]
)
WITH (DROP_EXISTING = ON)
ON [USERDATA];
GO
اکنون اگر مجددا کوئری قبلی را اجرا کنیم:
SELECT
    [ContactPersonID],
    [OrderDate],
    [CustomerPurchaseOrderNumber]
FROM [Sales].[Orders]
WHERE [ContactPersonID] = 3144;
به این کوئری پلن دارای index seek بدون nested loop می‌رسیم:


چون ایندکس جدید تعریف شده کاملا کوئری ما را پوشش می‌دهد، دیگر نیازی به ایجاد یک nested loop، جهت کار با چندین index متفرقه نیست.


بهبود کارآیی یک کوئری، با حذف حلقه‌ی تو در توی کوئری پلن آن در حالت RID lookup

در اینجا یک جدول کپی را از روی جدول اصلی Orders ایجاد کرده‌ایم؛ به همراه تعریف یک NONCLUSTERED INDEX بر روی ستون ContactPersonID آن:
USE [WideWorldImporters]
GO

DROP TABLE [Sales].[Copy_Orders]
GO

SELECT *
INTO [Sales].[Copy_Orders]
FROM [Sales].[Orders];
GO

CREATE NONCLUSTERED INDEX [NCI_Copy_Orders_ContactPersonID]
ON [Sales].[Copy_Orders] (
[ContactPersonID]
);
GO
سپس کوئری زیر را که همانند کوئری مثال قبلی است، بر روی این جدول کپی اجرا می‌کنیم:
SELECT
    [ContactPersonID],
    [OrderDate],
    [CustomerPurchaseOrderNumber]
FROM [Sales].[Copy_Orders]
WHERE [ContactPersonID] = 3144;
نتیجه‌ی آن تولید کوئری پلن زیر است:


در اینجا یک nested loop را به همراه RID lookup داریم (RID به معنای row id است). همچنین واژه‌ی heap نیز ذکر شده‌است. در این حالت اطلاعات یک چنین جدولی بدون هیچگونه ترتیبی ذخیره شده‌اند؛ بنابراین نیاز به شماره ردیف آن (RID) برای برقراری ارتباطات می‌باشد. Key lookup زمانی رخ می‌دهند که یک جدول دارای یک clustered index باشد و RID lookup، در حالت عکس آن رخ می‌دهد. دقیقا مانند جدول کپی ایجاد شده، که دارای یک clustered index نیست.

در صورت مشاهده‌ی RID lookup نیز می‌توانیم ستون‌هایی از کوئری را که در NONCLUSTERED INDEX ذکر نشده‌اند، include کنیم:
CREATE NONCLUSTERED INDEX [NCI_Copy_Orders_ContactPersonID]
ON [Sales].[Copy_Orders] (
[ContactPersonID] ASC
)
INCLUDE (
[OrderDate], [CustomerPurchaseOrderNumber]
)
WITH (DROP_EXISTING = ON)
ON [USERDATA];
GO
و در این حالت اگر همان کوئری قبلی را مجددا اجرا کنیم، به کوئری پلن دارای index seek زیر خواهیم رسید:

مطالب
آشنایی با NHibernate - قسمت هفتم

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

ساخت یک شیء SessionFactory بسیار پر هزینه و زمانبر است. به همین جهت لازم است که این شیء یکبار حین آغاز برنامه ایجاد شده و سپس در پایان کار برنامه تخریب شود. انجام اینکار در برنامه‌های معمولی ویندوزی (WinForms ،WPF و ...)، ساده است اما در محیط Stateless وب و برنامه‌های ASP.Net ، نیاز به راه حلی ویژه وجود خواهد داشت و تمرکز اصلی این مقاله حول مدیریت صحیح سشن فکتوری در برنامه‌های ASP.Net است.

برای پیاده سازی شیء سشن فکتوری به صورتی که یکبار در طول برنامه ایجاد شود و بارها مورد استفاده قرار گیرد باید از یکی از الگوهای معروف طراحی برنامه نویسی شیء گرا به نام Singleton Pattern استفاده کرد. پیاده سازی نمونه‌ی thread safe آن که در برنامه‌های ذاتا چند ریسمانی وب و همچنین برنامه‌های معمولی ویندوزی می‌تواند مورد استفاده قرار گیرد، در آدرس ذیل قابل مشاهده است:



از پنجمین روش ذکر شده در این مقاله جهت ایجاد یک lazy, lock-free, thread-safe singleton استفاده خواهیم کرد.

بررسی مدل برنامه

در این مدل ساده ما یک یا چند پارکینگ داریم که در هر پارکینگ یک یا چند خودرو می‌توانند پارک شوند.


یک برنامه ASP.Net را آغاز کرده و ارجاعاتی را به اسمبلی‌های زیر به آن اضافه نمائید:
FluentNHibernate.dll
NHibernate.dll
NHibernate.ByteCode.Castle.dll
NHibernate.Linq.dll
و همچنین ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم

تصویر نهایی پروژه ما به شکل زیر خواهد بود:



پروژه ما دارای یک پوشه domain ، تعریف کننده موجودیت‌های برنامه جهت تهیه نگاشت‌های لازم از روی ‌آن‌ها است. سپس یک پوشه جدید را به نام NHSessionManager به آن جهت ایجاد یک Http module مدیریت کننده سشن‌های NHibernate در برنامه اضافه خواهیم کرد.

ساختار دومین برنامه (مطابق کلاس دیاگرام فوق):

namespace NHSample3.Domain
{
public class Car
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Color { get; set; }
}
}

using System.Collections.Generic;

namespace NHSample3.Domain
{
public class Parking
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Location { get; set; }
public virtual IList<Car> Cars { get; set; }

public Parking()
{
Cars = new List<Car>();
}
}
}
مدیریت سشن فکتوری در برنامه‌های وب

در این قسمت قصد داریم Http Module ایی را جهت مدیریت سشن‌های NHibernate ایجاد نمائیم.

در ابتدا کلاس Config را در پوشه مدیریت سشن NHibernate با محتویات زیر ایجاد کنید:

using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;

namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.ExposeConfiguration(
x => x.SetProperty("current_session_context_class", "managed_web")
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample3.Domain.Car).Assembly))
);
}

public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
با این کلاس در قسمت‌های قبل آشنا شده‌اید. در این کلاس با کمک امکانات Auto mapping موجود در Fluent Nhibernate (مطلب قسمت قبلی این سری آموزشی) اقدام به تهیه نگاشت‌های خودکار از کلاس‌های قرار گرفته در پوشه دومین خود خواهیم کرد (فضای نام این پوشه به دومین ختم می‌شود که در متد GetConfig مشخص است).
دو نکته جدید در متد GetConfig وجود دارد:
الف) استفاده از متد FromConnectionStringWithKey ، بجای تعریف مستقیم کانکشن استرینگ در متد مذکور که روشی است توصیه شده. به این صورت فایل وب کانفیگ ما باید دارای تعریف کلید مشخص شده در متد GetConfig به نام DbConnectionString باشد:

<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true" />
</connectionStrings>
ب) قسمت ExposeConfiguration آن نیز جدید است.
در اینجا به AutoMapper خواهیم گفت که قصد داریم از امکانات مدیریت سشن مخصوص وب فریم ورک NHibernate استفاده کنیم. فریم ورک NHibernate دارای کلاسی است به نام NHibernate.Context.ManagedWebSessionContext که جهت مدیریت سشن‌های خود در پروژه‌های وب ASP.Net پیش بینی کرده است و از این متد در Http module ایی که ایجاد خواهیم کرد جهت ردگیری سشن جاری آن کمک خواهیم گرفت.

اگر متد CreateDb را فراخوانی کنیم، جداول نگاشت شده به کلاس‌های پوشه دومین برنامه، به صورت خودکار ایجاد خواهند شد که دیتابیس دیاگرام آن به صورت زیر می‌باشد:



سپس کلاس SingletonCore را جهت تهیه تنها و تنها یک وهله از شیء سشن فکتوری در کل برنامه ایجاد خواهیم کرد (همانطور که عنوان شده، ایده پیاده سازی این کلاس thread safe ، از مقاله معرفی شده در ابتدای بحث گرفته شده است):

using NHibernate;

namespace NHSessionManager
{
/// <summary>
/// lazy, lock-free, thread-safe singleton
/// </summary>
public class SingletonCore
{
private readonly ISessionFactory _sessionFactory;

SingletonCore()
{
_sessionFactory = Config.GetConfig().BuildSessionFactory();
}

public static SingletonCore Instance
{
get
{
return Nested.instance;
}
}

public static ISession GetCurrentSession()
{
return Instance._sessionFactory.GetCurrentSession();
}

public static ISessionFactory SessionFactory
{
get { return Instance._sessionFactory; }
}

class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}

internal static readonly SingletonCore instance = new SingletonCore();
}
}
}
اکنون می‌توان از این Singleton object جهت تهیه یک Http Module کمک گرفت. برای این منظور کلاس SessionModule را به برنامه اضافه کنید:

using System;
using System.Web;
using NHibernate;
using NHibernate.Context;

namespace NHSessionManager
{
public class SessionModule : IHttpModule
{
public void Dispose()
{ }

public void Init(HttpApplication context)
{
if (context == null)
throw new ArgumentNullException("context");

context.BeginRequest += Application_BeginRequest;
context.EndRequest += Application_EndRequest;
}

private void Application_BeginRequest(object sender, EventArgs e)
{
ISession session = SingletonCore.SessionFactory.OpenSession();
ManagedWebSessionContext.Bind(HttpContext.Current, session);
session.BeginTransaction();
}

private void Application_EndRequest(object sender, EventArgs e)
{
ISession session = ManagedWebSessionContext.Unbind(
HttpContext.Current, SingletonCore.SessionFactory);
if (session == null) return;

try
{
if (session.Transaction != null &&
!session.Transaction.WasCommitted &&
!session.Transaction.WasRolledBack)
{
session.Transaction.Commit();
}
else
{
session.Flush();
}
}
catch (Exception)
{
session.Transaction.Rollback();
}
finally
{
if (session != null && session.IsOpen)
{
session.Close();
session.Dispose();
}
}
}
}
}
کلاس فوق کار پیاده سازی اینترفیس IHttpModule را جهت دخالت صریح در request handling pipeline برنامه ASP.Net جاری انجام می‌دهد. در این کلاس مدیریت متدهای استاندارد Application_BeginRequest و Application_EndRequest به صورت خودکار صورت می‌گیرد.
در متد Application_BeginRequest ، در ابتدای هر درخواست یک سشن جدید ایجاد و به مدیریت سشن وب NHibernate بایند می‌شود، همچنین یک تراکنش نیز آغاز می‌گردد. سپس در پایان درخواست، این انقیاد فسخ شده و تراکنش کامل می‌شود، همچنین کار پاکسازی اشیاء نیز صورت خواهد گرفت.

با توجه به این موارد، دیگر نیازی به ذکر using جهت dispose کردن سشن جاری در کدهای ما نخواهد بود، زیرا در پایان هر درخواست اینکار به صورت خودکار صورت می‌گیرد. همچنین نیازی به ذکر تراکنش نیز نمی‌باشد، چون مدیریت آن‌را خودکار کرده‌ایم.

جهت استفاده از این Http module تهیه شده باید چند سطر زیر را به وب کانفیگ برنامه اضافه کرد:

<httpModules>
<!--NHSessionManager-->
<add name="SessionModule" type="NHSessionManager.SessionModule"/>
</httpModules>
بدیهی است اگر نخواهید از Http module استفاده کنید باید این کدها را در فایل Global.asax برنامه قرار دهید.

اکنون مثالی از نحوه‌ی استفاده از امکانات فراهم شده فوق به صورت زیر می‌تواند باشد:
ابتدا کلاس ParkingContext را جهت مدیریت مطلوب‌تر LINQ to NHibernate تشکیل می‌دهیم.

using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample3.Domain;

namespace NHSample3
{
public class ParkingContext : NHibernateContext
{
public ParkingContext(ISession session)
: base(session)
{ }

public IOrderedQueryable<Car> Cars
{
get { return Session.Linq<Car>(); }
}

public IOrderedQueryable<Parking> Parkings
{
get { return Session.Linq<Parking>(); }
}
}
}
سپس در فایل Default.aspx.cs برنامه ، برای نمونه تعدادی رکورد را افزوده و نتیجه را در یک گرید ویوو نمایش خواهیم داد:

using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHSample3.Domain;
using NHSessionManager;

namespace NHSample3
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//ایجاد دیتابیس در صورت نیاز
//Config.CreateDb();

//ثبت یک سری رکورد در دیتابیس
ISession session = SingletonCore.GetCurrentSession();

Car car1 = new Car() { Name = "رنو", Color = "مشکلی" };
session.Save(car1);
Car car2 = new Car() { Name = "پژو", Color = "سفید" };
session.Save(car2);

Parking parking1 = new Parking()
{
Location = "آدرس پارکینگ مورد نظر",
Name = "پارکینگ یک",
Cars = new List<Car> { car1, car2 }
};

session.Save(parking1);

//نمایش حاصل در یک گرید ویوو
ParkingContext db = new ParkingContext(session);
var query = from x in db.Cars select new { CarName = x.Name, CarColor = x.Color };
GridView1.DataSource = query.ToList();
GridView1.DataBind();
}
}
}
مدیریت سشن فکتوری در برنامه‌های غیر وب

در برنامه‌های ویندوزی مانند WinForms ، WPF و غیره، تا زمانیکه یک فرم باز باشد، کل فرم و اشیاء مرتبط با آن به یکباره تخریب نخواهند شد، اما در یک برنامه ASP.Net جهت حفظ منابع سرور در یک محیط چند کاربره، پس از پایان نمایش یک صفحه وب، اثری از آثار اشیاء تعریف شده در کدهای آن صفحه در سرور وجود نداشته و همگی بلافاصله تخریب می‌شوند. به همین جهت بحث‌های ویژه state management در ASP.Net در اینباره مطرح است و مدیریت ویژه‌ای باید روی آن صورت گیرد که در قسمت قبل مطرح شد.
از بحث فوق، تنها استفاده از کلاس‌های Config و SingletonCore ، جهت استفاده و مدیریت بهینه‌ی سشن فکتوری در برنامه‌های ویندوزی کفایت می‌کنند.

دریافت سورس برنامه قسمت هفتم

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

مطالب
ماندگاری با تاخیر در SQL Server 2014
به صورت پیش فرض SQL Server از روش  write-ahead log - WAL استفاده می‌کند. به این معنا که کلیه تغییرات، پیش از commit نهایی باید در لاگ فایل آن نوشته شوند. این مساله با تعداد بالای تراکنش‌ها تا حدودی بر روی سرعت سیستم می‌تواند تاثیرگذار باشد. برای بهبود این وضعیت، در SQL Server 2014 قابلیتی به نام delayed_durability اضافه شده‌است که با فعال سازی آن، کلیه اعمال مرتبط با لاگ‌های تراکنش‌ها به صورت غیرهمزمان انجام می‌شوند. به این ترتیب تراکنش‌ها زودتر از معمول به پایان خواهد رسید؛ با این فرض که نوشته شدن تغییرات در لاگ فایل‌ها، در آینده‌ای محتمل انجام خواهند شد. این مساله به معنای فدا کردن D در ACID است (Atomicity, Consistency, Isolation, Durability). البته باید دقت داشت که رسیدن به ACID کامل هزینه‌بر است و شاید خیلی از اوقات تمام اجزای آن نیازی نباشند یا حتی بتوان با اندکی تخفیف آن‌ها را اعمال کرد؛ مانند D به تاخیر افتاده.
برای اینکار SQL Server از یک بافر 60 کیلوبایتی برای ذخیره سازی اطلاعات لاگ‌هایی که قرار است به صورت غیرهمزمان با تراکنش‌ها نوشته شوند، استفاده می‌کند. هر زمان که این 60KB پر شد، آن‌را flush کرده و ثبت خواهد نمود. به این ترتیب به دو مزیت خواهیم رسید:
- پردازش تراکنش‌ها بدون منتظر شدن جهت commit نهایی در دیسک سخت ادامه خواهند یافت. صبر کمتر به معنای امکان پردازش تراکنش‌های بیشتری در یک سیستم پر ترافیک است.
- با توجه به بافری که از آن صحبت شد، اینبار اعمال Write به صورت یک سری batch اعمال می‌شوند که کارآیی و سرعت بیشتری نسبت به حالت تکی دارند.

اندکی تاریخچه
ایده یک چنین عملی 28 سال قبل توسط Hal Berenson ارائه شده‌است! اوراکل آن‌را در سال 2006 تحت عنوان Asynchronous Commit پیاده سازی کرد و مایکروسافت در سال 2014 آن‌را ارائه داده‌است.


فعال سازی ماندگاری غیرهمزمان در SQL Server

فعال سازی این قابلیت در سطح بانک اطلاعاتی، در سطح یک تراکنش مشخص و یا در سطح رویه‌های ذخیره شده کامپایل شده مخصوص OLTP درون حافظه‌ای، میسر است.
برای فعال سازی ماندگاری با تاخیر در سطح یک دیتابیس، خواهیم داشت:
 ALTER DATABASE dbname SET DELAYED_DURABILITY = DISABLED | ALLOWED | FORCED;


در اینجا اگر ALLOWED را انتخاب کنید، به این معنا است که لاگ کلیه تراکنش‌های مرتبط با این بانک اطلاعاتی به صورت غیرهمزمان نوشته می‌شوند. حالت FORCED نیز دقیقا به همین معنا است با این تفاوت که اگر حالت ALLOWED انتخاب شود، تراکنش‌های ماندگار (آن‌هایی که به صورت دستی DELAYED_DURABILITY را غیرفعال کرده‌اند)، سبب flush کلیه تراکنش‌هایی با ماندگاری به تاخیر افتاده خواهند شد و سپس اجرا می‌شوند. در حالت Forced تنظیم دسترسی DELAYED_DURABILITY = OFF در سطح تراکنش‌ها تاثیری نخواهد داشت؛ اما در حالت ALLOWED این مساله به صورت دستی در سطح یک تراکنش قابل لغو است.
البته باید توجه داشت، صرفنظر از این تنظیمات، یک سری از تراکنش‌ها همیشه ماندگار هستند و بدون تاخیر؛ مانند تراکنش‌های سیستمی، تراکنش‌های بین دو یا چند بانک اطلاعاتی و کلیه تراکنش‌هایی که با FileTable، Change Data Capture و Change Tracking سر و کار دارند.

در سطح تراکنش‌های می‌توان نوشت:
 COMMIT TRANSACTION WITH (DELAYED_DURABILITY = ON);
و یا در رویه‌های ذخیره شده کامپایل شده مخصوص OLTP درون حافظه‌ای خواهیم داشت:
 BEGIN ATOMIC WITH (DELAYED_DURABILITY = ON, ...)

سؤال: آیا فعال سازی DELAYED_DURABILITY بر روی مباحث locking و isolation levels تاثیر دارند؟
پاسخ: خیر. کلیه تنظیمات قفل گذاری‌ها همانند قبل و بر اساس isolation levels تعیین شده، رخ خواهند داد. تنها تفاوت در اینجا است که با فعال سازی DELAYED_DURABILITY، کار commit بدون صبر کردن برای پایان نوشته شدن اطلاعات در لاگ سیستم صورت می‌گیرد. به این ترتیب قفل‌های انجام شده زودتر آزاد خواهند شد.

سؤال: میزان از دست دادن اطلاعات احتمالی در این روش چقدر است؟
در صورتیکه سرور کرش کند یا ری‌استارت شود، حداکثر به اندازه‌ی 60KB اطلاعات را از دست خواهید داد (اندازه‌ی بافری که برای اینکار درنظر گرفته شده‌است). البته عنوان شده‌است که اگر ری‌استارت یا خاموشی سرور، از پیش تعیین شده باشد، ابتدا کلیه لاگ‌های flush نشده، ذخیره شده و سپس ادامه‌ی کار صورت خواهد گرفت؛ ولی زیاد به آن اطمینان نکنید. اما همواره با فراخوانی sys.sp_flush_log، می‌توان به صورت دستی بافر لاگ‌های سیستم را flush کرد.


یک آزمایش

در ادامه قصد داریم یک جدول جدید را در بانک اطلاعاتی آزمایشی testdb2 ایجاد کنیم. سپس یکبار تنظیم DELAYED_DURABILITY = FORCED را انجام داده و 10 هزار رکورد را ثبت می‌کنیم و بار دیگر DELAYED_DURABILITY = DISABLED را تنظیم کرده و همین عملیات را تکرار خواهیم کرد:
CREATE TABLE tblData(
    ID INT IDENTITY(1, 1),
    Data1 VARCHAR(50),
    Data2 INT
);
CREATE CLUSTERED INDEX PK_tblData ON tblData(ID);
CREATE NONCLUSTERED INDEX IX_tblData_Data2 ON tblData(Data2);
 
-------------------------

alter database testdb2 SET DELAYED_DURABILITY = FORCED;

-------------------------

SET NOCOUNT ON
Print 'DELAYED_DURABILITY = FORCED'
DECLARE @counter AS INT = 0
DECLARE @start datetime = getdate()
WHILE (@counter < 10000)
BEGIN
      INSERT INTO tblData (Data1, Data2) VALUES('My Data', @counter)
      SET @counter += 1
END
Print DATEDIFF(ms,@start,getdate());
GO

-------------------------

alter database testdb2 SET DELAYED_DURABILITY = DISABLED;
truncate table tblData;
-------------------------

SET NOCOUNT ON
Print 'DELAYED_DURABILITY = DISABLED'
DECLARE @counter AS INT = 0
DECLARE @start datetime = getdate()
WHILE (@counter < 10000)
BEGIN
      INSERT INTO tblData (Data1, Data2) VALUES('My Data', @counter)
      SET @counter += 1
END
Print DATEDIFF(ms,@start,getdate());
GO

-----------------------
با این خروجی:
 DELAYED_DURABILITY = FORCED
666
DELAYED_DURABILITY = DISABLED
2883
در این آزمایش، سرعت insertها در حالت DELAYED_DURABILITY = FORCED حدود 4 برابر است نسبت به حالت معمولی.


برای مطالعه بیشتر

Control Transaction Durability
SQL Server 2014 Delayed Durability/Lazy Commit
Delayed Durability in SQL Server 2014 – Part 1
Is In-Memory OLTP Always a silver bullet for achieving better transactional speed
Delayed Durability in SQL Server 2014
نظرات مطالب
ساخت بسته‌های نیوگت مخصوص NET Core.
یک نکته‌ی تکمیلی: تولید خودکار بسته‌ی نیوگت پس از Build

در فایل‌‌های csproj بجای ذکر post build event زیر:
<Target Name="PostcompileScript" AfterTargets="Build">
    <Exec Command="dotnet pack --no-build --configuration $(Configuration)" />
</Target>
می‌توان خاصیت GeneratePackageOnBuild را true کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <Version>1.1.1</Version>
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
  </PropertyGroup>
</Project>
و یا اگر ترجیح می‌دهید آن‌را توسط NET Core CLI. به صورت مجزا و در زمان مناسبی انجام دهید، دستور نهایی آن به صورت ذیل است:
dotnet pack -c release