‏نمایش پست‌ها با برچسب NHibernate. نمایش همه پست‌ها
‏نمایش پست‌ها با برچسب NHibernate. نمایش همه پست‌ها

۱۳۹۰/۱۲/۲۵

استفاده از SQL-CE به كمك NHibernate


خلاصه‌اي را در مورد SQL Server CE قبلا در اين سايت مطالعه‌ كرده‌ايد. در ادامه خلاصه‌اي كاربردي را از تنظيمات و نكات مرتبط به كار با SQL-CE به كمك NHibernate ملاحظه خواهيد نمود:

1) دريافت SQL-CE 4.0


همين مقدار براي استفاده از SQL-CE 4.0 به كمك NHibernate كفايت مي‌كند و حتي نيازي به نصب سرويس پك يك VS 2010 هم نيست.

2) ابزار سازي جهت ايجاد يك بانك اطلاعاتي خالي SQL-CE

using System;
using System.IO;

namespace NHibernate.Helper.DbSpecific
{
    public class SqlCEDbHelper
    {
        const string engineTypeName = "System.Data.SqlServerCe.SqlCeEngine, System.Data.SqlServerCe";

        /// <summary>
        /// note: this method will delete existing db and then creates a new one.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="password"></param>
        public static void CreateEmptyDatabaseFile(string filename, string password = "")
        {
            if (File.Exists(filename))
                File.Delete(filename);

            var type = System.Type.GetType(engineTypeName);
            var localConnectionString = type.GetProperty("LocalConnectionString");
            var createDatabase = type.GetMethod("CreateDatabase");

            var engine = Activator.CreateInstance(type);

            string connectionStr = string.Format("Data Source='{0}';Password={1};Encrypt Database=True", filename, password);
            if (string.IsNullOrWhiteSpace(password))
                connectionStr = string.Format("Data Source='{0}'", filename);

            localConnectionString.SetValue(
                obj: engine,
                value: connectionStr,
                index: null);
            createDatabase.Invoke(engine, new object[0]);
        }

        /// <summary>
        /// use this method to compact or encrypt existing db or decrypt it to a new db with all records
        /// </summary>
        /// <param name="sourceConnection"></param>
        /// <param name="destConnection"></param>
        public static void CompactDatabase(string sourceConnection, string destConnection)
        {
            var type = System.Type.GetType(engineTypeName);
            var engine = Activator.CreateInstance(type);

            var localConnectionString = type.GetProperty("LocalConnectionString");
            localConnectionString.SetValue(
                obj: engine,
                value: sourceConnection,
                index: null);

            var compactDatabase = type.GetMethod("Compact");
            compactDatabase.Invoke(engine, new object[] { destConnection });
        }
    }
}

كلاس فوق، يك كلاس عمومي است و مرتبط به NHibernate نيست و در همه جا قابل استفاده است.
متد CreateEmptyDatabaseFile يك فايل بانك اطلاعاتي خالي با فرمت مخصوص SQL-CE را براي شما توليد خواهد كرد. به اين ترتيب مي‌توان بدون نياز به ابزار خاصي، سريعا يك بانك خالي را توليد و شروع به كار كرد. در اين متد اگر كلمه عبوري را وارد نكنيد، بانك اطلاعاتي رمزنگاري شده نخواهد بود و اگر كلمه عبور را وارد كنيد، ديتابيس اوليه به همراه كليه اعمال انجام شده بر روي آن در طول زمان، با كمك الگوريتم AES به صورت خودكار رمزنگاري خواهند شد. كل كاري را هم كه بايد انجام دهيد ذكر اين كلمه عبور در كانكشن استرينگ است.
متد CompactDatabase، يك متد چند منظوره است. اگر بانك اطلاعاتي SQL-CE رمزنگاري نشده‌اي داريد و مي‌خواهيد كل آن‌را به همراه تمام اطلاعات درون آن رمزنگاري كنيد، مي‌توانيد جهت سهولت كار از اين متد استفاده نمائيد. آرگومان اول آن به كانكشن استرينگ بانكي موجود و آرگومان دوم به كانكشن استرينگ بانك جديدي كه توليد خواهد شد، اشاره مي‌كند.
همچنين اگر يك بانك اطلاعاتي SQL-CE رمزنگاري شده داريد و مي‌خواهيد آن‌را به صورت يك بانك اطلاعاتي جديد به همراه تمام ركوردهاي آن رمزگشايي كنيد، باز هم مي‌توان از اين متد استفاده كرد. البته بديهي است كه كلمه عبور را بايد داشته باشيد و اين كلمه عبور جايي درون فايل بانك اطلاعاتي ذخيره نمي‌شود. در اين حالت در كانكشن استرينگ اول بايد كلمه عبور ذكر شود و كانكشن استرينگ دوم نيازي به كلمه عبور نخواهد داشت.

فرمت كلي كانكشن استرينگ SQL-CE هم به شكل زير است:

Data Source=c:\path\db.sdf;Password=1234;Encrypt Database=True

البته اين براي حالتي است كه قصد داشته باشيد بانك اطلاعاتي مورد استفاده را رمزنگاري كنيد يا از يك بانك اطلاعاتي رمزنگاري شده استفاده نمائيد. اگر بانك اطلاعاتي شما كلمه عبوري ندارد، ذكر Data Source=c:\path\db.sdf كفايت مي‌كند.

اين كلاس هم از اين جهت مطرح شد كه NHibernate مي‌تواند ساختار بانك اطلاعاتي را بر اساس تعاريف نگاشت‌ها به صورت خودكار توليد و اعمال كند، «اما» بر روي يك بانك اطلاعاتي خالي SQL-CE از قبل تهيه شده (در غيراينصورت خطاي The database file cannot be found. Check the path to the database را دريافت خواهيد كرد).

نكته:
اگر دقت كرده باشيد در اين كلاس engineTypeName به صورت رشته ذكر شده است. چرا؟
علت اين است كه با ذكر engineTypeName به صورت رشته، مي‌توان از اين كلاس در يك كتابخانه عمومي هم استفاده كرد، بدون اينكه مصرف كننده نيازي داشته باشد تا ارجاع مستقيمي را به اسمبلي SQL-CE به برنامه خود اضافه كند. اگر اين ارجاع وجود داشت، متدهاي ياد شده كار مي‌كنند، در غيراينصورت در گوشه‌اي ساكت و بدون دردسر و بدون نياز به اسمبلي خاصي براي روز مبادا قرار خواهند گرفت.


3) ابزار مرور اطلاعات بانك اطلاعاتي SQL-CE

با استفاده از management studio خود SQL Server هم مي‌شود با بانك‌هاي اطلاعاتي SQL-CE كار كرد، اما ... اينبار برخلاف نگارش كامل اس كيوال سرور، با يك نسخه‌ي بسيار بدوي، كه حتي امكان rename فيلدها را هم ندارد مواجه خواهيد شد. به همين جهت به شخصه برنامه SqlCe40Toolbox را ترجيح مي‌دهم و اطمينان داشته باشيد كه امكانات آن براي كار با SQL-CE از امكانات ارائه شده توسط management studio مايكروسافت، بيشتر و پيشرفته‌تر است!



4) تنظيمات NHibernate جهت كار با SQL-CE

