Skip to content

1. مقدمه‌ای بر ORM NHibernate

نسخه PDF این سند در |ICI| موجود است.

نمونه‌های این سند در |ICI| موجود است.

این سند مقدمه‌ای کوتاه بر NHibernate است، معادل .NET چارچوب Java Hibernate. برای یک معرفی جامع، ببینید:


عنوان: NHibernate در عمل، نویسنده: پیر-هنری کواتِه، ناشر: منینگ، ISBN-13: 978-1932394924


ORM (نقشه‌بردار شیء-رابطه‌ای) مجموعه‌ای از کتابخانه‌ها است که به یک برنامه در حال استفاده از پایگاه داده امکان می‌دهد بدون صدور دستورات صریح SQL و بدون نیاز به دانستن جزئیات SGBD مورد استفاده، به آن دسترسی پیدا کند.


پیش‌نیازها


در چارچوب [débutant-intermédiaire-avancé]، این سند بخشی از بخش [intermédiaire] را تشکیل می‌دهد. درک آن نیازمند پیش‌نیازهای مختلفی است که در برخی از اسنادی که نوشته‌ام یافت می‌شوند:

  1. C# 2008: [آموزش C# نسخه ۳.۰ با فریم‌ور .NET ۳.۵ (۲۰۰۸)]
  1. [Spring IoC]، در آدرس [[Spring IoC برای .NET (۲۰۰۵)]] در دسترس است. این سند، مبانی معکوس‌سازی کنترل (IoC) یا تزریق وابستگی (DI) را در فریم‌ورک‌های Spring.Net و [Spring.NET | Homepage ] ارائه می‌دهد.

گاهی پیشنهادهای مطالعهٔ بیشتر در ابتدای پاراگراف‌های این سند ارائه می‌شوند. این پیشنهادها به اسناد قبلی ارجاع می‌دهند.


ابزارها


