Skip to content

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

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


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


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


پیش‌نیازها


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

  1. C# 2008: [Apprentissage du langage C# Version 3.0 avec le Framework .NET 3.5 ]
  2. [Spring IoC]، موجود در URL و [Spring IoC pour .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 در آدرس [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] کامپایل شده‌اند:

 

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

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

لایه [dao] از طریق API و ADO.NET با SGBD ارتباط برقرار می‌کند (به بخش 3.3 مراجعه کنید).در معماری قبلی، کانکتور [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] تغییر یابد.

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

برای نشان دادن نحوه کار با NHibernate، از پایگاه داده MySQL [dbpam_nhibernate] که در بخش ۳.۱ توصیف شده است، استفاده خواهیم کرد. صادرات ساختار پایگاه داده به یک فایل 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 هر بار که یک رکورد اضافه می‌شود، مقادیر کلید اصلی را به طور خودکار تولید خواهد کرد. توسعه‌دهنده نیازی به نگرانی در این مورد ندارد.

6.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]، و برنامه‌های کنسول آزمایشی

6.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]، نام assembly [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] قرار داشته باشد.

6.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

6.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 برابر ۰ ذخیره کند، باید یک عملیات INSERT را روی پایگاه داده انجام دهد که در طی آن باید مقداری را برای کلید اصلی رکورد بازیابی کند. اکثر اشیاء SGBD دارای یک متد اختصاصی برای تولید خودکار این مقدار هستند. تگ <generator> برای تعریف مکانیزم مورد استفاده برای تولید کلید اصلی به کار می‌رود. تگ <generator class="native"> نشان می‌دهد که باید از مکانیزم پیش‌فرض شیء SGBD در حال استفاده استفاده شود. در بخش 6.2 دیدیم که کلیدهای اصلی سه جدول MySQL ما دارای ویژگی autoincrement بودند. در طول عملیات خود، INSERT برای ستون ID رکورد اضافه شده، هیچ ارزشی ارائه نمی‌دهد و این کار را به MySQL می‌سپارد تا این مقدار را تولید کند.
  • خط ۸: تگ <version> برای تعریف ستون جدول (و همچنین فیلد کلاس متناظر) استفاده می‌شود که امکان «نسخه‌بندی» رکوردها را فراهم می‌کند. در ابتدا، نسخه روی ۱ تنظیم می‌شود. این مقدار با هر عملیات UPDATE افزایش می‌یابد. علاوه بر این، هر عملیات UPDATE یا DELETE با فیلتر WHERE ID= id AND QZXW2HTMLP001 انجام می‌شود. بنابراین، کاربر تنها در صورتی می‌تواند یک شیء را اصلاح یا حذف کند که نسخه صحیح آن را در اختیار داشته باشد. در غیر این صورت، یک استثنا توسط UPDATE ایجاد می‌شود.482ZQX=v1. بنابراین کاربر تنها در صورتی می‌تواند یک شیء را اصلاح یا حذف کند که نسخهٔ صحیح آن را در اختیار داشته باشد. در غیر این صورت، توسط NHibernate یک استثنا ایجاد می‌شود.
  • خط ۹: تگ <property> برای تعریف نگاشت ستون عادی استفاده می‌شود (نه کلید اصلی و نه ستون نسخه). بنابراین، خط ۹ نشان می‌دهد که ستون CSGRDS در جدول [COTISATIONS] با ویژگی CsgRds از کلاس [Cotisations] مرتبط است.

6.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] وجود داشته باشد.

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

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

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

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

کلاس برای جدول [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] تنظیم شود. این بدان معناست که هنگام ساخت پروژه، فایل نگاشت باید در اسمبلی تولید شده گنجانده شود.

6.4. API از NHibernate

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

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

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

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

6.4.1. شیء SessionFactory

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

  • باز کردن یک جلسه NHibernate
  • شروع یک تراکنش در داخل جلسه
  • انجام عملیات پایداری با استفاده از جلسه (Load, Get, Find, CreateQuery, Save, SaveOrUpdate, Delete)
  • تأیید یا لغو تراکنش
  • بستن جلسه 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 مدیریت می‌شوند.

6.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 (خط ۱۰) commit می‌شود. اگر در داخل تراکنش یک عملیات پایداری استثناء پرتاب کند، تراکنش با خروج از using به‌طور خودکار توسط Rollback لغو (roll back) می‌شود.
  • بلوک‌های try/catch در خطوط 1 و 13 اجازه می‌دهند هر استثنایی که توسط کد درون بلوک try (session, transaction, persistence) پرتاب می‌شود، مهار شود.

6.4.3. رابط ISession

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

ITransaction BeginTransaction()
شروع یک تراکنش در داخل جلسه
ITransaction tx=session.BeginTransaction();
void Clear()
جلسه را پاک می‌کند. اشیایی که در آن وجود داشتند جدا می‌شوند.
session.Clear();
void Close()
جلسه را می‌بندد. اشیایی که در آن وجود داشتند با پایگاه داده همگام‌سازی می‌شوند. این عملیات همگام‌سازی همچنین در پایان یک تراکنش انجام می‌شود. مورد دوم رایج‌ترین است.
session.Close();
IQuery CreateQuery(string queryString)
یک پرس‌وجوی HQL (زبان پرس‌وجوی Hibernate) برای اجرای بعدی ایجاد می‌کند.
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);

6.4.4. رابط IQuery

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

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

پارامتر متد createQuery یک کوئری HQL (زبان کوئری هایبرنیت) است؛ زبانی مشابه 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)
  • ..

6.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] و برنامه‌های کنسول آزمایشی. این‌ها مواردی هستند که بخشی از آن‌ها را ارائه خواهیم کرد.

6.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]

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

6.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]

6.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é...

6.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 یک استثنا پرتاب خواهد کرد:

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 (?, ?, ?, ?, ?, ?, ?, ?)]

6.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 را در زمینه پایداری قرار می‌دهند. این امر هنگام 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] خالی است. هیچ درج‌داده‌ای انجام نشده است.

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

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


// 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] شامل یک سطر است.