الف) پس از نصب SQL-CE ، فايل‌هاي آن‌را در مسير C:\Program Files\Microsoft SQL Server Compact Edition\v4.0 مي‌توان يافت. درايور ADO.NET آن هم در مسير C:\Program Files\Microsoft SQL Server Compact Edition\v4.0\Desktop قرار دارد. بنابراين در ابتدا نياز است تا ارجاعي را به اسمبلي System.Data.SqlServerCe.dll به برنامه خود اضافه كنيد (نام پوشه desktop آن هم غلط انداز است. از اين جهت كه نگارش 4 آن، به راحتي در برنامه‌هاي ذاتا چند ريسماني ASP.Net بدون مشكل قابل استفاده است).
نكته مهم: در اين حالت NHibernate قادر به يافتن فايل درايور ياد شده نخواهد بود و پيغام خطاي «Could not create the driver from NHibernate.Driver.SqlServerCeDriver» را دريافت خواهيد كرد. براي رفع آن، اسمبلي System.Data.SqlServerCe.dll را در ليست ارجاعات برنامه يافته و در برگه خواص آن، خاصيت «Copy Local» را true كنيد. به اين معنا كه NHibernate اين اسمبلي را در كنار فايل اجرايي برنامه شما جستجو خواهد كرد.

ب) مطلب بعد، تنظيمات ابتدايي NHibernate‌ است جهت شناساندن SQL-CE . مابقي مسايل (نكات mapping، كوئري‌ها و غيره) هيچ تفاوتي با ساير بانك‌هاي اطلاعاتي نخواهد داشت و يكي است. به اين معنا كه اگر برنامه شما از ويژگي‌هاي خاص بانك‌هاي اطلاعاتي استفاده نكند (مثلا اگر از رويه‌هاي ذخيره شده اس كيوال سرور استفاده نكرده باشد)، فقط با تغيير كانكشن استرينگ و معرفي dialect و driver جديد، به سادگي مي‌تواند به يك بانك اطلاعاتي ديگر سوئيچ كند؛ بدون اينكه حتي بخواهيد يك سطر از كدهاي اصلي برنامه خود را تغيير دهيد.



تنها نكته جديد آن اين متد است:

private Configuration getConfig()
{
            var configure = new Configuration();
            configure.SessionFactoryName("BuildIt");

            configure.DataBaseIntegration(db =>
            {
                db.ConnectionProvider<DriverConnectionProvider>();
                db.Dialect<MsSqlCe40Dialect>();
                db.Driver<SqlServerCeDriver>();
                db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
                db.IsolationLevel = IsolationLevel.ReadCommitted;
                db.ConnectionString = ConnectionString;
                db.Timeout = 10;

                //for testing ...
                db.LogFormattedSql = true;
                db.LogSqlInConsole = true;
            });

            return configure;
}

كه در آن نحوه تعريف MsSqlCe40Dialect و SqlServerCeDriver مشخص شده است.

نكته حاشيه‌اي!
در اين مثال primary key از نوع identity تعريف شده و بدون مشكل كار كرد. همين را اگر با EF تست كنيد، اين خطا را دريافت مي‌كنيد: «Server-generated keys and server-generated values are not supported by SQL Server Compact». بله، EF نمي‌تواند با primary key از نوع identity حين كار با SQL-CE كار كند. براي رفع آن توصيه شده است كه از Guid استفاده كنيد!

نكته تكميلي:
استفاده از Dialect سفارشي در NHibernate


نكته پاياني!
و در پايان بايد اشاره كرد كه SQL-CE يك بانك اطلاعاتي نوشته شده با دات نت نيست (با CPP نوشته شده است و نصب آن هم نياز به ران تايم به روز VC را دارد). به اين معنا كه جهت سيستم‌هاي 64 بيتي و 32 بيتي بايد نسخه مناسب آن‌را توزيع كنيد. يا اينكه Target platform پروژه جاري دات نت خود را بر روي X86 قرار دهيد (نه بر روي Any CPU پيش فرض) و در اين حالت تنها يك نسخه X86 بانك اطلاعاتي SQL-CE و همچنين برنامه خود را براي تمام سيستم‌ها توزيع كنيد.

۱۳۹۰/۱۲/۰۸

ارتقاء به NH 3.2 - قسمت دوم


پيشتر مطلبي را در مورد 18 مقاله‌اي كه اكثر حالت‌هاي Mapping موجود در NHibernate را خلاصه كرده بود، مطالعه كرديد.
يك مورد هم در اين مطلب به نظر در مقايسه با Fluent NHibernate درنظر گرفته نشده است و آن هم بحث AutoMapping است. Fluent NHibernate اين قابليت را دارد كه بر اساس تعاريف كلاس‌هاي شما و روابط بين آن‌ها به صورت خودكار نگاشت‌ها را تشكيل دهيد. يعني خودش مباحث ارتباط‌هاي يك به چند و چند به چند و غيره را در پشت صحنه به صورت خودكار توليد كند؛ بدون حتي يك سطر كدنويسي اضافي. فقط حداكثر يك سري IAutoMappingOverride و همچنين تعدادي Convention نامگذاري را هم مي‌توان جهت تنظيمات اين سيستم تمام خودكار اعمال كرد. مثلا توسط IAutoMappingOverride، يكي از خاصيت‌هاي كلاس را به صورت Unique معرفي كرد و مابقي هم به صورت خودكار توسط قابليت Autommaping نگاشت مي‌شوند. يا توسط Convention نامگذاري سفارشي خود، به Fluent NHibernate اعلام مي‌كنيم كه من علاقمندم نام مثلا كليدهاي خارجي تشكيل شده بجاي اعدادي منحصربفرد، از روش ويژه‌اي كه تعيين مي‌كنم، ساخته شوند. اينجا است كه به نظر من كار با NHibernate حتي از Entity framework هم ساده‌تر است (يا ساده‌تر شده است).
قابليت AutoMapping ياد شده، در سيستم جديد توكار Mapping by code هم وجود دارد. فقط چون جايي به صورت درست و درمان مستند نشده، همه دور خودشان مي‌چرخند! به همين جهت مثالي را در اين زمينه آماده كردم كه موارد زير را پوشش مي‌دهد:
- نحوه اعمال تنظيمات بانك اطلاعاتي با كدنويسي در NH3,2
- نحوه يكپارچه سازي اين روش جديد با كتابخانه NHibernate Validator
- استفاده از NHibernate Validator جهت تنظيم طول فيلدهاي بانك اطلاعاتي توليدي به صورت خودكار
- نحوه تعريف قراردادهاي نامگذاري ويژه (مثلا نام جداول توليد شده به صورت خودكار،‌ برخلاف نام موجوديت‌ها، جمع باشد نه مفرد)
- اضافه كردن قابليت تشخيص many-to-many به auto-mapping موجود در NH3,2 (براي تشخيص اين مورد به كمك امكانات مهياي پيش فرض NH3,2، بايد اندكي كدنويسي كرد)
- نحوه بازنويسي قراردادهاي پيش فرض auto-mapping. مثلا اگر قرار باشد همه چيز خودكار شود اما يكي از فيلدها به صورت unique معرفي شود چكار بايد كرد.
- نحوه ذخيره اطلاعات mapping كامپايل شده در فايل و سپس بارگذاري خودكار آن در حين اجراي برنامه جهت بالا بردن سرعت بارگذاري اوليه برنامه.



۱۳۹۰/۱۱/۳۰

ارتقاء به NHibernate 3.2


