۱۳۹۰/۰۳/۲۷

فيلدهاي پويا در NHibernate


يكي از قابليت‌هاي جالب NHibernate امكان تعريف فيلدها به صورت پويا هستند. به اين معنا كه زيرساخت طراحي يك برنامه "فرم ساز" هم اكنون در اختيار شما است! سيستمي كه امكان افزودن فيلدهاي سفارشي را دارا است كه توسط برنامه نويس در زمان طراحي اوليه آن ايجاد نشده‌اند. در ادامه نحوه‌ي تعريف و استفاده از اين قابليت را توسط Fluent NHibernate بررسي خواهيم كرد.

در اينجا كلاسي كه قرار است توانايي افزودن فيلدهاي سفارشي را داشته باشد به صورت زير تعريف مي‌شود:
using System.Collections;

namespace TestModel
{
public class DynamicEntity
{
public virtual int Id { get; set; }
public virtual IDictionary Attributes { set; get; }
}
}

Attributes در عمل همان فيلدهاي سفارشي مورد نظر خواهند بود. جهت معرفي صحيح اين قابليت نياز است تا نگاشت آن‌را از نوع dynamic component تعريف كنيم:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace TestModel
{
public class DynamicEntityMapping : IAutoMappingOverride<DynamicEntity>
{
public void Override(AutoMapping<DynamicEntity> mapping)
{
mapping.Table("tblDynamicEntity");
mapping.Id(x => x.Id);
mapping.IgnoreProperty(x => x.Attributes);
mapping.DynamicComponent(x => x.Attributes,
c =>
{
c.Map(x => (string)x["field1"]);
c.Map(x => (string)x["field2"]).Length(300);
c.Map(x => (int)x["field3"]);
c.Map(x => (double)x["field4"]);
});
}
}
}
و مهم‌ترين نكته‌ي اين بحث هم همين نگاشت فوق است.
ابتدا از IgnoreProperty جهت نديد گرفتن Attributes استفاده كرديم. زيرا درغيراينصورت در حالت Auto mapping ، يك رابطه چند به يك به علت وجود IDictionary به صورت خودكار ايجاد خواهد شد كه نيازي به آن نيست (يافتن اين نكته نصف روز كار برد ....! چون مرتبا خطاي An association from the table DynamicEntity refers to an unmapped class: System.Collections.Idictionary ظاهر مي‌شد و مشخص نبود كه مشكل از كجاست).
سپس Attributes به عنوان يك DynamicComponent معرفي شده است. در اينجا چهار فيلد سفارشي را اضافه كرده‌ايم. به اين معنا كه اگر نياز باشد تا فيلد سفارشي ديگري به سيستم اضافه شود بايد يكبار Session factory ساخته شود و SchemaUpdate فراخواني گردد و خوشبختانه با وجود Fluent NHibernate ، تمام اين تغييرات در كدهاي برنامه قابل انجام است (بدون نياز به سر و كار داشتن با فايل‌هاي XML نگاشت‌ها و ويرايش دستي آن‌ها). از تغيير نام جدول كه براي مثال در اينجا tblDynamicEntity در نظر گرفته شده تا افزودن فيلدهاي ديگر در قسمت DynamicComponent فوق.
همچنين باتوجه به اينكه اين نوع تغييرات (ساخت دوبار سشن فكتوري) مواردي نيستند كه قرار باشد هر ساعت انجام شوند، بنابراين سربار آنچناني را به سيستم تحميل نمي‌كنند.
   drop table tblDynamicEntity

create table tblDynamicEntity (
Id INT IDENTITY NOT NULL,
field1 NVARCHAR(255) null,
field2 NVARCHAR(300) null,
field3 INT null,
field4 FLOAT null,
primary key (Id)
)

اگر با SchemaExport، اسكريپت خروجي معادل با نگاشت فوق را تهيه كنيم به جدول فوق خواهيم رسيد. نوع و طول اين فيلدهاي سفارشي بر اساس نوعي كه براي اشياء ديكشنري مشخص مي‌كنيد، تعيين خواهند شد.

چند مثال جهت كار با اين فيلدهاي سفارشي يا پويا :

نحوه‌ي افزودن ركوردهاي جديد بر اساس خاصيت‌هاي سفارشي:
//insert
object savedId = 0;
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var obj = new DynamicEntity();
obj.Attributes = new Hashtable();
obj.Attributes["field1"] = "test1";
obj.Attributes["field2"] = "test2";
obj.Attributes["field3"] = 1;
obj.Attributes["field4"] = 1.1;

savedId = session.Save(obj);
tx.Commit();
}
}

با خروجي
INSERT
INTO
tblDynamicEntity
(field1, field2, field3, field4)
VALUES
(@p0, @p1, @p2, @p3);
@p0 = 'test1' [Type: String (0)], @p1 = 'test2' [Type: String (0)], @p2 = 1
[Type: Int32 (0)], @p3 = 1.1 [Type: Double (0)]

نحوه‌ي كوئري گرفتن از اين اطلاعات (فعلا پايدارترين روشي را كه براي آن يافته‌ام استفاده از HQL مي‌باشد ...):
//query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
//using HQL
var list = session
.CreateQuery("from DynamicEntity d where d.Attributes.field1=:p0")
.SetString("p0", "test1")
.List<DynamicEntity>();

if (list != null && list.Any())
{
Console.WriteLine(list[0].Attributes["field2"]);
}
tx.Commit();
}
}
با خروجي:
    select
dynamicent0_.Id as Id1_,
dynamicent0_.field1 as field2_1_,
dynamicent0_.field2 as field3_1_,
dynamicent0_.field3 as field4_1_,
dynamicent0_.field4 as field5_1_
from
tblDynamicEntity dynamicent0_
where
dynamicent0_.field1=@p0;
@p0 = 'test1' [Type: String (0)]

استفاده از HQL هم يك مزيت مهم دارد: چون به صورت رشته قابل تعريف است، به سادگي مي‌توان آن‌را داخل ديتابيس ذخيره كرد. براي مثال يك سيستم گزارش ساز پويا هم در اين كنار طراحي كرد ....

نحوه‌ي به روز رساني و حذف اطلاعات بر اساس فيلدهاي پويا:
//update
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
entity.Attributes["field2"] = "new-val";
tx.Commit(); // Persist modification
}
}
}

//delete
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
session.Delete(entity);
tx.Commit();
}
}
}