ابزارهای مورد استفاده در این مطالعه موردی به‌صورت رایگان در وب در دسترس هستند. این ابزارها به شرح زیر هستند (دسامبر ۲۰۱۱):

  • NHibernate 3.2، در آدرس [http://nhforge.org/Default.aspx] در دسترس است
  • Spring.net نسخه 1.3.2 در آدرس [http://www.springframework.net] موجود است. چارچوب Spring.net بسیار جامع است. در اینجا، ما فقط از کتابخانه‌ای که این چارچوب برای تسهیل استفاده از NHibernate ارائه می‌دهد، استفاده خواهیم کرد.
  • Log4net 1.2.10 در آدرس [http://logging.apache.org/log4net] در دسترس است. این چارچوب لاگ‌گیری توسط NHibernate استفاده می‌شود.
  • NUnit 2.5 در URL [http://www.nunit.org/] در دسترس است. این چارچوب تست واحد معادل NET برای چارچوب JUnit در پلتفرم جاوا است.
  • درایور ADO.NET نسخه 6.4.4 برای SGBD MySQL 5 در آدرس URL [http://dev.mysql.com/downloads/connector/net] در دسترس است

تمام فایل‌های DLL مورد نیاز برای پروژه‌های Visual Studio 2010 در یک پوشه [libnet4] کامپایل شده‌اند:

 

1.1. نقش NHIBERNATE در معماری لایه‌ای .NET

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

لایه [dao] از طریق API و ADO.NET با SGBD ارتباط برقرار می‌کند. بیایید روش‌های اصلی این API را مرور کنیم.

در حالت متصل، برنامه:

  1. یک اتصال به منبع داده باز می‌کند
  2. در حالت آنلاین، برنامه:یک اتصال به منبع داده باز می‌کند اتصال را می‌بندد با منبع داده در حالت خواندن/نوشتن کار می‌کند
  3. ارتباط را قطع می‌کند

سه رابط ADO.NET عمدتاً در این عملیات دخیل هستند:

  • IDbConnection، که ویژگی‌ها و متدهای اتصال را در خود جای داده است.
  • IDbCommand، که ویژگی‌ها و متدهای دستور اجراشده SQL را در خود جای می‌دهد.
  • IDataReader، که ویژگی‌ها و متدهای نتیجهٔ فرمان Select در SQL را در بر می‌گیرد.

رابط IDbConnection

برای مدیریت اتصال به پایگاه داده استفاده می‌شود. متدها (M) و ویژگی‌ها (P) این رابط شامل موارد زیر هستند:

نام
نوع
نقش
ConnectionString
P
رشته اتصال پایگاه داده. این مشخص می‌کند تمام پارامترهای لازم برای برقراری اتصال به یک پایگاه داده مشخص.
Open
M
ارتباط را به پایگاه داده تعریف‌شده توسط ConnectionString باز می‌کند
Close
M
اتصال را می‌بندد
BeginTransaction
M
شروع یک تراکنش.
State
P
وضعیت اتصال: ConnectionState.Closed, ConnectionState.Open, ConnectionState.Connecting, ConnectionState.Executing, ConnectionState.Fetching, ConnectionState.Broken

اگر Connection کلاسی باشد که رابط IDbConnection را پیاده‌سازی می‌کند، اتصال به شرح زیر باز می‌شود:

1
2
3
IDbConnection connexion=new Connection();
connexion.ConnectionString=...;
connexion.Open();

رابط IDbCommand

برای اجرای یک دستور SQL یا یک رویهٔ ذخیره‌شده استفاده می‌شود. متدها (M) و ویژگی‌های (P) این رابط شامل موارد زیر هستند:

نام
نوع
نقش
CommandType
P
مشخص می‌کند که چه چیزی باید اجرا شود – مقادیر آن را از یک شمارش‌شده می‌گیرد:
- CommandType.Text: دستور SQL را که در خصوصیت CommandText تعریف شده است، اجرا می‌کند. این مقدار پیش‌فرض است.
- CommandType.StoredProcedure: یک رویهٔ ذخیره‌شده در پایگاه داده را اجرا می‌کند
CommandText
P
- متن دستور SQL که در صورت برابر بودن CommandType با CommandType.Text اجرا می‌شود
- نام رویهٔ ذخیره‌شده‌ای که در صورت برابر بودن CommandType با CommandType.StoredProcedure اجرا می‌شود
Connection
P
اتصال IDbConnection که برای اجرای دستور SQL استفاده می‌شود
Transaction
P
معامله IDbTransaction که در آن فرمان SQL اجرا می‌شود
Parameters
P
فهرست پارامترها برای یک فرمان پیکربندی‌شده SQL. فرمان `update articles set price=price*1.1 where id=@id` دارای پارامتر @id است.
ExecuteReader
M
برای اجرای یک دستور SQL Select. این یک شیء IDataReader را بازمی‌گرداند که نماینده نتیجه Select است.
ExecuteNonQuery
M
برای اجرای دستور SQL Update, Insert, Delete. این دستور تعداد ردیف‌های تحت تأثیر عملیات (به‌روزرسانی‌شده، درج‌شده، حذف‌شده) را بازمی‌گرداند.
ExecuteScalar
M
برای اجرای دستوری SQL Select، که تنها یک نتیجه را بازمی‌گرداند، مانند: select count(*) from articles.
CreateParameter
M
برای ایجاد پارامترها IDbParameter برای یک فرمان پیکربندی‌شده SQL.
Prepare
M
اجرای یک پرس‌وجوی پارامتریک را هنگام اجرای مکرر با پارامترهای مختلف بهینه‌سازی می‌کند.

اگر Command کلاسی باشد که رابط IDbCommand را پیاده‌سازی می‌کند، اجرای یک فرمان SQL بدون تراکنش به شکل زیر خواهد بود:

//ورود 
IDbConnection connexion=...
connexion.Open();
// آماده‌سازی فرمان
IDbCommand commande=new Command();
commande.Connection=connexion;
//اجرای عبارت SELECT
commande.CommandText="select ...";
IDbDataReader reader=commande.ExecuteReader();
...
//اجرای دستور به‌روزرسانی، درج، حذف
commande.CommandText="insert ...";
int nbLignesInsérées=commande.ExecuteNonQuery();
...
// بستن اتصال
connexion.Close();

رابط IDataReader

برای محصورسازی نتایج یک فرمان SQL یا Select استفاده می‌شود. یک شیء IDataReader نمایانگر جدولی با سطرها و ستون‌ها است که به‌صورت متوالی پردازش می‌شوند: ابتدا سطر اول، سپس سطر دوم و غیره. متدها (M) و ویژگی‌ها (P) این رابط شامل موارد زیر هستند:

نام
نوع
نقش
FieldCount
P
تعداد ستون‌ها در جدول IDataReader
GetName
M
GetName(i) نام ستون شماره i در جدول IDataReader را برمی‌گرداند.
Item
P
Item[i] نمایانگر ستون شماره i از ردیف جاری در جدول IDataReader است.
Read
M
به سطر بعدی جدول IDataReader می‌رود. اگر عملیات خواندن با موفقیت انجام شود، مقدار بولی True را برمی‌گرداند، در غیر این صورت مقدار False را.
Close
M
جدول IDataReader را می‌بندد.
GetBoolean
M
GetBoolean(i): مقدار بولی ستون شماره i در ردیف جاری جدول IDataReader را برمی‌گرداند. سایر روش‌های مشابه به شرح زیر هستند: GetDateTime, GetDecimal, GetDouble, GetFloat, GetInt16, GetInt32, GetInt64, GetString.
Getvalue
M
Getvalue(i): مقدار ستون شماره i در ردیف جاری جدول IDataReader را به نوع object برمی‌گرداند.
IsDBNull
M
IsDBNull(i) مقدار True را بازمی‌گرداند اگر ستون شماره i از ردیف جاری در جدول IDataReader فاقد مقدار باشد، که با مقدار SQL NULL نشان داده می‌شود.

ارزیابی یک شیء IDataReader اغلب به شکل زیر است:

// باز کردن اتصال 
IDbConnection connexion=...
connexion.Open();
// آماده‌سازی دستور
IDbCommand commande=new Command();
commande.Connection=connexion;
//اجرای دستور SELECT
commande.CommandText="select ...";
IDataReader reader=commande.ExecuteReader();
//پردازش نتایج
while(reader.Read()){
     // پردازش خط جاری
        ...
}
// بستن خواننده
reader.Close();
// بستن اتصال
connexion.Close();

در معماری قبلی،

کانکتور [ADO.NET] به SGBD متصل است. بنابراین، کلاسی که رابط [IDbConnection] را پیاده‌سازی می‌کند، عبارت است از:

  • کلاس [MySQLConnection] برای SGBD و MySQL
  • کلاس [SQLConnection] برای SGBD و SQLServer

لایه [dao] بنابراین به SGBD مورد استفاده بستگی دارد. برخی از فریم‌ورک‌ها (Linq، Ibatis.net، NHibernate) این محدودیت را با افزودن یک لایه اضافی بین لایه [dao] و کانکتور [ADO.NET] از SGBD مورد استفاده، از بین می‌برد. در اینجا از چارچوب [NHibernate] استفاده خواهیم کرد.

در نمودار بالا، لایه [dao] دیگر با کانکتور [ADO.NET] ارتباط برقرار نمی‌کند، بلکه با چارچوب NHibernate ارتباط برقرار می‌کند، که یک رابط مستقل از کانکتور [ADO.NET] مورد استفاده را برای آن فراهم می‌کند. این معماری امکان تغییر SGBD را بدون تغییر لایه [dao] فراهم می‌کند. تنها نیاز است که کانکتور [ADO.NET] تغییر یابد.

1.2. پایگاه دادهٔ نمونه

برای نشان دادن نحوه کار با NHibernate، از پایگاه داده زیر MySQL [dbpam_nhibernate] استفاده خواهیم کرد:

  • در [1]، پایگاه داده سه جدول دارد:
    • [employes]: جدولی که کارکنان یک مهدکودک را ثبت می‌کند
    • [cotisations]: جدولی که نرخ‌های مشارکت تأمین اجتماعی را ثبت می‌کند
    • [indemnites]: جدولی که اطلاعات مورد استفاده برای محاسبه حقوق کارکنان را ذخیره می‌کند

جدول [employes]

  • [2] جدول کارمندان است و [3] شامل تعریف فیلدها می‌باشد

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

 

جدول [cotisations]

  • در [4]، جدول مشارکت‌ها، و در [5]، معانی فیلدهای آن

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

 

جدول [indemnites]

  • در [6]، جدول مزایا، و در [7]، معانی فیلدهای آن

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

 

صادرات ساختار پایگاه داده به یک فایل SQL نتیجه زیر را می‌دهد:

#
#ساختار جدول `cotisations`: 
#

CREATE TABLE `cotisations` (
  `ID` bigint(20) NOT NULL auto_increment,
  `SECU` double NOT NULL,
  `RETRAITE` double NOT NULL,
  `CSGD` double NOT NULL,
  `CSGRDS` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

#
#ساختار جدول `indemnites`: 
#

CREATE TABLE `indemnites` (
  `ID` bigint(20) NOT NULL auto_increment,
  `ENTRETIEN_JOUR` double NOT NULL,
  `REPAS_JOUR` double NOT NULL,
  `INDICE` int(11) NOT NULL,
  `INDEMNITES_CP` double NOT NULL,
  `BASE_HEURE` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

#
#ساختار جدول `کارمندان`: 
#

CREATE TABLE `employes` (
  `ID` bigint(20) NOT NULL auto_increment,
  `PRENOM` varchar(20) NOT NULL,
  `SS` varchar(15) NOT NULL,
  `ADRESSE` varchar(50) NOT NULL,
  `CP` varchar(5) NOT NULL,
  `VILLE` varchar(30) NOT NULL,
  `NOM` varchar(30) NOT NULL,
  `VERSION` int(11) NOT NULL,
  `INDEMNITE_ID` bigint(20) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `SS` (`SS`),
  KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`),
  CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

توجه کنید که در خطوط ۶، ۲۰ و ۳۶، کلیدهای اصلی ID دارای ویژگی autoincrement هستند. این بدان معناست که MySQL هر بار که یک رکورد اضافه می‌شود، مقادیر کلید اصلی را به‌طور خودکار تولید خواهد کرد. توسعه‌دهنده نیازی به نگرانی در این مورد ندارد.

1.3. پروژهٔ نمایشی C#

برای نمایش پیکربندی و استفاده از NHibernate، از معماری زیر استفاده خواهیم کرد:

یک برنامه کنسول، [1]، داده‌ها را از پایگاه داده قبلی، [2]، از طریق چارچوب [NHibernate] [3] دستکاری خواهد کرد. این ما را به ارائه موارد زیر می‌رساند:

  • فایل‌های پیکربندی برای NHibernate
  • API از NHibernate

پروژه C# به شرح زیر خواهد بود:

عناصر مورد نیاز برای پروژه به شرح زیر است:

  • در [1]، فایل‌های DLL مورد نیاز پروژه:
    • [NHibernate]: DLL از چارچوب NHibernate
    • [MySql.Data]: DLL از کانکتور ADO.NET از SGBD MySQL
    • [log4net]: DLL از چارچوب Log4net که امکان تولید لاگ‌ها را فراهم می‌کند
  • در [2]، کلاس‌های ساختار جدول برای پایگاه داده
  • در [3]، فایل [App.config] که کل برنامه را پیکربندی می‌کند، از جمله چارچوب [NHibernate]
  • در [4]، و برنامه‌های کنسول آزمایشی

1.3.1. پیکربندی اتصال پایگاه داده

بیایید به معماری تست بازگردیم:

همانطور که در بالا نشان داده شده است، [NHibernate] باید بتواند به پایگاه داده دسترسی داشته باشد. برای این کار، به اطلاعات خاصی نیاز دارد:

  • SGBD که مدیریت پایگاه داده را بر عهده دارد (MySQL، SQLServer، Postgres، Oracle و غیره). اکثر SGBDها افزونه‌های خود را به زبان SQL اضافه کرده‌اند. با درک SGBD، NHibernate می‌تواند دستورات SQL را که صادر می‌کند برای این SGBD تطبیق دهد. NHibernate از مفهوم گویش SQL استفاده می‌کند.
  • پارامترهای اتصال به پایگاه داده (نام پایگاه داده، نام کاربری مالک اتصال و رمز عبور او)

این اطلاعات را می‌توان در فایل پیکربندی [App.config] قرار داد. در اینجا نمونه‌ای است که با پایگاه داده MySQL 5 استفاده خواهد شد:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!--بخش‌های پیکربندی -->
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
        <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    </configSections>


    <!--پیکربندی NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
    </hibernate-configuration>

    <!-- این بخش شامل تنظیمات پیکربندی log4net است -->
    <!--NOTE IMPORTANTE: لاگ‌ها به‌طور پیش‌فرض فعال نیستند. آنها باید به‌صورت برنامه‌ای با استفاده از دستور log4net.Config.XmlConfigurator.Configure(); فعال شوند.
    ! -->
    <log4net>
        <!-- یک افزونه خروجی تعریف کنید (محلی که لاگ‌ها ارسال خواهند شد) -->
        <appender name="LogFileAppender" type="log4net.Appender.FileAppender, log4net">
            <param name="File" value="log.txt" />
            <param name="AppendToFile" value="false" />
            <layout type="log4net.Layout.PatternLayout, log4net">
                <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n" />
            </layout>
        </appender>
        <appender name="LogDebugAppender" type="log4net.Appender.DebugAppender, log4net">
            <layout type="log4net.Layout.PatternLayout, log4net">
                <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n"/>
            </layout>
        </appender>
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender, log4net">
            <layout type="log4net.Layout.PatternLayout, log4net">
                <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n"/>
            </layout>
        </appender>

        <!-- راه‌اندازی دسته‌بندی ریشه، تنظیم سطح اولویت پیش‌فرض و افزودن الحاق‌کننده(ها) (محل ارسال لاگ‌ها) -->
        <root>
            <priority value="INFO" />
            <!--
            <appender-ref ref="LogFileAppender" />
            <appender-ref ref="LogDebugAppender"/>
            -->
            <appender-ref ref="ConsoleAppender"/>
        </root>

        <!-- سطح را برای فضاهای نام مشخص تعیین کنید -->
        <!--سطح می‌تواند باشد: ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF -->
        <logger name="NHibernate">
            <level value="INFO" />
        </logger>
    </log4net>
</configuration>
  • خطوط ۴–۷: بخش‌های پیکربندی را در فایل [App.config] تعریف می‌کنند. بیایید خط ۶ را بررسی کنیم:

<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />

این خط بخش پیکربندی برای NHibernate را در فایل [App.config] تعریف می‌کند. این بخش دو ویژگی دارد: name و type.

  • ویژگی [name] بخش پیکربندی را مشخص می‌کند. این بخش باید در اینجا با برچسب‌های <name>...</name> محدود شود، در این مورد <hibernate-configuration>...</hibernate-configuration>، در خطوط ۱۱–۲۴.
  • ویژگی [type=classe,DLL] نام کلاسی را مشخص می‌کند که مسئول پردازش بخشی است که توسط ویژگی [name] تعریف شده است، و همچنین DLL که حاوی این کلاس است. در اینجا، کلاس [NHibernate.Cfg.ConfigurationSectionHandler] نامیده می‌شود و در DLL [NHibernate.dll] قرار دارد. به یاد داشته باشید که این DLL یکی از مراجع پروژه مورد بررسی است.

اکنون بخش پیکربندی NHibernate را بررسی کنیم:


    <!-- پیکربندی NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>
  • خط ۲: پیکربندی برای NHibernate در داخل تگ <hibernate-configuration> قرار دارد. ویژگی xmlns (Xml NameSpace) نسخه‌ای را که برای پیکربندی NHibernate استفاده می‌شود، مشخص می‌کند. در واقع، با گذشت زمان، نحوه پیکربندی NHibernate تکامل یافته است. در اینجا از نسخه 2.2 استفاده می‌شود.
  • خط ۳: پیکربندی برای NHibernate به طور کامل در داخل تگ <session-factory> (خطوط ۳ و ۱۴) قرار دارد. یک جلسه NHibernate ابزاری است که برای کار با یک پایگاه داده بر اساس طرحواره استفاده می‌شود:
    • باز کردن یک جلسه
    • کار با پایگاه داده با استفاده از متدهای API و NHibernate
    • بستن جلسه

این جلسه توسط یک factory ایجاد می‌شود، که یک اصطلاح کلی است و به کلاسی اشاره دارد که قادر به ایجاد اشیاء است. خطوط ۳ تا ۱۴ این factory را پیکربندی می‌کنند.

  • خطوط ۴، ۶، ۸ و ۹: اتصال به پایگاه داده مقصد را پیکربندی می‌کنند. جزئیات کلیدی عبارتند از نام SGBD مورد استفاده، نام پایگاه داده، شناسه کاربری و رمز عبور.
  • خط ۴: ارائه‌دهنده اتصال را تعریف می‌کند، یعنی همان چیزی که اتصال به پایگاه داده از آن درخواست می‌شود. مقدار ویژگی [connection.provider] نام یک کلاس NHibernate است. این ویژگی مستقل از SGBD مورد استفاده است.
  • خط ۶: درایور ADO.NET که باید استفاده شود. این نام یک کلاس NHibernate است که برای یک SGBD مشخص تخصصی شده است، در این مورد MySQL. خط ۶ به صورت نظر (comment) درآمده است، زیرا ضروری نیست.
  • خط ۸: خاصیت [dialect] گویش SQL را برای استفاده با SGBD تعیین می‌کند. در اینجا، این گویش SGBD است، MySQL.

اگر به SGBD تغییر دهیم، چگونه می‌توانیم گویش آن، NHibernate را پیدا کنیم؟ بیایید به پروژهٔ C# قبلی بازگردیم و در برگهٔ [References] روی DLL [NHibernate] دوبار کلیک کنیم:

  • در [1]، زبانه [Explorateur d'objets] تعدادی فایل DLL را نمایش می‌دهد، از جمله آن‌هایی که توسط پروژه ارجاع شده‌اند.
  • در [2]، DLL و [NHibernate]
  • در [3]، DLL و [NHibernate] گسترش یافته‌اند. در اینجا شما نام‌فضای‌های مختلفی را که درون آن‌ها تعریف شده‌اند، خواهید یافت.
  • در [4]، فضای نام [NHibernate.Dialect] شامل کلاس‌هایی است که گویش‌های مختلف قابل استفاده SQL را تعریف می‌کنند.
  • در [5]، کلاس گویش برای SGBD و MySQL 5.
  • در [6]، فضای نام کلاس [MySqlDataDriver] که در خط ۶ زیر استفاده شده است:

    <!-- configuration NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQLDialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>
  • خطوط ۹–۱۱: رشته اتصال پایگاه داده. این رشته به شکل «param1=val1;param2=val2; ...» است. مجموعه پارامترهای تعریف‌شده به این صورت به درایور SGBD امکان برقراری اتصال را می‌دهد. فرمت این رشته اتصال به درایور SGBD مورد استفاده بستگی دارد. رشته‌های اتصال برای درایورهای اصلی SGBD را می‌توان در وب‌سایت [http://www.connectionstrings.com/] یافت. در اینجا، رشته "Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;" یک رشته اتصال برای SGBD MySQL است. این نشان می‌دهد که:
    • Server=localhost;: نمونه SGBD روی همان ماشینی قرار دارد که کلاینت در تلاش برای برقراری اتصال است
    • Database=dbpam_nhibernate; : پایگاه داده هدف MySQL
    • Uid=root;: کاربری که اتصال را برقرار می‌کند، کاربر root است
    • Pwd=;: این کاربر رمز عبور ندارد (یک مورد خاص در این مثال)
  • خط ۱۲: ویژگی [show_sql] مشخص می‌کند که آیا NHibernate باید دستورات SQL را که به پایگاه داده می‌فرستد، ثبت کند یا خیر. در فاز توسعه، مفید است که این ویژگی را روی [true] تنظیم کنید تا دقیقاً بفهمید NHibernate چه کاری انجام می‌دهد.
  • خط ۱۳: برای درک تگ <mapping>، بیایید به معماری برنامه بازگردیم:

اگر برنامه کنسول مشتری مستقیم کانکتور ADO.NET بود و می‌خواست لیست کارمندان را دریافت کند، از کانکتور می‌خواست تا فرمان SQL Select را اجرا کند، و در مقابل یک شیء از نوع IDataReader دریافت می‌کرد که برای به دست آوردن فهرست کارمندان درخواستی اولیه، باید آن را پردازش می‌کرد.

در مثال بالا، برنامه کنسول مشتری NHibernate است و NHibernate مشتری کانکتور ADO.NET است. بعداً خواهیم دید که API که به NHibernate متصل است، به برنامه کنسول امکان می‌دهد تا فهرست کارمندان را درخواست کند. NHibernate این درخواست را به یک فرمان SQL Select تبدیل می‌کند که آن را توسط کانکتور ADO.NET اجرا خواهد کرد. کانکتور یک شیء از نوع IDataReader را بازمی‌گرداند. با استفاده از این شیء، NHibernate باید قادر به ساخت فهرست کارمندانی باشد که درخواست شده است. این امر از طریق پیکربندی امکان‌پذیر می‌شود. هر جدول در پایگاه داده با یک کلاس C# مرتبط است. بنابراین، با استفاده از سطرهای جدول [employes] که توسط IDataReader بازگردانده می‌شود، NHibernate قادر خواهد بود فهرستی از اشیاء نمایانگر کارمندان را بسازد و آن را به برنامه کنسول بازگرداند. این روابط جدول <--> کلاس در فایل‌های پیکربندی تعریف می‌شوند. NHibernate از اصطلاح «نقشه‌برداری» برای توصیف این روابط استفاده می‌کند.

بیایید به خط ۱۳ زیر بازگردیم:


    <!-- پیکربندی NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>

خط ۱۳ نشان می‌دهد که فایل‌های پیکربندی جدول <--> کلاس در اسمبلی [pam-nhibernate-demos] قرار خواهند گرفت. اسمبلی، فایل اجرایی یا DLL تولیدشده توسط کامپایل یک پروژه است. در اینجا، فایل‌های نگاشت در اسمبلی پروژهٔ نمونه قرار داده می‌شوند. برای پی بردن به نام این اسمبلی، ویژگی‌های پروژه را بررسی کنید:

  • در [1]، ویژگی‌های پروژه
  • در زبانه [Application]، [2]، نام اسمبلی [3] که تولید خواهد شد.
  • از آنجا که نوع خروجی [Application console] [4] است، فایلی که هنگام کامپایل پروژه تولید می‌شود با نام [pam-nhibernate-demos.exe] نام‌گذاری خواهد شد. اگر نوع خروجی [Bibliothèque de classes] [5] بود، فایلی که هنگام کامپایل پروژه تولید می‌شود، [pam-nhibernate-demos.dll] نامیده می‌شد.
  • اسمبلی در پوشه [bin/Release] پروژه [6] تولید می‌شود.

از توضیحات قبلی برمی‌آید که جدول نگاشت <--> فایل‌های کلاس باید در فایل [pam-nhibernate-demos.exe] [6] قرار داشته باشد.

1.3.2. پیکربندی نگاشت کلاس‌های <--> tables

بیایید به معماری پروژه مورد بررسی بازگردیم:

  • در [1]، برنامه کنسول از متدهای API در چارچوب NHibernate استفاده می‌کند. این دو بلوک اشیاء را مبادله می‌کنند.
  • در [2]، NHibernate از API یک کانکتور .NET استفاده می‌کند. دستورالعمل‌ها را از SQL به مقصد SGBD ارسال می‌کند.

برنامه کنسول اشیایی را که نمایانگر جداول پایگاه داده هستند، دستکاری خواهد کرد. در این پروژه، این اشیاء و پیوندهایی که آن‌ها را به جداول پایگاه داده متصل می‌کنند، در پوشه [Entites] زیر قرار داده شده‌اند:

 
  • هر جدول پایگاه داده با یک کلاس و یک فایل نگاشت بین این دو مطابقت دارد.
جدول
کلاس
نقشه‌برداری
مشارکت‌ها
Cotisations.cs
Cotisations.hbm.xml
کارمندان
Employe.cs
Employe.hbm.xml
مزایا
Indemnites.cs
Indemnites.hbm.xml

1.3.2.1. نقشه‌برداری جدول [cotisations]

بیایید جدول [cotisations] را در نظر بگیریم:

ID
کلید اصلی خودافزایشی
VERSION
شماره نسخه رکورد
SECU
نرخ مشارکت تأمین اجتماعی (درصد)
RETRAITE
نرخ مشارکت بازنشستگی
CSGD
نرخ مشارکت برای مشارکت اجتماعی عمومی قابل کسر
CSGRDS
نرخ مشارکت برای مشارکت اجتماعی عمومی و مشارکت بازپرداخت بدهی اجتماعی

یک سطر از این جدول را می‌توان در یک شیء از نوع [Cotisations.cs] به صورت زیر دربرگرفت:


namespace PamNHibernateDemos {
    public class Cotisations {
        // ویژگی‌های خودکار
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual double CsgRds { get; set; }
        public virtual double Csgd { get; set; }
        public virtual double Secu { get; set; }
        public virtual double Retraite { get; set; }

        //تولیدکنندگان
        public Cotisations() {
        }
        //ToString
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}]", CsgRds, Csgd, Secu, Retraite);
        }
    }

}

برای هر ستون در جدول [cotisations] یک ویژگی خودکار ایجاد شده است. هر یک از این ویژگی‌ها باید مجازی (virtual) اعلام شوند (virtual) زیرا NHibernate از کلاس ارث می‌برد و ویژگی‌های آن را بازنویسی (override) می‌کند. بنابراین این ویژگی‌ها باید مجازی باشند.

توجه کنید که در خط ۱، کلاس به فضای نام [PamNHibernateDemos] تعلق دارد.

فایل نگاشت [Cotisations.hbm.xml] بین جدول [cotisations] و کلاس [Cotisations] به شرح زیر است:


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Cotisations" table="COTISATIONS">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="CsgRds" column="CSGRDS"/>
        <property name="Csgd" column="CSGD"/>
        <property name="Retraite" column="RETRAITE"/>
        <property name="Secu" column="SECU"/>
    </class>
</hibernate-mapping>
  • فایل نگاشت یک فایل XML است که در داخل تگ <hibernate-mapping> (خطوط ۲ و ۱۴) تعریف شده است
  • خط ۴: تگ <class> یک جدول پایگاه داده را به یک کلاس پیوند می‌دهد. در اینجا، جدول [COTISATIONS] (ویژگی table) و کلاس [Cotisations] (ویژگی name). در .NET، یک کلاس باید با نام کامل خود (شامل فضای نام) و با اسمبلی که حاوی آن است تعریف شود. هر دو مورد از این اطلاعات توسط خط ۳ ارائه شده‌اند. مورد اول (فضای نام) را می‌توان در تعریف کلاس یافت. دومین مورد (اسمبلی) نام اسمبلی پروژه است. قبلاً توضیح داده‌ایم که چگونه این نام را پیدا کنیم.
  • خطوط ۵–۷: تگ <id> برای تعریف نگاشت کلید اصلی برای جدول [cotisations] استفاده می‌شود.
    • خط ۵: ویژگی name فیلدی را در کلاس [Cotisations] مشخص می‌کند که کلید اصلی جدول [cotisations] را در خود نگه می‌دارد. ویژگی column ستونی را در جدول [cotisations] مشخص می‌کند که به عنوان کلید اصلی عمل می‌کند. ویژگی unsaved-value برای تعریف یک کلید اصلی که هنوز تولید نشده است، استفاده می‌شود. این مقدار به NHibernate امکان می‌دهد تا نحوه ذخیره یک شیء [Cotisations] را در جدول [cotisations] تعیین کند. اگر این شیء دارای فیلدی با Id=0 باشد، یک عملیات SQL INSERT انجام خواهد داد؛ در غیر این صورت، یک عملیات SQL UPDATE انجام خواهد داد. مقدار unsaved-value به نوع فیلد Id در کلاس [Cotisations] بستگی دارد. در اینجا، نوع آن int است و مقدار پیش‌فرض برای نوع int عدد 0 است. بنابراین، یک شیء [Cotisations] که هنوز ذخیره نشده است (و در نتیجه کلید اصلی ندارد)، مقدار فیلد Id آن روی 0 تنظیم خواهد شد. اگر فیلد Id از نوع Object یا یک نوع مشتق بود، ما می‌نوشتیم unsaved-value=null.
    • خط ۶: وقتی NHibernate نیاز دارد یک شیء [Cotisations] با فیلد Id=0 را ذخیره کند، باید عملیاتی INSERT روی پایگاه داده انجام دهد که در طول آن باید مقداری را برای کلید اصلی رکورد بازیابی کند. اکثر SGBDها دارای یک روش اختصاصی برای تولید خودکار این مقدار هستند. تگ <generator> برای تعریف مکانیزم تولید کلید اصلی استفاده می‌شود. تگ <generator class="native"> نشان می‌دهد که باید از مکانیزم پیش‌فرض SGBD مورد استفاده بهره گرفته شود. در بخش 1.2 دیدیم که کلیدهای اصلی سه جدول MySQL ما دارای ویژگی autoincrement بودند. در طول عملیات خود، INSERT برای ستون ID رکورد اضافه شده، مقداری ارائه نخواهد کرد و این کار را به MySQL واگذار می‌کند تا این مقدار را تولید کند.
  • خط ۸: تگ <version> برای تعریف ستون جدول (و همچنین فیلد کلاس متناظر) استفاده می‌شود که امکان نسخه‌بندی رکوردها را فراهم می‌کند. در ابتدا، نسخه روی ۱ تنظیم می‌شود. با هر عملیات UPDATE، این مقدار افزایش می‌یابد. علاوه بر این، هر عملیات UPDATE یا DELETE با فیلتر WHERE ID= id AND VERSION=v1. بنابراین کاربر تنها در صورتی می‌تواند یک شیء را اصلاح یا حذف کند که نسخهٔ صحیح آن را در اختیار داشته باشد. در غیر این صورت، توسط NHibernate یک استثنا ایجاد می‌شود.
  • خط ۹: تگ <property> برای تعریف یک نگاشت ستون عادی استفاده می‌شود (نه کلید اصلی و نه ستون نسخه). بنابراین، خط ۹ نشان می‌دهد که ستون CSGRDS در جدول [COTISATIONS] با ویژگی CsgRds از کلاس [Cotisations] مرتبط است.

1.3.2.2. نقشه‌برداری جدول [indemnites]

بیایید جدول [indemnites] را در نظر بگیریم:

ID
کلید اصلی خودافزایشی
VERSION
شماره نسخه رکورد
BASE_HEURE
هزینه به یورو برای یک ساعت آماده‌باش
ENTRETIEN_JOUR
اضافه‌کاری روزانه به یورو برای آماده‌به‌کار
REPAS_JOUR
علاوه غذا به یورو در روز کاری
INDEMNITES_CP
اضافه‌پرداخت‌های تعطیلات. این درصد است که باید بر حقوق پایه اعمال شود.

یک سطر در این جدول می‌تواند در یک شیء از نوع [Indemnites] به صورت زیر گنجانده شود:


namespace PamNHibernateDemos {
    public class Indemnites {

        // ویژگی‌های خودکار
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual int Indice { get; set; }
        public virtual double BaseHeure { get; set; }
        public virtual double EntretienJour { get; set; }
        public virtual double RepasJour { get; set; }
        public virtual double IndemnitesCp { get; set; }

        // سازنده‌ها
        public Indemnites() {
        }

        // هویت
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}|{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
        }

    }
}

فایل نگاشت برای جدول [indemnites] <--> کلاس [Indemnites] می‌تواند به صورت زیر باشد (Indemnites.hbm.xml):


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Indemnites" table="INDEMNITES">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="Indice" column="INDICE" unique="true"/>
        <property name="BaseHeure" column="BASE_HEURE" />
        <property name="EntretienJour" column="ENTRETIEN_JOUR" />
        <property name="RepasJour" column="REPAS_JOUR" />
        <property name="IndemnitesCp" column="INDEMNITES_CP" />
    </class>
</hibernate-mapping>

در اینجا نسبت به فایل نگاشت توضیح‌داده‌شده قبلاً چیز جدیدی وجود ندارد. تنها تفاوت در خط ۹ است. ویژگی unique="true" نشان می‌دهد که یک محدودیت یکتایی بر روی ستون [INDICE] در جدول [indemnites] وجود دارد: نمی‌توان دو سطر با مقدار یکسان برای ستون [INDICE] وجود داشته باشد.

1.3.2.3. نقشه‌برداری جدول [employes]

بیایید جدول [employes] را در نظر بگیریم:

ID
کلید اصلی خودافزایشی
VERSION
شماره نسخه رکورد
PRENOM
نام کوچک کارمند
NOM
نام خانوادگی کارمند
ADRESSE
آدرس آنها
CP
کد پستی او
VILLE
شهر او
INDEMNITE_ID
کلید خارجی روی INDEMNITES (ID)

ویژگی جدید در مقایسه با جداول قبلی، وجود یک کلید خارجی است: ستون [INDEMNITE_ID] یک کلید خارجی است که به ستون [ID] در جدول [INDEMNITES] ارجاع می‌دهد. این فیلد به ردیف جدول [INDEMNITES] ارجاع می‌دهد که برای محاسبه مزایای کارمند استفاده می‌شود.

کلاس [Employe] s یک نما از جدول [employes] است و می‌تواند به صورت زیر باشد:


namespace PamNHibernateDemos {
    public class Employe {
        // ویژگی‌های خودکار
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual string SS { get; set; }
        public virtual string Nom { get; set; }
        public virtual string Prenom { get; set; }
        public virtual string Adresse { get; set; }
        public virtual string Ville { get; set; }
        public virtual string CodePostal { get; set; }
        public virtual Indemnites Indemnites { get; set; }

        // سازنده‌ها
        public Employe() {
        }

        // ToString
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
        }
    }
}

فایل نگاشت [Employe.hbm.xml] می‌تواند به صورت زیر باشد:


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Employe" table="EMPLOYES">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="SS" column="SS"/>
        <property name="Nom" column="NOM"/>
        <property name="Prenom" column="PRENOM"/>
        <property name="Adresse" column="ADRESSE"/>
        <property name="Ville" column="VILLE"/>
        <property name="CodePostal" column="CP"/>
        <many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
    </class>
</hibernate-mapping>

ویژگی جدید در خط ۱۵ قرار دارد، جایی که یک تگ جدید ظاهر می‌شود: <many-to-one>. این تگ برای نگاشت ستون کلید خارجی [INDEMNITE_ID] از جدول [EMPLOYES] به ویژگی [Indemnites] از کلاس [Employe] استفاده می‌شود:


namespace PamNHibernateDemos {
    public class Employe {
        // ویژگی‌های خودکار
..
        public virtual Indemnites Indemnites { get; set; }

...
    }
}

جدول [EMPLOYES] دارای یک کلید خارجی [INDEMNITE_ID] است که به ستون [ID] در جدول [INDEMNITES] ارجاع می‌دهد. چند (بسیاری) از سطرهای جدول [EMPLOYES] ممکن است به یک (یک) سطر در جدول [INDEMNITES] ارجاع دهند. از این رو نام تگ <many-to-one> است. این تگ در اینجا دارای ویژگی‌های زیر است:

  • column: نام ستون در جدول [EMPLOYES] را مشخص می‌کند که به‌عنوان کلید خارجی روی جدول [INDEMNITES] عمل می‌کند
  • name: ویژگی کلاس [Employe] را که با این ستون مرتبط است، مشخص می‌کند. نوع این ویژگی باید کلاسی باشد که با جدول هدف کلید خارجی مرتبط است، که در این مورد جدول [INDEMNITES] است. می‌دانیم که این کلاس، کلاس [Indemnites] است که قبلاً توصیف شده است. این موضوع در خط ۵ بالا منعکس شده است. این بدان معناست که وقتی NHibernate یک شیء [Employe] را از پایگاه داده بازیابی می‌کند، شیء مرتبط [Indemnites] را نیز بازیابی خواهد کرد.
  • cascade: این ویژگی می‌تواند مقادیر مختلفی داشته باشد:
    • save-update: یک عملیات درج (ذخیره) یا به‌روزرسانی روی شیء [Employe] باید به شیء [Indemnites] که در آن قرار دارد، منتقل شود.
    • delete: حذف یک شیء [Employe] باید به شیء [Indemnites] که آن را در خود جای داده است، منتقل شود.
    • all: عملیات درج (ذخیره)، به‌روزرسانی و حذف را منتقل می‌کند.
    • none: هیچ چیزی را منتقل نمی‌کند

در نهایت، بیایید پیکربندی NHibernate را در فایل [App.config] به یاد آوریم:


    <!-- پیکربندی NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>

خط ۱۳ نشان می‌دهد که فایل‌های نگاشت *.hbm.xml در اسمبلی [pam-nhibernate-demos] یافت خواهند شد. این رفتار پیش‌فرض نیست. باید در پروژه C# پیکربندی شود:

  • در [1]، ویژگی‌های یک فایل نگاشت را انتخاب کنید
  • به [2]؛ اقدام ساخت باید روی [Ressource incorporée] [3] تنظیم شود. این بدان معناست که هنگام ساخت پروژه، فایل نگاشت باید در اسمبلی تولید شده گنجانده شود.

1.4. API از NHibernate

بیایید به معماری پروژه نمونه خود بازگردیم:

در بخش‌های قبلی، ما NHibernate را به دو روش پیکربندی کردیم:

  • در [App.config]، اتصال پایگاه داده را پیکربندی کردیم
  • برای هر جدول در پایگاه داده، کلاس متناظر و فایل نگاشت را تعریف کردیم که امکان تبدیل بین کلاس و جدول و بالعکس را فراهم می‌کند.

اکنون باید متدهای ارائه‌شده توسط NHibernate برای دستکاری داده‌های پایگاه داده را بررسی کنیم: درج، به‌روزرسانی، حذف و فهرست.

1.4.1. شیء SessionFactory

هر عملیات NHibernate در یک جلسه انجام می‌شود. یک توالی معمول از عملیات NHibernate به شرح زیر است:

  • باز کردن یک جلسه NHibernate
  • شروع یک تراکنش در داخل جلسه
  • انجام عملیات پایداری در داخل جلسه (بارگذاری، دریافت، یافتن، CreateQuery، ذخیره، SaveOrUpdate، حذف)
  • تأیید یا لغو تراکنش
  • بستن جلسه NHibernate

یک جلسه از یک فابریک از نوع [SessionFactory] به دست می‌آید. این فابریک همان فابریکی است که توسط تگ <session-factory> در فایل پیکربندی [App.config] پیکربندی شده است:


    <!-- پیکربندی NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>

در کد C#، SessionFactory را می‌توان به صورت زیر به دست آورد:


ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();

کلاس Configuration یک کلاس در چارچوب NHibernate است. عبارت قبلی از بخش پیکربندی NHibernate در داخل [App.config] استفاده می‌کند. سپس شیء حاصل [ISessionFactory] شامل موارد زیر است:

  • اطلاعات لازم برای ایجاد یک اتصال به پایگاه داده مقصد
  • فایل‌های نگاشت بین جداول پایگاه داده و کلاس‌های پایدار که توسط NHibernate مدیریت می‌شوند.

1.4.2. سشن NHibernate

پس از ایجاد SessionFactory (این کار تنها یک‌بار انجام می‌شود)، می‌توانید جلسات مورد نیاز برای انجام عملیات پایداری NHibernate را به‌دست آورید. یک قطعه کد نمونه به شرح زیر است:


try{
      //ورود 
      using (ISession session = sessionFactory.OpenSession())
      {
        // شروع تراکنش
        using (ITransaction transaction = session.BeginTransaction())
        {
........................ opérations de persistance
          // اعتبارسنجی تراکنش
          transaction.Commit();
        }
      }
}catch (Exception ex){
....
}
  • خط ۳: یک جلسه از SessionFactory در داخل یک عبارت using ایجاد می‌شود. پس از خروج از عبارت using، جلسه به طور خودکار بسته می‌شود. بدون عبارت using، لازم است جلسه به صراحت بسته شود (session.Close()).
  • خط ۶: عملیات پایداری در داخل یک تراکنش انجام می‌شوند. یا همه آنها با موفقیت به پایان می‌رسند، یا هیچ‌کدام موفق نمی‌شوند. در داخل عبارت using,، تراکنش توسط یک Commit (خط ۱۰) نهایی می‌شود. اگر در داخل تراکنش، یک عملیات پایداری (persistence) خطا (exception) ایجاد کند، تراکنش با خروج از using به طور خودکار توسط Rollback لغو (rolled back) خواهد شد.
  • بلوک‌های try/catch در خطوط 1 و 13 اجازه می‌دهند هر استثنایی که توسط کد درون بلوک try (session, transaction, persistence) پرتاب می‌شود، مهار شود.

1.4.3. رابط ISession

اکنون برخی از متدهای رابط ISession را که توسط یک جلسه NHibernate پیاده‌سازی شده‌اند، ارائه می‌دهیم:

ITransaction BeginTransaction()
شروع یک تراکنش در داخل جلسه
ITransaction tx=session.BeginTransaction();
void Clear()
سشن را پاک می‌کند. اشیایی که در آن وجود داشتند جدا می‌شوند.
session.Clear();
void Close()
جلسه را می‌بندد. اشیایی که در آن وجود داشتند با پایگاه داده همگام‌سازی می‌شوند. این عملیات همگام‌سازی همچنین در پایان یک تراکنش انجام می‌شود. مورد دوم رایج‌ترین است.
session.Close();
IQuery CreateQuery(string queryString)
یک پرس‌وجوی HQL (زبان پرس‌وجوی هایبرنت) برای اجرای بعدی ایجاد می‌کند.
IQuery query=session.createQuery("select e from Employee e);
void Delete(object obj)
یک شیء را حذف می‌کند. این شیء ممکن است بخشی از جلسه (متصل) باشد یا نباشد (جداشده). وقتی جلسه با پایگاه داده همگام‌سازی می‌شود، عملیاتی از نوع SQL یا DELETE روی این شیء انجام خواهد شد.
// بارگذاری یک کارمند از BD
Employee e = session.Get<Employee>(143);
// حذف کارمند
session.Delete(e);
void Flush()
جلسه را مجبور می‌کند تا با پایگاه داده همگام‌سازی شود. محتوای جلسه تغییر نمی‌کند.
session.Flush();
T Get<T>(object id)
شیء T با کلید اصلی id را از پایگاه داده بازیابی می‌کند. اگر این شیء وجود نداشته باشد، نشانگر را به null تنظیم می‌کند.
// ما یک کارمند را از BD بارگذاری می‌کنیم
Employee e = session.Get<Employee>(143);
object Save(object obj)
شیء `obj` را در جلسه قرار می‌دهد. این شیء قبل از Save هیچ کلید اصلی ندارد. پس از Save، یک کلید اصلی خواهد داشت. وقتی جلسه همگام‌سازی شود، یک عملیات SQL INSERT روی پایگاه داده انجام خواهد شد.
// ایجاد یک کارمند
Employee e = new Employee(){...};
// آن را ذخیره کنید
e = session.Save(e);
SaveOrUpdate(object obj)
اگر obj کلید اولیه نداشته باشد، یک عملیات Save انجام می‌دهد، یا اگر از قبل کلید اولیه داشته باشد، یک عملیات Update انجام می‌دهد.
void Update(object obj)
شیء obj را در پایگاه داده به‌روزرسانی می‌کند. سپس عملیاتی از نوع SQL یا UPDATE روی پایگاه داده انجام می‌شود.
// بارگذاری یک کارمند از BD
Employee e = session.Get<Employee>(143);
// تغییر نام آنها
e.Nom = ...;
// به‌روزرسانی کارمند در پایگاه داده
session.Update(e);

1.4.4. رابط IQuery

رابط IQuery به شما امکان می‌دهد تا از پایگاه داده پرس‌وجو کرده و داده‌ها را استخراج کنید. ما دیده‌ایم که چگونه یک نمونه از آن ایجاد کنیم:

IQuery query=session.createQuery("select e from Employe e);

پارامتر متد createQuery یک پرس‌وجوی HQL (زبان پرس‌وجوی Hibernate) است؛ زبانی مشابه SQL که به جای جداول، کلاس‌ها را پرس‌وجو می‌کند. پرس‌وجوی بالا فهرستی از تمام کارمندان را بازمی‌گرداند. در اینجا چند مثال از پرس‌وجوهای HQL آورده شده است:

select e from Employe e where e.Nom like 'A%'
select e from Employe order by e.Nom asc
select e from Employe e where e.Indemnites.Indice=2

اکنون به برخی از متدهای رابط IQuery می‌پردازیم:

IList<T> List<T>()
نتیجه پرس‌وجو را به صورت یک لیست از اشیاء T برمی‌گرداند
IList<Employee> employees = session.createQuery("select e from Employee e order by e.Nom asc").List<Employee>();
IList List()
نتیجه پرس‌وجو را به صورت یک لیست بازمی‌گرداند، که در آن هر عنصر لیست نمایانگر یک سطر نتیجه از پرس‌وجوی Select به شکل یک آرایه از اشیاء است.
IList rows = session.createQuery("select e.Nom, e.Prenom, e.SS from Employee").List();
rows[i][j] نمایانگر ستون j از سطر i در یک نوع «ابجکت» است. بنابراین، rows[10][1] یک نوع «ابجکت» است که نمایانگر نام کوچک یک شخص است. تبدیل انواع معمولاً برای بازیابی داده‌ها در نوع دقیق خود ضروری است.
T UniqueResult<T>()
اولین شیء را از نتیجه پرس‌وجو بازمی‌گرداند
Employee e = session.createQuery("select e from Employee e where e.Nom='MARTIN'").UniqueResult<Employee>();

یک پرس‌وجوی HQL را می‌توان به صورت زیر پیکربندی کرد:

1
2
3
string numSecu;
...
Employe e=session.createQuery("select e from Employe e where e.SS=:num").SetString("num",numSecu).UniqueResult<Employe>();

در پرس‌وجوی HQL در خط ۳، :num پارامتری است که باید قبل از اجرای پرس‌وجو برای آن مقداری تعیین شود. در مثال بالا، متد SetString برای این منظور استفاده می‌شود. رابط IQuery متدهای Set مختلفی را برای تخصیص مقدار به یک پارامتر فراهم می‌کند:

  • - SetBoolean(string name, bool value)
  • - SetSingle(string name, single value)
  • - SetDouble(string name, double value)
  • - SetInt32(string name, int32 value)
  • ..

1.5. برخی نمونه‌های کد

مثال‌های زیر بر اساس معماری مورد بحث در بالا و خلاصه‌شده در زیر هستند. پایگاه داده، پایگاه داده MySQL [dbpam_nhibernate] است که آن نیز ارائه شده است. این مثال‌ها برنامه‌های کنسولی [1] هستند که از چارچوب NHibernate [3] برای دستکاری پایگاه داده [2] استفاده می‌کنند.

پروژهٔ C# حاوی مثال‌های زیر همان پروژه‌ای است که قبلاً ارائه شده است:

  • در [1]، فایل‌های DLL مورد نیاز پروژه:
    • [NHibernate]: DLL از فریم‌ورک NHibernate
    • [MySql.Data]: DLL از کانکتور ADO.NET از SGBD MySQL 5
    • [log4net]: DLL یک ابزار مورد استفاده برای تولید لاگ‌ها
  • در [2]، کلاس‌های تصویر برای جداول پایگاه داده
  • در [3]، فایل [App.config] که کل برنامه را پیکربندی می‌کند، از جمله چارچوب [NHibernate]
  • در [4]، و برنامه‌های کنسول آزمایشی. این‌ها مواردی هستند که ما بخشی از آن‌ها را ارائه خواهیم کرد.

1.5.1. استخراج محتویات پایگاه داده

برنامه [ShowDataBase.cs] به شما امکان می‌دهد محتوای پایگاه داده را نمایش دهید:


using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;


namespace PamNHibernateDemos
{
  public class ShowDataBase
  {

    private static ISessionFactory sessionFactory = null;

    // برنامه اصلی
    static void Main(string[] args)
    {
      // ابتدای کارخانه NHibernate
      sessionFactory = new Configuration().Configure().BuildSessionFactory();
      try
      {
        // نمایش محتویات پایگاه داده
        Console.WriteLine("Affichage base -------------------------------------");
        ShowDataBase1();
      }
      catch (Exception ex)
      {
        //نمایش استثنا 
        Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
      }
      finally
      {
        if (sessionFactory != null)
        {
          sessionFactory.Close();
        }
      }
      // انتظار برای ورودی صفحه‌کلید
      Console.ReadLine();
    }

    // test1
    static void ShowDataBase1()
    {
      // ورود 
      using (ISession session = sessionFactory.OpenSession())
      {
        // شروع تراکنش
        using (ITransaction transaction = session.BeginTransaction())
        {
          // در حال بازیابی فهرست کارمندان
          IList<Employe> employes = session.CreateQuery(@"select e from Employe e order by e.Nom asc").List<Employe>();
          // نمایش آن
          Console.WriteLine("--------------- liste des employés");
          foreach (Employe e in employes)
          {
            Console.WriteLine(e);
          }
          // فهرست مزایای جانبی را بازیابی می‌کند
          IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i order by i.Indice asc").List<Indemnites>();
          // نمایش آن
          Console.WriteLine("--------------- liste des indemnités");
          foreach (Indemnites i in indemnites)
          {
            Console.WriteLine(i);
          }
          // بازیابی فهرست مشارکت‌ها
          Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
          Console.WriteLine("--------------- tableau des taux de cotisations");
          Console.WriteLine(cotisations);
          // ثبت تراکنش
          transaction.Commit();
        }
      }
    }
  }
}

توضیحات:

  • خط ۱۹: شیء SessionFactory ایجاد می‌شود. این همان چیزی است که به ما امکان بازیابی اشیاء Session را می‌دهد.
  • خط ۲۴: محتویات پایگاه داده نمایش داده می‌شوند
  • خطوط ۳۱–۳۷: شیء SessionFactory در داخل عبارت finally از try. بسته می‌شود
  • خط ۴۳: متدی که محتویات پایگاه داده را نمایش می‌دهد
  • خط ۴۶: یک Session از SessionFactory دریافت می‌شود.
  • خط ۴۹: یک تراکنش آغاز می‌شود
  • خط ۵۲: پرس‌وجوی HQL برای بازیابی فهرست کارمندان. به دلیل کلید خارجی که انتهایه Employe را به انتهایه Indemnite متصل می‌کند، برای هر کارمند، فوق‌العاده او در دسترس خواهد بود.
  • خط ۶۰: پرس‌وجوی HQL برای بازیابی فهرست مزایای جانبی.
  • خط ۶۸: پرس‌وجوی HQL برای بازیابی یک سطر از جدول مشارکت‌ها.
  • خط ۷۲: پایان تراکنش
  • خط ۷۳: پایان دستور «using Itransaction» از خط ۴۹ – تراکنش به‌طور خودکار بسته می‌شود
  • خط ۷۴: پایان عبارت «using Isession» از خط ۴۶ – جلسه به طور خودکار بسته می‌شود.

خروجی صفحه به دست آمده:

Affichage base -------------------------------------
--------------- liste des employés
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[1|1,93|2|3|12]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[2|2,1|2,1|3,1|15]]

--------------- liste des indemnités
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tableau des taux de cotisations
[3,49|6,15|9,39|7,88]

توجه داشته باشید که در خطوط ۳ و ۴، هنگام استعلام یک کارمند، معادل او نیز بازیابی شد.

1.5.2. وارد کردن داده‌ها به پایگاه داده

برنامه [FillDataBase.cs] به شما امکان می‌دهد داده‌ها را در پایگاه داده وارد کنید:


using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;


namespace PamNHibernateDemos
{
  public class FillDataBase
  {

    private static ISessionFactory sessionFactory = null;

    // برنامه اصلی
    static void Main(string[] args)
    {
      // راه‌اندازی کارخانه NHibernate
      sessionFactory = new Configuration().Configure().BuildSessionFactory();
      try
      {
        // حذف محتویات پایگاه داده
        Console.WriteLine("Effacement base -------------------------------------");
        ClearDataBase1();
        Console.WriteLine("Affichage base -------------------------------------");
        ShowDataBase();
        Console.WriteLine("Remplissage base -------------------------------------");
        FillDataBase1();
        Console.WriteLine("Affichage base -------------------------------------");
        ShowDataBase();
      }
      catch (Exception ex)
      {
        //نمایش استثنا 
        Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
      }
      finally
      {
        if (sessionFactory != null)
        {
          sessionFactory.Close();
        }
      }
      // انتظار برای ورودی صفحه‌کلید
      Console.ReadLine();
    }

    // test1
    static void ShowDataBase()
    {
             // به مثال قبلی مراجعه کنید
    }

    //ClearDataBase1
    static void ClearDataBase1()
    {
      // ورود 
      using (ISession session = sessionFactory.OpenSession())
      {
        // شروع تراکنش
        using (ITransaction transaction = session.BeginTransaction())
        {
          // بازیابی فهرست کارمندان
          IList<Employe> employes = session.CreateQuery(@"select e from Employe e").List<Employe>();
          // حذف تمام کارمندان
          Console.WriteLine("--------------- suppression des employés associés");
          foreach (Employe e in employes)
          {
            session.Delete(e);
          }
          // بازیابی فهرست مزایای جانبی
          IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i").List<Indemnites>();
          // حذف مزایای شغلی
          Console.WriteLine("--------------- suppression des indemnités");
          foreach (Indemnites i in indemnites)
          {
            session.Delete(i);
          }
          //فهرست مشارکت‌ها را بازیابی می‌کند
          Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
          Console.WriteLine("--------------- suppression des taux de cotisations");
          if (cotisations != null)
          {
            session.Delete(cotisations);
          }
          // ثبت تراکنش
          transaction.Commit();
        }
      }
    }

    //FillDataBase
    static void FillDataBase1()
    {
      // ورود 
      using (ISession session = sessionFactory.OpenSession())
      {
        // شروع تراکنش
        using (ITransaction transaction = session.BeginTransaction())
        {
          //دو کمک‌هزینه ایجاد می‌شوند
          Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
          Indemnites i2 = new Indemnites() { Id = 0, Indice = 2, BaseHeure = 2.1, EntretienJour = 2.1, RepasJour = 3.1, IndemnitesCp = 15 };
          //دو کارمند ایجاد می‌شوند
          Employe e1 = new Employe() { Id = 0, SS = "254104940426058", Nom = "Jouveinal", Prenom = "Marie", Adresse = "5 rue des oiseaux", Ville = "St Corentin", CodePostal = "49203", Indemnites = i1 };
          Employe e2 = new Employe() { Id = 0, SS = "260124402111742", Nom = "Laverti", Prenom = "Justine", Adresse = "La Brûlerie", Ville = "St Marcel", CodePostal = "49014", Indemnites = i2 };
          //نرخ‌های مشارکت ایجاد می‌شوند
          Cotisations cotisations = new Cotisations() { Id = 0, CsgRds = 3.49, Csgd = 6.15, Secu = 9.39, Retraite = 7.88 };
          // همه چیز را ذخیره کن
          session.Save(e1);
          session.Save(e2);
          session.Save(cotisations);
          // تأیید تراکنش
          transaction.Commit();
        }
      }
    }

  }
}

توضیحات

  • خط ۱۹: SessionFactory ایجاد می‌شود
  • خطوط ۳۷–۴۳: در بند finally از try بسته می‌شود
  • خط ۵۵: متد ClearDataBase1 که پایگاه داده را خالی می‌کند. اصل کار به این صورت است:
    • تمام کارمندان بازیابی می‌شوند (خط ۶۴) و در یک لیست قرار می‌گیرند
    • آنها را یکی یکی حذف می‌کنیم (خطوط ۶۷–۷۰)
  • خط ۹۳: متد FillDataBase1 مقداری داده را در پایگاه داده وارد می‌کند
  • دو موجودیت Indemnites ایجاد می‌شوند (خطوط ۱۰۲ و ۱۰۳)
  • دو کارمند با این مزایای شغلی ایجاد می‌شوند (خطوط ۱۰۵، ۱۰۶)
  • یک شیء Cotisations در خط ۱۰۸ ایجاد می‌شود.
  • خطوط ۱۱۰ و ۱۱۱: دو موجودیت «کارمند» در پایگاه داده ذخیره می‌شوند
  • خط ۱۱۲: موجوده «Contributions» نیز ذخیره می‌شود
  • شاید تعجب‌آور باشد که اشیاء Indemnités در خطوط ۱۰۲ و ۱۰۳ ذخیره نشدند. در واقع، آن‌ها هم‌زمان با اشیاء Employe ذخیره شدند. برای درک این موضوع، باید نگاشت موجودیت Employe را بررسی کنیم:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Employe" table="EMPLOYES">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="SS" column="SS"/>
        <property name="Nom" column="NOM"/>
        <property name="Prenom" column="PRENOM"/>
        <property name="Adresse" column="ADRESSE"/>
        <property name="Ville" column="VILLE"/>
        <property name="CodePostal" column="CP"/>
        <many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
    </class>
</hibernate-mapping>

خط ۱۵، که رابطه کلید خارجی بین موجودیت Employe و موجودیت Indemnites را نگاشت می‌کند، دارای ویژگی cascade="save-update" است، که به این معنی است که عملیات «save " و "update" بر روی موجودیت Employe به موجودیت داخلی Indemnites منتقل می‌شوند.

نمایش صفحه دریافت‌شده:

Effacement base -------------------------------------
--------------- suppression des employés et des indemnités associées
--------------- suppression des indemnités restantes
--------------- suppression des taux de cotisations
Affichage base -------------------------------------
--------------- liste des employés
--------------- liste des indemnités
--------------- tableau des taux de cotisations

Remplissage base -------------------------------------
Affichage base -------------------------------------
--------------- liste des employés
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[1|1,93|2|3|12]]
--------------- liste des indemnités
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tableau des taux de cotisations
[3,49|6,15|9,39|7,88]

1.5.3. جستجوی کارمند

برنامه [Program.cs] شامل روش‌های مختلفی است که نشان می‌دهد چگونه به داده‌ها در پایگاه داده دسترسی پیدا کرده و آن‌ها را دستکاری کنید. در اینجا چند مورد از آن‌ها را ارائه می‌دهیم.

روش [FindEmployee] به شما امکان می‌دهد کارمند را بر اساس شماره بیمه ملی او پیدا کنید:


//FindEmployee
    static void FindEmployee() {
      try {
        // ورود 
        using (ISession session = sessionFactory.OpenSession()) {
          // شروع تراکنش
          using (ITransaction transaction = session.BeginTransaction()) {
            //جستجو برای کارمند بر اساس شماره او SS
            String numSecu = "254104940426058";
            IQuery query = session.CreateQuery(@"select e from Employe e where e.SS=:numSecu");
            Employe employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
            if (employe != null) {
              Console.WriteLine("Employe[" + numSecu + "]=" + employe);
            } else {
              Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
            }

            numSecu = "xx";
            employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
            if (employe != null) {
              Console.WriteLine("Employe[" + numSecu + "]=" + employe);
            } else {
              Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
            }

            // تثبیت تراکنش
            transaction.Commit();
          }
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

توضیحات

  • خط ۱۰: پرس‌وجوی SELECT که توسط numSecu برای اجرا پیکربندی شده است
  • خط ۱۱: تخصیص یک مقدار به پارامتر numSecu و اجرای متد UniqueResult برای بازگرداندن یک نتیجه واحد.

نمایش صفحه دریافت‌شده:

Recherche d'un employé -------------------------------------
Employe[254104940426058]=[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
Employe[xx] non trouvé...

1.5.4. وارد کردن انتیت‌های نامعتبر

روش زیر تلاش می‌کند یک انتیته‌ی مقداردهی‌نشده [Employe] را ذخیره کند.


// SaveEmptyEmployee
    static void SaveEmptyEmployee() {
      try {
        // ورود 
        using (ISession session = sessionFactory.OpenSession()) {
          // شروع تراکنش
          using (ITransaction transaction = session.BeginTransaction()) {
            //ایجاد یک رکورد خالی کارمند
            Employe e = new Employe();
            // یک فوق‌العاده غیرموجود ایجاد می‌شود
            Indemnites i = new Indemnites() { Id = 0, Indice = 3, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
            // که به کارمند لینک شده است
            e.Indemnites = i;
            // ذخیره کارمند، با خالی گذاشتن سایر فیلدها
            session.Save(e);
            // تأیید تراکنش
            transaction.Commit();
          }
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

توضیحات

بیایید کد کلاس [Employe] را به یاد بیاوریم:


namespace PamNHibernateDemos {
    public class Employe {
        // ویژگی‌های خودکار
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual string SS { get; set; }
        public virtual string Nom { get; set; }
        public virtual string Prenom { get; set; }
        public virtual string Adresse { get; set; }
        public virtual string Ville { get; set; }
        public virtual string CodePostal { get; set; }
        public virtual Indemnites Indemnites { get; set; }

        // سازنده‌ها
        public Employe() {
        }

        // ToString
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
        }
    }
}

یک شیء [Employe] که مقداردهی نشده باشد، برای تمام فیلدهای از نوع رشته‌ای خود مقدار null خواهد داشت. هنگام درج رکورد در جدول [employesNHibernate ستون‌های متناظر با این فیلدها را خالی رها خواهد کرد. با این حال، در جدول [employes]، تمام ستون‌ها دارای ویژگی «not null» هستند که مانع خالی ماندن آن‌ها می‌شود. در نتیجه، درایور ADO.NET یک استثنا (exception) پرتاب (throw) خواهد کرد:

sauvegarde d'un employé vide -------------------------------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Employe][SQL: INSERT INTO EMPLOYES (VERSION, SS, NOM, PRENOM, ADRESSE, VILLE, CP, INDEMNITE_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?)]

1.5.5. ایجاد دو امتیاز با همان شاخص در یک تراکنش واحد

در جدول [indemnites]، ستون [indice] با ویژگی «unique» تعریف شده است که مانع از یکسان بودن شاخص دو ردیف می‌شود. روش زیر دو امتیاز را با شاخص یکسان در یک تراکنش واحد ایجاد می‌کند:


// CreateIndemnites1
    static void CreateIndemnites1() {
      try {
        //ورود 
        using (ISession session = sessionFactory.OpenSession()) {
          // شروع تراکنش
          using (ITransaction transaction = session.BeginTransaction()) {
            // دو کمک‌هزینه با همان شاخص ایجاد می‌شوند
            Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
            Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
            // آنها ذخیره می‌شوند
            session.Save(i1);
            session.Save(i2);
            // تأیید تراکنش
            transaction.Commit();
          }
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

توضیحات

  • در خطوط ۹ و ۱۰، دو موجودیت Indemnites با همان شاخص ایجاد می‌شوند. با این حال، در پایگاه داده، ستون INDICE دارای ویژگی UNIQUE است.
  • خطوط ۱۲ و ۱۳ دو موجودیت Indemnites را در زمینه پایداری (persistence context) قرار می‌دهند. این کار هنگام commit تراکنش در خط ۱۵ با پایگاه داده همگام‌سازی می‌شود. این همگام‌سازی منجر به ایجاد دو موجودیت INSERT خواهد شد. دومی به دلیل محدودیت یکتایی روی ستون INDICE یک استثنا ایجاد خواهد کرد. از آنجایی که ما در یک تراکنش هستیم، اولین INSERT برگشت داده خواهد شد.

نتیجه حاصل به شرح زیر است:

Effacement base -------------------------------------
--------------- حذف کارمندان
--------------- حذف معافیت‌ها
--------------- حذف نرخ‌های مشارکت
Création de deux indemnités de même indice dans une transaction --------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Indemnites][SQL: INSERT INTO INDEMNITES (VERSION, INDICE, BASE_HEURE, ENTRETIEN_JOUR, REPAS_JOUR, INDEMNITES_CP) VALUES (?, ?, ?, ?, ?, ?)]
Affichage base -------------------------------------
--------------- فهرست کارکنان
--------------- فهرست مزایای غیرنقدی
--------------- جدول نرخ‌های مشارکت

در خط ۹ می‌بینیم که جدول [indemnites] خالی است. هیچ درج‌داده‌ای انجام نشده است.

1.5.6. ایجاد دو allowance با همان index خارج از یک transaction

روش زیر دو امتیاز را با همان شاخص بدون استفاده از تراکنش ایجاد می‌کند:


// CreateIndemnites2
    static void CreateIndemnites2() {
      try {
        //ورود 
        using (ISession session = sessionFactory.OpenSession()) {

          // دو کمک‌هزینه با شاخص یکسان ایجاد می‌شوند
          Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
          Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.94, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
          // ذخیره آنها
          session.Save(i1);
          session.Save(i2);
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

توضیحات

  • ما همان کد قبلی را داریم، اما بدون تراکنش.
  • زمینه پایداری زمانی که این زمینه بسته می‌شود، در خط ۱۳ (بسته‌شدن Session) با پایگاه داده همگام‌سازی خواهد شد. این همگام‌سازی دو عملیات INSERT را تحریک خواهد کرد. دومی به دلیل محدودیت یکتایی روی ستون INDICE با خطا مواجه خواهد شد. با این حال، از آنجایی که ما در یک تراکنش قرار نداریم، INSERT اول برگشت داده نخواهد شد.

نتیجهٔ به‌دست‌آمده به شرح زیر است:

1
2
3
4
5
6
7
Création de deux indemnités de même indice sans transaction --------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Indemnites][SQL: INSERT INTO INDEMNITES (VERSION, INDICE, BASE_HEURE, ENTRETIEN_JOUR, REPAS_JOUR, INDEMNITES_CP) VALUES (?, ?, ?, ?, ?, ?)]
Affichage base -------------------------------------
--------------- فهرست کارمندان
--------------- فهرست مزایای رفاهی
[1|1,93|2|3|12]
--------------- جدول نرخ‌های مشارکت

پایگاه داده قبل از اجرای متد خالی بود. در خط ۶ می‌بینیم که جدول [indemnites] شامل یک سطر است.