شروع به كار با NH به دو قسمت تقسيم مي‌شود. يك قسمت نگاشت كلا‌س‌ها است و قسمت دوم سشن گرداني آن. قسمت دوم آن به همان مباحث كلاس‌هاي singleton ايي كه بحث آن‌ها در سايت هست بر مي‌گردد. يا حتي استفاده از كتابخانه‌هاي IOC براي مديريت آن (كه اين پياده سازي را به صورت توكار هم دارند).
قسمت نگاشت كلاس‌ها در NH انواع و اقسامي دارد:
  • ابتدا همان فايل‌هاي XML مدل Hibernate جاوا بود.
  • بعد شد مدل annotation ايي به نام Castle ActiveRecord. (اين پروژه آنچنان فعال نيست و علتش به اين بر مي‌گردد كه نويسنده اصلي جذب مايكروسافت شده)
  • سپس Fluent NHibernate پديد آمد. (اين پروژه هم پس از NH 3.2 ، سرد شده و به نظر آنچنان فعال نيست)
  • الان هم مدل جديدي به صورت توكار و بدون نياز به كتابخانه‌هاي جانبي از NH 3.2 به بعد به آن اضافه شده به نام mapping-by-code .
بنابراين روش مرجح از NH 3,2 به بعد، همين روش mapping-by-code توكار آن است. خصوصا اينكه نياز به وابستگي خارجي ندارد. براي مثال به دليل عدم فعال بودن پروژه‌هايي كه نام برده شد، مثلا NH 3,3 امروز ارائه مي‌شود، شايد دو ماه بعد، اين كتابخانه‌هاي جانبي ساده سازي نگاشت‌ها، به روز شوند يا نشوند.

و ... خبر خوب اينكه شخصي در 18 قسمت به توضيح اين قابليت جديد mapping by code پرداخته و روش‌هاي نگاشت مرتبط رو با مثال توضيح داده كه در آدرس زير مي‌تونيد اون‌ها رو پيدا كنيد:



NHibernate و مديريت خودكار تغييرات ساختار بانك اطلاعاتي


يكي از دردهاي عظمايي كه حين كار با بانك‌هاي اطلاعاتي رابطه‌اي وجود دارد، هماهنگ نبودن ديتابيس توسعه، با ديتابيس كاري است. البته ابزار‌هاي متعددي براي تهيه Diff بين اين دو وجود دارند. ولي زمانيكه قرار باشد اين كار را در چندجا هم انجام دهيم، باز هم مشكل خواهد بود.
با NHibernate مي‌شود كل اين مساله را فراموش كرد! مي‌شود راحت خاصيتي را به كلاسي اضافه كرد و در اولين بار اجراي برنامه، خود NHibernate هماهنگ سازي‌ها را انجام دهد. فيلد اضافه كند. جدول اضافه كند. روابط مرتبط را اضافه كند. يعني تا اين حد كه ما فقط فايل اجرايي برنامه را به روز كنيم، كافي باشد. البته در لابلاي مطالبي كه تا به حال در مورد NHibernate در اين سايت منتشر شده به اين موضوع هم پرداخته شده و مطلب جاري، خلاصه‌ي بزرگنمايي شده آن‌ها است.

اولين قدم: آيا ساختار ديتابيس جاري، با مدل برنامه تطابق دارد؟
قبل از اينكه از NHibernate بخواهيم ساختار بانك اطلاعاتي ما را تغيير دهيد، بايد بدانيم كه آيا واقعا نيازي به اينكار هست يا خير؟
توضيحات بيشتر در مورد روش تشخيص در اينجا: (^)

قدم دوم: اگر ساختار ديتابيس جاري با مدل برنامه تطابق ندارد، چگونه بايد آن‌را به صورت خودكار به روز كرد؟
متد زير بر اساس Configuration ابتدايي بانك اطلاعاتي و نگاشت‌هاي شما، كار به روز رساني خودكار ساختار بانك اطلاعاتي را انجام خواهد داد:

public void UpdateDatabaseSchema(NHibernate.Cfg.Configuration config)
{
     var schemaUpdate = new NHibernate.Tool.hbm2ddl.SchemaUpdate(config);
     schemaUpdate.Execute(script: false, doUpdate: true);
}

يك نكته را هم بايد درنظر داشت. در اين روش هيچ فيلد و جدولي حذف نمي‌شود و به اين ترتيب، جهت امنيت بيشتر طراحي شده. اگر واقعا نياز داشتيد فيلد يا جدولي را حذف كنيد بايد دستي، همانند سابق اقدام كنيد.

قدم سوم: چگونه و در كجا، دو قدم قبل را با برنامه يكپارچه كنيم؟
بلافاصله پس از ايجاد SessionFactory در برنامه، متد زير را فراخواني كنيد:

public void TryValidateAndUpdateDatabaseSchema(NHibernate.Cfg.Configuration config)
{
     try
     {
            ValidateDatabaseSchemaAgainstMappings();
     }
     catch
     {
            UpdateDatabaseSchema(config);
     }
}

متد ValidateDatabaseSchemaAgainstMappings در صورت عدم تطابق مدلي با بانك اطلاعاتي، يك exception را صادر مي‌كند. بنابراين در اينجا كافي است متد UpdateDatabaseSchema را در قسمت catch فراخواني كرد.
و از اين پس ديگر مي‌توانيد به روز رساني ساختار بانك اطلاعاتي برنامه را فراموش كنيد! فيلد اضافه كنيد، كلاس اضافه كنيد، تمام اين‌ها در اولين بار اجراي برنامه به روز شده، به صورت خودكار به بانك اطلاعاتي اعمال خواهند شد.


۱۳۹۰/۰۶/۱۸

NHibernate 3 Beginners Guide


كتاب جديدي در مورد NHibernate 3 ماه قبل توسط انتشارات Packt منتشر گرديد، كه توسط آقاي دكتر Schenker نوشته شده و از همه مهم‌تر توسط تيم NHibernate بازخواني و رفع اشكال شده است.


قسمتي از اين كتاب مقدماتي را اينجا مي‌توانيد مطالعه كنيد.

و... يكي دو روزي است كه فايل PDF كامل آن در اكثر سايت‌ها قابل دريافت است.


۱۳۹۰/۰۶/۰۷

آدرس جديد مخزن كد NHibernate

تيم NHibernate از سيستم SVN سورس فورج، به سورس كنترل Git در سايت GitHub نقل مكان كرده است: [^]
همچنين Issue tracker آن‌ها هم مدتي است كه به آدرس جديدي منتقل شده است: [^]
و ... اگر علاقمند باشيد كه از آخرين تغييرات اين كتابخانه آگاه شويد، زياد به دنبال وبلاگ‌ يا سايت خاصي نگرديد. روش متداول كار با كتابخانه‌هاي سورس باز، دنبال كردن change log ارسالي آن‌ها به سيستم‌هاي سورس كنترل است (همان متني كه حين commit ارسال مي‌كنند). براي مثال جهت آگاه شدن از آخرين تغييرات NHibernate مشترك اين فيد شويد: [^]






۱۳۹۰/۰۵/۲۱

به روز رساني اسمبلي‌هاي داراي امضاي ديجيتال در VS.NET

زمانيكه در VS.NET يك اسمبلي داراي امضاي ديجيتال را اضافه مي‌كنيم، در فايل پروژه برنامه مدخلي شبيه به عبارت زير اضافه مي‌شود:

<Reference Include="NHibernate, Version=2.1.0.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4, processorArchitecture=MSIL">

همانطور كه ملاحظه مي‌كنيد، شماره نگارش فايل، PublicKeyToken و غيره دقيقا ذكر مي‌شوند. حال اگر همين پروژه را بخواهيد به نگارش 3.2 ارتقاء دهيد، احتمالا به روش متداول كپي اسمبلي جديد در پوشه bin برنامه اكتفاء خواهيد كرد. برنامه هم پس از يك Rebuild، به خوبي كامپايل مي‌شود و مشكلي ندارد. اما به محض اجرا و ديباگ در VS.NET، با خطاي زير مواجه خواهيد شد:

