مديريت بهينهي سشن فكتوري
ساخت يك شيء 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);
}
}
}
دو نكته جديد در متد GetConfig وجود دارد:
الف) استفاده از متد FromConnectionStringWithKey ، بجاي تعريف مستقيم كانكشن استرينگ در متد مذكور كه روشي است توصيه شده. به اين صورت فايل وب كانفيگ ما بايد داراي تعريف كليد مشخص شده در متد GetConfig به نام DbConnectionString باشد:
<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true" />
</connectionStrings>
در اينجا به 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();
}
}
}
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();
}
}
}
}
}
در متد Application_BeginRequest ، در ابتداي هر درخواست يك سشن جديد ايجاد و به مديريت سشن وب NHibernate بايند ميشود، همچنين يك تراكنش نيز آغاز ميگردد. سپس در پايان درخواست، اين انقياد فسخ شده و تراكنش كامل ميشود، همچنين كار پاكسازي اشياء نيز صورت خواهد گرفت.
با توجه به اين موارد، ديگر نيازي به ذكر using جهت dispose كردن سشن جاري در كدهاي ما نخواهد بود، زيرا در پايان هر درخواست اينكار به صورت خودكار صورت ميگيرد. همچنين نيازي به ذكر تراكنش نيز نميباشد، چون مديريت آنرا خودكار كردهايم.
جهت استفاده از اين Http module تهيه شده بايد چند سطر زير را به وب كانفيگ برنامه اضافه كرد:
<httpModules>
<!--NHSessionManager-->
<add name="SessionModule" type="NHSessionManager.SessionModule"/>
</httpModules>
اكنون مثالي از نحوهي استفاده از امكانات فراهم شده فوق به صورت زير ميتواند باشد:
ابتدا كلاس 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>(); }
}
}
}
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 ، جهت استفاده و مديريت بهينهي سشن فكتوري در برنامههاي ويندوزي كفايت ميكنند.
دريافت سورس برنامه قسمت هفتم
ادامه دارد ....