۱۳۹۰/۰۳/۳۰

استفاده از فيلدهاي XML در NHibernate


در مورد طراحي يك برنامه "فرم ساز" در مطلب قبلي بحث شد ... حدودا سه سال قبل اينكار را براي شركتي انجام دادم. يك برنامه درخواست خدمات نوشته شده با ASP.NET كه مديران برنامه مي‌توانستند براي آن فرم طراحي كنند؛ فرم درخواست پرينت، درخواست نصب نرم افزار، درخواست وام، درخواست پيك، درخواست آژانس و ... فرم‌هايي كه تمامي نداشتند! آن زمان براي حل اين مساله از فيلدهاي XML استفاده كردم.
فيلدهاي XML قابليت نه چندان جديدي هستند كه از SQL Server 2005 به بعد اضافه شده‌اند. مهم‌ترين مزيت آن‌ها‌ هم امكان ذخيره سازي اطلاعات هر نوع شيء‌ايي به عنوان يك فيلد XML است. يعني همان زيرساختي كه براي ايجاد يك برنامه فرم ساز نياز است. ذخيره سازي آن هم آداب خاصي را طلب نمي‌كند. به ازاي هر فيلد مورد نظر كاربر، يك نود جديد به صورت رشته معمولي بايد اضافه شود و نهايتا رشته توليدي بايد ذخيره گردد. از ديد ما يك رشته‌ است، از ديد SQL Server يك نوع XML واقعي؛ به همراه اين مزيت مهم كه به سادگي مي‌توان با T-SQL/XQuery/XPath از جزئيات اطلاعات اين نوع فيلدها كوئري گرفت و سرعت كار هم واقعا بالا است؛ به علاوه بر خلاف مطلب قبلي در مورد dynamic components ، اينبار نيازي نيست تا به ازاي هر يك فيلد درخواستي كاربر، واقعا يك فيلد جديد را به جدول خاصي اضافه كرد. داخل اين فيلد XML هر نوع ساختار دلخواهي را مي‌توان ذخيره كرد. به عبارتي به كمك فيلدهايي از نوع XML مي‌توان داخل يك سيستم بانك اطلاعاتي رابطه‌اي، schema-less كار كرد (un-typed XML) و همچنين از اين اطلاعات ويژه، كوئري‌هاي پيچيده هم گرفت.
تا جايي كه اطلاع دارم، چند شركت ديگر هم در ايران دقيقا از همين ايده فيلدهاي XML براي ساخت برنامه فرم ساز استفاده كرده‌اند ...؛ البته مطلب جديدي هم نيست؛ برنامه‌هاي فرم ساز اوراكل و IBM هم سال‌ها است كه از XML براي همين منظور استفاده مي‌كنند. مايكروسافت هم به همين دليل (شايد بتوان گفت مهم‌ترين دليل وجودي فيلدهاي XML در SQL Server)، پشتيباني توكاري از XML به عمل آورده‌ است.
يا روش ديگري را كه براي طراحي سيستم‌هاي فرم ساز پيشنهاد مي‌كنند استفاده از بانك‌هاي اطلاعاتي مبتني بر key-value‌ مانند Redis يا RavenDb است؛ يا استفاده از بانك‌هاي اطلاعاتي schema-less واقعي مانند CouchDb.


خوب ... اكنون سؤال اين است كه NHibernate براي كار با فيلدهاي XML چه تمهيداتي را درنظر گرفته است؟
براي اين منظور خاصيتي را كه قرار است به يك فيلد از نوع XML نگاشت شود، با نوع XDocument مشخص خواهيم ساخت:
using System.Xml.Linq;

namespace TestModel
{
public class DynamicTable
{
public virtual int Id { get; set; }
public virtual XDocument Document { get; set; }
}
}

سپس بايد جهت معرفي اين نوع ويژه، به صورت صريح از XDocType استفاده كرد؛ يعني نكته‌ي اصلي، استفاده از CustomType مرتبط است:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
using NHibernate.Type;

namespace TestModel
{
public class DynamicTableMapping : IAutoMappingOverride<DynamicTable>
{
public void Override(AutoMapping<DynamicTable> mapping)
{
mapping.Id(x => x.Id);
mapping.Map(x => x.Document).CustomType<XDocType>();
}
}
}

البته لازم به ذكر است كه دو نوع NHibernate.Type.XDocType و NHibernate.Type.XmlDocType براي كار با فيلد‌هاي XML در NHibernate وجود دارند. XDocType براي كار با نوع System.Xml.Linq.XDocument طراحي شده است و XmlDocType مخصوص نگاشت نوع System.Xml.XmlDocument است.

اكنون اگر به كمك كلاس SchemaExport ، اسكريپت توليد جدول متناظر با اطلاعات فوق را ايجاد كنيم به حاصل زير خواهيم رسيد:
   if exists (select * from dbo.sysobjects
where id = object_id(N'[DynamicTable]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [DynamicTable]

create table [DynamicTable] (
Id INT IDENTITY NOT NULL,
Document XML null,
primary key (Id)
)

يك سري اعمال متداول ذخيره سازي اطلاعات و تهيه كوئري نيز در ادامه ذكر شده‌اند:
//insert
object savedId = 0;
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var obj = new DynamicTable
{
Document = System.Xml.Linq.XDocument.Parse(
@"<Doc><Node1>Text1</Node1><Node2>Text2</Node2></Doc>"
)
};
savedId = session.Save(obj);
tx.Commit();
}
}

//simple query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicTable>(savedId);
if (entity != null)
{
Console.WriteLine(entity.Document.Root.ToString());
}

tx.Commit();
}
}

//advanced query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var list = session.CreateSQLQuery("select [Document].value('(//Doc/Node1)[1]','nvarchar(255)') from [DynamicTable] where id=:p0")
.SetParameter("p0", savedId)
.List();

if (list != null)
{
Console.WriteLine(list[0]);
}

tx.Commit();
}
}

و در پايان بديهي است كه جهت كار با امكانات پيشرفته‌تر موجود در SQL Server در مورد فيلد‌هاي XML ( براي نمونه: + و +) بايد مثلا رويه ذخيره شده تهيه كرد (يا مستقيما از متد CreateSQLQuery همانند مثال فوق كمك گرفت) و آن‌را در NHibernate مورد استفاده قرار داد. البته به اين صورت كار شما محدود به SQL Server خواهد شد و بايد در نظر داشت كه در كل تعداد كمي بانك اطلاعاتي وجود دارند كه نوع‌هاي XML را به صورت توكار پشتيباني مي‌كنند.