Could not load file or assembly 'NHibernate, Version=2.0.0.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4' 
or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. 
(Exception from HRESULT: 0x80131040)

بله! هنوز به دنبال نگارش 2 مي‌گردد و به نظر، نگارش 3.2 جديد را نديد گرفته است. مشكل هم به همان مدخل دقيق موجود در فايل پروژه برنامه، مرتبط است. اين مدخل صرفا با copy/paste فايل‌هاي جديد در پوشه bin برنامه يا rebuild پروژه، «به روز نمي‌شود» !
يا بايد دستي اين فايل csproj يا vbproj را ويرايش كنيد، يا يكبار بايد از داخل VS.NET اين ارجاعات را حذف كرده و مجددا بر اساس فايل‌هاي جديد ايجاد كنيد تا فايل پروژه برنامه بر اين اساس به روز شود.

اين مشكلي هست كه حداقل با تمام مثال‌هاي NHibernate دريافتي از اين سايت خواهيد داشت.


روش ديگر حل اين مشكل، مراجعه به خواص اسمبلي اضافه شده در ليست ارجاعات پروژه در VS.NET و خاموش كردن گزينه‌ي "Specific Version" آن است.


به صورت خلاصه حين به روز رساني اسمبلي‌هاي داراي امضاي ديجيتال:
  • يا بايد ارجاعات داراي امضاي ديجيتال را حذف و بار ديگر اضافه كنيد.
  • يا بايد فايل پروژه برنامه را با يك ويرايشگر متني ساده باز كرده و شماره نگارش‌ها را اصلاح كنيد. (ساده‌ترين روش ممكن)
  • يا خاموش كردن بررسي Specific Version را هم آزمايش كنيد.

۱۳۹۰/۰۵/۱۵

NHibernate 3.2


نگارش نهايي NHibernate 3.2 مدتي است كه ارائه شده و به همراه آن قابليت‌هايي همانند Fluent NHibernate جهت حذف فايل‌هاي XML ايي تعريف نگاشت‌ها به كمك كد نويسي هم وجود دارد. در حال حاضر آنچنان مطالب خودآموز قابل توجهي را در اين مورد نمي‌توان يافت ولي در كل دو ويديوي مقدماتي زير مي‌توانند كمك خوبي جهت شروع به كار با اين امكان جديد باشند:



ماخذ

۱۳۹۰/۰۴/۰۴

نگاشت IDictionary در Fluent NHibernate


نگاشت خودكار مجموعه‌ها در Fluent NHibernate ساده است و نياز به تنظيم خاصي ندارد. براي مثال IList به صورت خودكار به Bag ترجمه مي‌شود و الي آخر.
البته شايد سؤال بپرسيد كه اين Bag از كجا آمده؟ كلا 6 نوع مجموعه در NHibernate پشتيباني مي‌شوند كه شامل Array، Primitive-Array ، Bag ، Set ، List و Map هستند؛ اين‌ اسامي هم جهت حفظ سازگاري با جاوا تغيير نكرده‌اند و گرنه معادل‌هاي آن‌ها در دات نت به اين شرح هستند:
Bag=IList
Set=Iesi.Collections.ISet
List=IList
Map=IDictionary

البته در دات نت 4 ، ISet هم به صورت توكار اضافه شده، اما NHibernate از مدت‌ها قبل آن‌را از كتابخانه‌ي Iesi.Collections به عاريت گرفته است. مهم‌ترين تفاوت‌هاي اين مجموعه‌ها هم در پذيرفتن يا عدم پذيرش اعضاي تكراري است. Set و Map اعضاي تكراري نمي‌پذيرند.
در ادامه مي‌خواهيم طرز كار با Map يا همان IDictionary دات نت را بررسي كنيم:

الف) حالتي كه نوع كليد و مقدار (در يك عضو Dictionary تعريف شده)، Entity نيستند
using System.Collections.Generic;

namespace Test1.Model12
{
public class User
{
public virtual int Id { set; get; }
public virtual string Name { get; set; }
public virtual IDictionary<string, string> Preferences { get; set; }
}
}

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

namespace Test1.Model12
{
public class UserMapping : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany(x => x.Preferences)
.AsMap<string>("FieldKey")
.Element("FieldValue", x => x.Type<string>().Length(500));
}
}
}

خروجي SQL متناظر:
create table "User" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)

create table Preferences (
User_id INT not null,
FieldValue NVARCHAR(500) null,
FieldKey NVARCHAR(255) not null,
primary key (User_id, FieldKey)
)

alter table Preferences
add constraint FKD6CB18523B1FD789
foreign key (User_id)
references "User"

ب) حالتي كه مقدار، Entity است
using System.Collections.Generic;

namespace Test1.Model13
{
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IDictionary<string, Property> Properties { get; set; }
}

public class Property
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Value { get; set; }
public virtual User User { get; set; }
}
}

نحوه تعريف نگاشت:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace Test1.Model13
{
public class UserMapping : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany<Property>(x => x.Properties)
.AsMap<string>("FieldKey")
.Component(x => x.Map(c => c.Id));
}
}
}
خروجي SQL متناظر:
create table "Property" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
Value NVARCHAR(255) null,
User_id INT null,
primary key (Id)
)

create table "User" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)

create table Properties (
User_id INT not null,
Id INT null,
FieldKey NVARCHAR(255) not null,
primary key (User_id, FieldKey)
)

alter table "Property"
add constraint FKF9F4D85A3B1FD7A2
foreign key (User_id)
references "User"

alter table Properties
add constraint FK63646D853B1FD7A2
foreign key (User_id)
references "User"

ج) حالتي كه كليد، Entity است
using System;
using System.Collections.Generic;

namespace Test1.Model14
{
public class FormData
{
public virtual int Id { get; set; }
public virtual DateTime? DateTime { get; set; }
public virtual IDictionary<FormField, string> FormPropertyValues { get; set; }
}

public class FormField
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
}

نحوه تعريف نگاشت:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace Test1.Model14
{
public class FormDataMapping : IAutoMappingOverride<FormData>
{
public void Override(AutoMapping<FormData> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany<FormField>(x => x.FormPropertyValues)
.AsEntityMap("FieldId")
.Element("FieldValue", x => x.Type<string>().Length(500))
.Cascade.All();
}
}
}
خروجي SQL متناظر:
create table "FormData" (
Id INT IDENTITY NOT NULL,
DateTime DATETIME null,
primary key (Id)
)

create table FormPropertyValues (
FormData_id INT not null,
FieldValue NVARCHAR(500) null,
FieldId INT not null,
primary key (FormData_id, FieldId)
)

create table "FormField" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)

alter table FormPropertyValues
add constraint FKB807B9C090849E
foreign key (FormData_id)
references "FormData"

alter table FormPropertyValues
add constraint FKB807B97165898A
foreign key (FieldId)
references "FormField"

يك مثال عملي:
امكانات فوق جهت طراحي قسمت ثبت اطلاعات يك برنامه «فرم ساز» مبتني بر Key-Value بسيار مناسب هستند؛ براي مثال:
برنامه‌اي را در نظر بگيريد كه مي‌تواند تعدادي خدمات داشته باشد كه توسط مدير برنامه قابل اضافه شدن است؛ براي نمونه خدمات درخواست نصب نرم افزار، خدمات درخواست تعويض كارت پرسنلي، خدمات درخواست مساعده، خدمات ... :
public class Service
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<ServiceFormField> Fields { get; set; }
public virtual IList<ServiceFormData> Forms { get; set; }
}

براي هر خدمات بايد بتوان يك فرم طراحي كرد. هر فرم هم از يك سري فيلد خاص آن خدمات تشكيل شده است. براي مثال:
public class ServiceFormField
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual bool IsRequired { get; set; }
public virtual Service Service { get; set; }
}

در اينجا نيازي نيست به ازاي هر فيلد جديد واقعا يك فيلد متناظر به ديتابيس اضافه شود و ساختار آن تغيير كند (برخلاف حالت dynamic components كه پيشتر در مورد آن بحث شد).
اكنون با داشتن يك خدمات و فيلدهاي پوياي آن كه توسط مديربرنامه تعريف شده‌اند، مي‌توان اطلاعات وارد كرد. مهم‌ترين نكته‌ي آن هم IDictionary تعريف شده است كه حاوي ليستي از فيلدها به همراه مقادير وارد شده توسط كاربر خواهد بود:
public class ServiceFormData
{
public virtual int Id { get; set; }
public virtual IDictionary<ServiceFormField, string> FormPropertyValues { get; set; }
public virtual DateTime? DateTime { get; set; }
public virtual Service Service { get; set; }
}

در مورد نحوه نگاشت آن هم در حالت «ج» فوق توضيح داده شد.

۱۳۹۰/۰۳/۳۰

استفاده از فيلدهاي 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 را به صورت توكار پشتيباني مي‌كنند.

۱۳۹۰/۰۳/۲۷

فيلدهاي پويا در 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();
}
}
}

۱۳۹۰/۰۳/۰۳

استفاده از Dialect سفارشي در NHibernate


Dialects در NHibernate كلاس‌هايي هستند جهت معرفي تعاريف ويژگي‌هاي خاص بانك‌هاي اطلاعاتي مختلف؛ مثلا SQL Server 2008 چه ويژگي‌هاي جديدي دارد يا SQL Server CE 4.0 كه جديدا ارائه شده، امكان تعريف offset را در كوئري‌هاي خود ميسر كرده (چيزي كه قرار است در نگارش بعدي SQL Server اصلي(!) در دسترس باشد) ، اكنون چگونه مي‌توان اين ويژگي را فعال كرد (بايد Dialect آن به روز شود و ... همين). يك سري Dialect از پيش تعريف شده هم براي اكثر بانك‌هاي اطلاعاتي در NHibernate وجود دارد. ممكن است اين Dialects پيش فرض الزاما خواسته شما را برآورده نكنند يا مو به مو مستندات بانك‌ اطلاعاتي مرتبط را پياده سازي نكرده باشند و سؤال اين است كه اكنون چه بايد كرد؟ آيا بايد حتما سورس‌ها را دستكاري و بعد كامپايل كرد؟ به اين صورت هر بار با ارائه يك نگارش جديد NHibernate به مشكل برخواهيم خورد چون بايد كل عمليات تكرار شود.
خبر خوب اينكه مي‌توان از همين Dialects موجود ارث بري كرد، سپس مواردي را كه نياز داريم override كرده يا به كلاس مشتق شده افزود. اكنون مي‌توان از اين Dialect سفارشي به جاي Dialect اصلي استفاده كرد. در ادامه با يك نمونه آشنا خواهيم شد.
فرض كنيد Dialect انتخابي مرتبط است با SQL Server CE استاندارد. كوئري ساده زير را مي‌نويسيم، به ظاهر بايد كار كند:
var list = session.Query<SomeClass>().Where(x=>x.Date.Year==2011).ToList();
اما كار نمي‌كند! علت اين است كه تمام Dialects در NHibernate از يك Dialect پايه مشتق شده‌اند. در اين Dialect پايه، تعريف تابع استخراج year از يك تاريخ به نحو زير است:
extract(year, ?1)
اما در SQL CE اين تابع بايد به صورت زير تغيير كند تا كار كند:
datepart(year, ?1)
و ... اين Override انجام نشده (تا نگارش فعلي آن). مهم نيست! خودمان انجام خواهيم داد! به صورت زير:
using NHibernate;
using NHibernate.Dialect;
using NHibernate.Dialect.Function;

namespace Test1
{
public class CustomSqlCeDialect : MsSqlCeDialect
{
public CustomSqlCeDialect()
{
RegisterFunction("year", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(year, ?1)"));
}
}
}
خوب تا اينجا ما يك Dialect جديد را با ارث بري از MsSqlCeDialect اصلي توليد كرده‌ايم. مرحله بعد نوبت به معرفي آن به NHibernate است. اينكار توسط Fluent NHibernate به سادگي زير است:
var dbType = MsSqlCeConfiguration.Standard
...
.Dialect<CustomSqlCeDialect>();

پس از آن كوئري LINQ ابتداي بحث بدون مشكل اجرا خواهد شد چون اكنون مي‌داند كه بجاي extract year ، بايد از تابع datepart‌ استفاده كند.
مرحله بعد هم مي‌تواند تهيه يك patch و ارسال به گروه اصلي براي به روز رساني پروژه NH باشد.

۱۳۹۰/۰۲/۲۷

QueryOver Extensions


جهت تكميل مطلب قبل (+)، مي‌توان به ازاي تمام توابع SQL موجود و همچنين تمام حالت‌هاي اعمال محدوديت مانند مساوي، بزرگتر، كوچكتر و امثال آن، extension method نوشت. يا اينكه يك متد داشت كه بتوان پارامترهاي آن را تنظيم كرد. به همين جهت كتابخانه زير را تهيه كرده‌ام كه از آدرس زير قابل دريافت است:



نحوه استفاده:
ابتدا بايد به NH معرفي شود (يكبار در ابتداي كار برنامه):
RegistrExt.RegistrMyQueryOverExts();
سپس استفاده از آن به سادگي زير خواهد بود:
using QueryOverSqlFuncsExts;

var data = session.QueryOver<Account>()
.Where(x => x.Name.Evaluate(new SqlFunc().CharIndex("a", 1).IsEqualTo(2)))
.List();
مثال‌هاي بيشتر را در پوشه تست پروژه مي‌توانيد پيدا كنيد.

۱۳۹۰/۰۲/۲۴

QueryOver در NHibernate و تفاوت‌هاي آن با LINQ to NH


در NHibernate چندين و چند روش، جهت تهيه كوئري‌ها وجود دارد كه QueryOver يكي از آن‌ها است (+). QueryOver نسبت به LINQ to NH سازگاري بهتري با ساز و كار دروني NHibernate دارد؛ براي مثال امكان يكپارچگي آن با سطح دوم كش. هر چند ظاهر QueryOver با LINQ يكي است، اما در عمل متفاوتند و راه و روش خاص خودش را طلب مي‌كند. براي مثال در LINQ to NH مي‌تواند نوشت x.Property.Contains اما در QueryOver متدي به نام contains قابل استفاده نيست (هر چند در Intellisense ظاهر مي‌شود اما عملا تعريف نشده است و نبايد آن‌را با LINQ اشتباه گرفت) و سعي در استفاده از آن‌ها به استثناهاي زير ختم مي‌شوند:
Unrecognised method call: System.String:Boolean StartsWith(System.String)
Unrecognised method call: System.String:Boolean Contains(System.String)
براي مثال كلاس زير را در نظر بگيريد؛ كوئري‌هاي مطلب جاري بر اين اساس تهيه خواهند شد:
using NHibernate.Validator.Constraints;
using System;

namespace NH3Test.MappingDefinitions.Domain
{
public class Account
{
public virtual int Id { get; set; }

[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام بايد بين 3 و 120 كاراكتر باشد")]
public virtual string Name { get; set; }

[NotNull]
public virtual int Balance { set; get; }

[NotNull]
public virtual DateTime AddDate { set; get; }
}
}

1) يافتن ركوردهايي كه در يك مجموعه‌ي مشخص قرار دارند. براي مثال balance آن‌ها مساوي 10 و 12 است:
var list = new[]  { 12,10};
var resultList = session.QueryOver<Account>()
.WhereRestrictionOn(p => p.Balance)
.IsIn(list)
.List();

SELECT
this_.AccountId as AccountId0_0_,
this_.Name as Name0_0_,
this_.Balance as Balance0_0_
FROM
Accounts this_
WHERE
this_.Balance in (
@p0 /* = 10 */, @p1 /* = 12 */
)

2) پياده سازي همان متد Contains ذكر شده، در QueryOver:
var accountsContianX = session.QueryOver<Account>()
.WhereRestrictionOn(x => x.Name)
.IsLike("X", NHibernate.Criterion.MatchMode.Anywhere)
.List();

SELECT
this_.AccountId as AccountId0_0_,
this_.Name as Name0_0_,
this_.Balance as Balance0_0_
FROM
Accounts this_
WHERE
this_.Name like @p0 /* = %X% */

در اينجا بر اساس مقادير مختلف MatchMode مي‌توان متدهاي StartsWith (MatchMode.Start) ، EndsWith (MatchMode.End) ، Equals (MatchMode.Exact) را نيز تهيه نمود.

انجام مثال دوم راه ساده‌تري نيز دارد. قسمت WhereRestrictionOn و IsLike به صورت يك سري extension متد ويژه در فضاي نام NHibernate.Criterion تعريف شده‌اند. ابتدا اين فضاي نام را به كلاس جاري افزوده و سپس مي‌توان نوشت :
using NHibernate.Criterion;
...
var accountsContianX = session.QueryOver<Account>()
.Where(x => x.Name.IsLike("%X%"))
.List();

اين فضاي نام شامل چهار extension method به نام‌هاي IsLike ، IsInsensitiveLike ، IsIn و IsBetween است.


چگونه extension method سفارشي خود را تهيه كنيم؟

بهترين كار اين است كه به سورس NHibernate ، فايل‌هاي RestrictionsExtensions.cs و ExpressionProcessor.cs كه تعاريف متد IsLike در آن‌ها وجود دارد مراجعه كرد. در اينجا مي‌توان با نحوه‌ي تعريف و سپس ثبت آن در رجيستري extension methods مرتبط با QueryOver توسط متد عمومي RegisterCustomMethodCall آشنا شد. در ادامه سه كار را مي‌توان انجام داد:
-متد مورد نظر را در كدهاي خود (نه كدهاي اصلي NH) اضافه كرده و سپس با فراخواني RegisterCustomMethodCall آن‌را قابل استفاده نمائيد.
-متد خود را به سورس اصلي NH اضافه كرده و كامپايل كنيد.
-متد خود را به سورس اصلي NH اضافه كرده و كامپايل كنيد (بهتر است همان روش نامگذاري بكار گرفته شده در فايل‌هاي ذكر شده رعايت شود). يك تست هم براي آن بنويسيد (تست نويسي هم يك سري اصولي دارد (+)). سپس يك patch از آن روي آن ساخته (+) و براي تيم NH ارسال نمائيد (تا جايي كه دقت كردم از كليه ارسال‌هايي كه آزمون واحد نداشته باشند، صرفنظر مي‌شود).

مثال:
مي‌خواهيم extension متد جديدي به نام YearIs را به QueryOver اضافه كنيم. اين متد را هم بر اساس توابع توكار بانك‌هاي اطلاعاتي، تهيه خواهيم نمود. ليست كامل اين نوع متدهاي بومي SQL را در فايل Dialect.cs سورس‌هاي NH مي‌توان يافت (البته به صورت پيش فرض از متد extract براي جداسازي قسمت‌هاي مختلف تاريخ استفاده مي‌كند. اين متد در فايل‌هاي Dialect مربوط به بانك‌هاي اطلاعاتي مختلف، متفاوت است و برحسب بانك اطلاعاتي جاري به صورت خودكار تغيير خواهد كرد).
using System;
using System.Linq.Expressions;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Impl;

namespace NH3Test.ConsoleApplication
{
public static class MyQueryOverExts
{
public static bool YearIs(this DateTime projection, int year)
{
throw new Exception("Not to be used directly - use inside QueryOver expression");
}

public static ICriterion ProcessAnsiYear(MethodCallExpression methodCallExpression)
{
string property = ExpressionProcessor.FindMemberExpression(methodCallExpression.Arguments[0]);
object value = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
return Restrictions.Eq(
Projections.SqlFunction("year", NHibernateUtil.DateTime, Projections.Property(property)),
value);
}
}

public class QueryOverExtsRegistry
{
public static void RegistrMyQueryOverExts()
{
ExpressionProcessor.RegisterCustomMethodCall(
() => MyQueryOverExts.YearIs(DateTime.Now, 0),
MyQueryOverExts.ProcessAnsiYear);
}
}
}

اكنون براي استفاده خواهيم داشت:
QueryOverExtsRegistry.RegistrMyQueryOverExts(); //يكبار در ابتداي اجراي برنامه بايد ثبت شود
...
var data = session.QueryOver<Account>()
.Where(x => x.AddDate.YearIs(2010))
.List();

براي مثال اگر بانك اطلاعاتي انتخابي از نوع SQLite باشد، خروجي SQL مرتبط به شكل زير خواهد بود:
SELECT
this_.AccountId as AccountId0_0_,
this_.Name as Name0_0_,
this_.Balance as Balance0_0_,
this_.AddDate as AddDate0_0_
FROM
Accounts this_
WHERE
strftime("%Y", this_.AddDate) = @p0 /* =2010 */


هر چند ما تابع year را در متد ProcessAnsiYear ثبت كرده‌ايم اما بر اساس فايل SQLiteDialect.cs ، تعاريف مرتبط و مخصوص اين بانك اطلاعاتي (مانند متد strftime فوق) به صورت خودكار دريافت مي‌گردد و كد ما مستقل از نوع بانك اطلاعاتي خواهد بود.


نكته جالب!
LINQ to NH هم قابل بسط است؛ كاري كه در ORM هاي ديگر به اين سادگي نيست. چند مثال در اين زمينه:
چگونه تابع سفارشي SQL Server خود را به صورت يك extension method تعريف و استفاده كنيم: (+) ، يك نمونه ديگر: (+) و نمونه‌اي ديگر: (+).

۱۳۹۰/۰۲/۱۱

فعال سازي سطح دوم كش در Fluent NHibernate


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

الف) دريافت كش پروايدر
براي اين منظور به صفحه اصلي آن در سايت سورس فورج مراجعه نمائيد(+). اگر به علت تحريم‌ها امكان دريافت فايل‌هاي مرتبط را نداشتيد از اين برنامه استفاده كنيد(+). پس از دريافت، مي‌خواهيم نحوه فعال سازي NHibernate.Caches.SysCache.dll را بررسي كنيم (اين اسمبلي، در برنامه‌هاي وب و دسكتاپ بدون مشكل كار مي‌كند).

ب) اعمال به قسمت تعاريف اوليه
پس از دريافت اسمبلي NHibernate.Caches.SysCache.dll و افزودن ارجاعي به آن، اكنون نوبت به معرفي آن به تنظيمات Fluent NHibernate‌ مي‌باشد. اين‌كار هم بسيار ساده است:
...
.ConnectionString(x => x.FromConnectionStringWithKey(...))
.Cache(x => x.UseQueryCache()
.UseMinimalPuts()
.ProviderClass<NHibernate.Caches.SysCache.SysCacheProvider>())
...

ج) تعريف نوع كش در هنگام ايجاد نگاشت‌ها
اگر از ClassMap‌ها براي تعريف نگاشت‌ها استفاده مي‌كنيد، در انتهاي تعاريف يك سطر Cache.ReadWrite را اضافه كنيد.
اگر از AutoMapping استفاده مي‌كنيد، نياز است تا با استفاده از IAutoMappingOverride (+) سطر ياد شده اضافه گردد؛ براي مثال:
using FluentNHibernate.Automapping.Alterations;

namespace NH3Test.MappingDefinitions.Domain
{
public class AccountOverrides : IAutoMappingOverride<Account>
{
public void Override(FluentNHibernate.Automapping.AutoMapping<Account> mapping)
{
mapping.Cache.ReadWrite();
}
}
}
تعريف يك سطر فوق هم مهم است؛ زيرا در غيراينصورت فقط primary key حاصل از بار اول فراخواني كوئري‌هاي مرتبط كش مي‌شوند؛ نه نتيجه عمليات. هرچند اين مورد هم يك قدم مثبت به شمار مي‌رود از اين لحاظ كه براي مثال تهيه نتايج كوئري بر روي فيلدي كه ايندكس بر روي آن تعريف نشده است هميشه از حالت تهيه كوئري بر روي فيلد داراي ايندكس كندتر است. اما هدف ما در اينجا اين است كه پس از بار اول فراخواني كوئري، بار‌هاي دوم و بعدي ديگر كوئري خاصي را به بانك اطلاعاتي ارسال نكرده و نتايج از كش خوانده شوند (جهت استفاده عموم كاربران در كليه تراكنش‌هاي جاري برنامه).

د) اعمال متد Cacheable به كوئر‌ي‌ها
سه مرحله قبل نحوه برپايي مقدماتي سطح دوم كش را بيان مي‌كنند و تنها يكبار نياز است انجام شوند. در ادامه هر جايي كه نياز داشتيم نتايج كوئري مورد نظر كش شوند (و بايد دقت داشت كه اين كش شدن سطح دوم به معني در دسترس بودن نتايج آن جهت تمام كاربران برنامه در تمام تراكنش‌هاي جاري برنامه هستند؛ براي مثال نتايج آمار سايت كه دسترسي عمومي دارد) تنها كافي است متد Cacheable را به كوئري مورد نظر اضافه كرد؛ براي مثال:
var data = session.QueryOver<Account>()
.Where(s => s.Name == "name")
.Cacheable()
.List();

ه) چگونه صحت اعمال سطح دوم كش را بررسي كنيم؟
براي بررسي اين امر بايد به خروجي SQL نهايي مراجعه كرد (+). سه تراكنش مجزا را تعريف كنيد. در تراكنش اول يك insert ساده، در تراكنش دوم كوئري از اطلاعات قبل (به همراه اعمال متد Cacheable) و در تراكنش سوم مجددا همان كوئري تراكنش دوم را (به همراه اعمال متد Cacheable) تكرار كنيد. حاصل كار تنها بايد دو عبارت SQL باشند. يك مورد جهت insert و يك مورد هم select . در تراكنش سوم، از نتايج كش شده تراكنش دوم استفاده خواهد شد؛ به همين جهت ديگري كوئري سومي به بانك اطلاعاتي ارسال نخواهد شد.
اگر اعمال مورد (ج) فوق را فراموش كنيد، سه كوئري را مشاهده خواهيد كرد، اما كوئري سوم با كوئري دوم اندكي متفاوت خواهد بود و بهينه‌تر؛ چون به صورت هوشمند بر اساس جستجوي بر روي primary key تغيير كرده است (صرفنظر از اينكه قسمت where كوئري شما چيست).

۱۳۹۰/۰۱/۲۷

NH 3.2 و تاثير آن بر آينده‌ي FHN


در اين عنوان، NH همان NHibernate است و FHN همان Fluent NHibernate

نگارش آزمايشي NH 3.2 هم اكنون در دسترس است و يكي از مهمترين مباحثي را كه پوشش داده، جايگزين كردن فايل‌هاي XML تهيه نگاشت‌ها با كدنويسي است. دقيقا چيزي شبيه به Fluent NHibernate البته اينبار از يك كتابخانه ديگر به نام ConfOrm كدها يكي شده‌اند.
بايد توجه داشت كه نگارش 3.2 خاصيت AutoMapping مربوط به FHN را پشتيباني نمي‌كند (يا هنوز در اين نگارش به اين حد نرسيده است)، بنابراين نمي‌تواند جايگزين صد در صدي براي FHN باشد اما باز هم تا حدود 75 درصد كار FHN را پوشش مي‌دهد و مي‌تواند علاقمندان را از اين وابستگي خارجي (!) نجات دهد.
و ... اين مساله نويسنده‌ي اصلي FHN را كمي دلگير كرده است كه آيا FHN را ادامه دهد يا خير. اصل مطلب رو مي‌تونيد اينجا بخونيد.
نظر بعضي‌ها هم در اين بين اين بوده!
ConfOrm looks like lipstick on a pig as far as fluent interfaces go

۱۳۹۰/۰۱/۱۱

مكان اصلي يافتن آخرين نگارش‌هاي Fluent NHibernate


اگر علاقمند باشيد كه به آخرين نگارش‌هاي Fluent NHibernate دسترسي داشته باشيد، مكان اصلي نگهداري و Build آن‌ها در سايت teamcity.codebetter.com مي‌باشد. ثبت نام در آن رايگان است و سپس در آدرس ذيل مي‌توانيد آخرين Build ها را مشاهده و دريافت كنيد:





براي نمونه:

۱۳۹۰/۰۱/۱۰

به روز رساني ارجاعات يك اسمبلي داراي امضاي ديجيتال بدون كامپايل مجدد


سؤال: امروز NHibernate به روز شده اما Fluent NHibernate خير! چكار بايد كرد؟!

Fluent NHibernate كتابخانه‌اي است جهت رهايي برنامه نويس‌ها از نوشتن فايل‌هاي XML نگاشت كلاس‌ها به جداول به همراه قابليت‌هاي ديگري مانند نگاشت خودكار و غيره. بنابراين اين كتابخانه بدون NHibernate اصلي بدون كاربرد است. تيم توسعه آن هم با تيم اصلي NHibernate يكي نيست. عموما NHibernate به روز مي‌شود اما Fluent NHibernate ممكن است تا دو ماه بعد از آن هم به روز نشود. در اين مواقع چه بايد كرد؟

دو كار را مي‌توان انجام داد:

الف) سورس Fluent NHibernate را دريافت كنيم و سپس ارجاعات قبلي به NHibernate قديمي را حذف و ارجاعاتي را به اسمبلي‌هاي جديد آن اضافه و پروژه را كامپايل كنيم.
Fluent NHibernate در طي اين مدت به اندازه كافي رشد كرده و به قولي پخته است. كاري را هم كه ادعا مي‌كند به خوبي انجام مي‌دهد. اما چون اسمبلي‌هاي اصلي NHibernate و همچنين Fluent NHibernate داراي امضاي ديجيتال هستند، نمي‌توان از اسمبلي‌هاي جديد NHibernate به همراه Fluent NHibernate قديمي‌ استفاده كرد. خطاي حاصل شبيه به عبارات ذيل خواهد بود:

System.IO.FileLoadException: Could not load file or assembly ‘nameOfAssembly’,
Version=specificVersion, Culture=neutral, PublicKeyToken=publicKey’ or one of it's dependencies.
The located assembly’s manifest definition does not match the assembly reference.
(Exception from HRESULT: 0x80131040)

حذف ارجاعات به NHibernate قديمي و افزودن مجدد ارجاعات به فايل‌هاي جديد و كامپايل نهايي پروژه يك راه حل است.

ب) راه حل ديگر استفاده از ويژگي bindingRedirect است بدون دريافت سورس، حذف و افزودن ارجاعات و كامپايل مجدد:
  <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="NHibernate"
publicKeyToken="aa95f207798dfdb4"
culture="neutral" />
<bindingRedirect oldVersion="3.0.0.4000"
newVersion="3.1.0.4000"/>
</dependentAssembly>
</assemblyBinding>
</runtime>


در اين مثال، پس از افزودن تعاريف فوق به فايل config برنامه، به سادگي مي‌توان از اسمبلي اصلي NHibernate داراي نگارش 3.1.0.4000 به جاي اسمبلي قديمي‌تر 3.0.0.4000 آن استفاده كرد (همان نگارشي كه Fluent NHibernate ما بر اساس آن كامپايل شده) و ديگر نيازي هم به كامپايل مجدد پروژه‌اي كه از يك اسمبلي قديمي Fluent NHibernate استفاده مي‌كند، نخواهد بود.

۱۳۸۹/۱۱/۲۱

نحوه‌ي نگاشت فيلدهاي فرمول در Fluent NHibernate


اگر با SQL Server كار كرده باشيد حتما با مفهوم و امكان Computed columns (فيلدهاي محاسبه شده) آن آشنايي داريد. چقدر خوب مي‌شد اگر اين امكان براي ساير بانك‌هاي اطلاعاتي كه از تعريف فيلدهاي محاسبه شده پشتيباني نمي‌كنند، نيز مهيا مي‌شد. زيرا يكي از اهداف مهم استفاده‌ي صحيح از ORMs ، مستقل شدن برنامه از نوع بانك اطلاعاتي است. براي مثال امروز مي‌خواهيم با MySQL‌ كار كنيم، ماه بعد شايد بخواهيم يك نسخه‌ي سبك‌تر مخصوص كار با SQLite را ارائه دهيم. آيا بايد قسمت دسترسي به داده برنامه را از نو بازنويسي كرد؟ اينكار در NHibernate فقط با تغيير نحوه‌ي اتصال به بانك اطلاعاتي ميسر است و نه بازنويسي كل برنامه (و صد البته شرط مهم و اصلي آن هم اين است كه از امكانات ذاتي خود NHibernate استفاده كرده باشيد. براي مثال وسوسه‌ي استفاده از رويه‌هاي ذخيره شده را فراموش كرده و به عبارتي ORM مورد استفاده را به امكانات ويژه‌ي يك بانك اطلاعاتي گره نزده باشيد).
خوشبختانه در NHibernate امكان تعريف فيلدهاي محاسباتي با كمك تعريف نگاشت خواص به صورت فرمول مهيا است. براي توضيحات بيشتر لطفا به مثال ذيل دقت بفرمائيد:
در ابتدا كلاس كاربر تعريف مي‌شود:

using System;
using NHibernate.Validator.Constraints;

namespace FormulaTests.Domain
{
public class User
{
public virtual int Id { get; set; }

[NotNull]
public virtual DateTime JoinDate { set; get; }

[NotNullNotEmpty]
[Length(450)]
public virtual string FirstName { get; set; }

[NotNullNotEmpty]
[Length(450)]
public virtual string LastName { get; set; }

[Length(900)]
public virtual string FullName { get; private set; } //از طريق تعريف فرمول مقدار دهي مي‌گردد

public virtual int DayOfWeek { get; private set; }//از طريق تعريف فرمول مقدار دهي مي‌گردد
}
}
در اين كلاس دو خاصيت FullName و DayOfWeek به صورت فقط خواندني به كمك private set ذكر شده، تعريف گرديده‌اند. قصد داريم روي اين دو خاصيت فرمول تعريف كنيم:

using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace FormulaTests.Domain
{
public class UserCustomMappings : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(u => u.Id).GeneratedBy.Identity(); //ضروري است
mapping.Map(x => x.DayOfWeek).Formula("DATEPART(dw, JoinDate) - 1");
mapping.Map(x => x.FullName).Formula("FirstName + ' ' + LastName");
}
}
}
نحوه‌ي انتساب فرمول‌هاي مبتني بر SQL را در نگاشت فوق ملاحظه مي‌نمائيد. براي مثال FullName از جمع دو فيلد نام و نام خانوادگي حاصل خواهد شد و DayOfWeek از طريق فرمول SQL ديگري كه ملاحظه مي‌نمائيد (يا هر فرمول SQL دلخواه ديگري كه صلاح مي‌دانيد).
اكنون اگر Fluent NHibernate را وادار به توليد اسكريپت متناظر با اين دو كلاس كنيم حاصل به صورت زير خواهد بود:
    create table Users (
UserId INT IDENTITY NOT NULL,
JoinDate DATETIME not null,
FirstName NVARCHAR(450) not null,
LastName NVARCHAR(450) not null,
primary key (UserId)
)
همانطور كه ملاحظه مي‌كنيد در اينجا خبري از دو فيلد محاسباتي تعريف شده نيست. اين فيلدها در تعاريف نگاشت‌ها به صورت خودكار ظاهر مي‌شوند:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true"
name="FormulaTests.Domain.User, FormulaTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Users">
<id name="Id" type="System.Int32" unsaved-value="0">
<column name="UserId" />
<generator class="identity" />
</id>
<property name="DayOfWeek" formula="DATEPART(dw, JoinDate) - 1" type="System.Int32" />
<property name="FullName" formula="FirstName + ' ' + LastName" type="System.String" />
<property name="JoinDate" type="System.DateTime">
<column name="JoinDate" />
</property>
<property name="FirstName" type="System.String">
<column name="FirstName" />
</property>
<property name="LastName" type="System.String">
<column name="LastName" />
</property>
</class>
</hibernate-mapping>
اكنون اگر كوئري زير را در برنامه اجرا نمائيم:
var list = session.Query<User>.ToList();
foreach (var item in list)
{
Console.WriteLine("{0}:{1}", item.FullName, item.DayOfWeek);
}
به صورت خودكار به SQL ذيل ترجمه خواهد شد و اكنون نحوه‌ي بكارگيري فيلدهاي فرمول، بهتر مشخص مي‌گردد:
select
user0_.UserId as UserId0_,
user0_.JoinDate as JoinDate0_,
user0_.FirstName as FirstName0_,
user0_.LastName as LastName0_,
DATEPART(user0_.dw, user0_.JoinDate) - 1 as formula0_, --- همان فرمول تعريف شده است
user0_.FirstName + ' ' + user0_.LastName as formula1_ ---از طريق فرمول تعريف شده حاصل گرديده است
from
Users user0_