1. مقدمة إلى NHibernate ORM
ملف PDF لهذا المستند متاح |هنا|.
الأمثلة الواردة في المستند متاحة |هنا|.
هذه الوثيقة هي مقدمة موجزة عن NHibernate، وهو المكافئ في .NET لإطار عمل Java Hibernate. للحصول على مقدمة شاملة، انظر:
العنوان: NHibernate in Action، المؤلف: Pierre-Henri Kuaté، الناشر: Manning، ISBN-13: 978-1932394924
ORM (Object Relational Mapper) هو مجموعة من المكتبات التي تسمح لبرنامج يستخدم قاعدة بيانات بالتفاعل معها دون إصدار أوامر SQL صريحة ودون معرفة تفاصيل نظام إدارة قواعد البيانات المستخدم.
المتطلبات الأساسية
على مقياس [مبتدئ-متوسط-متقدم]، يندرج هذا المستند في فئة [متوسط]. يتطلب فهمه شروطًا مسبقة متنوعة يمكن العثور عليها في بعض المستندات التي قمت بكتابتها:
- C# 2008: [تعلم C# الإصدار 3.0 مع إطار عمل .NET 3.5]
- [Spring IoC]، متاح على الرابط [Spring IoC for .NET]. يعرض أساسيات انعكاس التحكم (IoC) أو حقن التبعية (DI) في إطار عمل Spring.NET [Spring.NET | الصفحة الرئيسية].
يتم أحيانًا تقديم توصيات للقراءة في بداية الفقرات في هذا المستند. وهي تشير إلى المستندات السابقة.
الأدوات
الأدوات المستخدمة في هذه الدراسة الإفرادية متاحة مجانًا على شبكة الإنترنت. وهي كما يلي (ديسمبر 2011):
- 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 لمنصة Java.
- برنامج تشغيل ADO.NET 6.4.4 لنظام إدارة قواعد البيانات MySQL 5 متاح على [http://dev.mysql.com/downloads/connector/net]
تم تجميع جميع ملفات DLL المطلوبة لمشاريع Visual Studio 2010 في مجلد [libnet4]:
![]() |
1.1. دور NHIBERNATE في بنية .NET الطبقية
يمكن تصميم تطبيق .NET الذي يستخدم قاعدة بيانات على شكل طبقات كما يلي:
![]() |
تتواصل طبقة [DAO] مع نظام إدارة قواعد البيانات (DBMS) عبر واجهة برمجة التطبيقات (API) ADO.NET. دعونا نستعرض الطرق الرئيسية لهذه الواجهة.
في الوضع المتصل، يقوم التطبيق بما يلي:
- يفتح اتصالاً بمصدر البيانات
- يعمل مع مصدر البيانات في وضع القراءة/الكتابة
- يغلق الاتصال
تشارك ثلاث واجهات ADO.NET بشكل أساسي في هذه العمليات:
- IDbConnection، التي تغلف خصائص وأساليب الاتصال.
- IDbCommand، التي تغلف خصائص وأساليب الأمر SQL المنفذ.
- IDataReader، التي تغلف خصائص وأساليب نتيجة عبارة SQL SELECT.
واجهة IDbConnection
لإدارة الاتصال بقاعدة البيانات. ومن بين الأساليب M والخصائص P لهذه الواجهة ما يلي:
الاسم | النوع | الدور |
P | سلسلة اتصال قاعدة البيانات. تحدد جميع المعلمات المطلوبة لإنشاء اتصال بقاعدة بيانات معينة. | |
M | يفتح الاتصال بقاعدة البيانات المحددة بواسطة ConnectionString | |
M | يغلق الاتصال | |
M | يبدأ معاملة. | |
P | حالة الاتصال: ConnectionState.Closed، ConnectionState.Open، ConnectionState.Connecting، ConnectionState.Executing، ConnectionState.Fetching، ConnectionState.Broken |
إذا كانت Connection فئة تنفذ واجهة IDbConnection، فيمكن فتح الاتصال على النحو التالي:
واجهة IDbCommand
لتنفيذ عبارة SQL أو إجراء مخزن. ومن بين الأساليب M والخصائص P لهذه الواجهة ما يلي:
الاسم | النوع | الدور |
P | يحدد ما يجب تنفيذه - يأخذ قيمه من قائمة: - CommandType.Text: ينفذ عبارة SQL المحددة في الخاصية CommandText. هذه هي القيمة الافتراضية. - CommandType.StoredProcedure: ينفذ إجراءً مخزّنًا في قاعدة البيانات | |
P | - نص عبارة SQL المراد تنفيذها إذا كان CommandType= CommandType.Text - اسم الإجراء المخزن المراد تنفيذه إذا كان CommandType= CommandType.StoredProcedure | |
P | اتصال IDbConnection المراد استخدامه لتنفيذ عبارة SQL | |
P | معاملة IDbTransaction التي سيتم تنفيذ عبارة SQL فيها | |
P | قائمة المعلمات لعبارة SQL المعلمة. تحتوي العبارة `update articles set price=price*1.1 where id=@id` على المعلمة `@id`. | |
M | لتنفيذ عبارة SQL من نوع SELECT. يعيد هذا كائن IDataReader يمثل نتيجة عبارة SELECT. | |
M | لتنفيذ عبارة SQL Update أو Insert أو Delete. وتُرجع عدد الصفوف التي تأثرت بالعملية (تم تحديثها أو إدراجها أو حذفها). | |
M | لتنفيذ عبارة SQL Select التي ترجع نتيجة واحدة، مثل: select count(*) from articles. | |
M | لإنشاء معلمات IDbParameter لعبارة SQL المعلمة. | |
M | يتيح لك تحسين تنفيذ استعلام معلم عندما يتم تنفيذه عدة مرات بمعلمات مختلفة. |
إذا كانت Command فئة تنفذ واجهة IDbCommand، فسيتخذ تنفيذ عبارة SQL بدون معاملة الشكل التالي:
واجهة IDataReader
تُستخدم لتغليف نتائج عبارة SQL Select. يمثل كائن IDataReader جدولاً يحتوي على صفوف وأعمدة، يتم معالجتها بالتسلسل: أولاً الصف الأول، ثم الثاني، وهكذا. ومن بين الأساليب (M) والخصائص (P) لهذه الواجهة ما يلي:
الاسم | النوع | الدور |
P | عدد الأعمدة في جدول IDataReader | |
M | تُرجع GetName(i) اسم العمود i في جدول IDataReader. | |
P | يمثل Item[i] العمود رقم i في الصف الحالي في جدول IDataReader. | |
M | إلى الصف التالي من جدول IDataReader. يُرجع True إذا نجحت عملية القراءة، وإلا يُرجع False. | |
M | يغلق جدول IDataReader. | |
M | GetBoolean(i): تُرجع القيمة المنطقية للعمود i في الصف الحالي من جدول IDataReader. تتضمن الطرق المماثلة الأخرى: GetDateTime، GetDecimal، GetDouble، GetFloat، GetInt16، GetInt32، GetInt64، GetString. | |
M | Getvalue(i): تُرجع قيمة العمود i في الصف الحالي من جدول IDataReader كنوع كائن. | |
M | تُرجع IsDBNull(i) القيمة True إذا كان العمود i في الصف الحالي في جدول IDataReader لا يحتوي على قيمة، وهو ما يمثله القيمة SQL NULL. |
غالبًا ما يبدو استخدام كائن IDataReader كما يلي:
في البنية السابقة،
![]() |
يتم ربط موصل [ADO.NET] بنظام إدارة قواعد البيانات (DBMS). وبالتالي، فإن الفئة التي تنفذ واجهة [IDbConnection] هي:
- فئة [MySQLConnection] لنظام إدارة قواعد البيانات MySQL
- فئة [SQLConnection] لنظام إدارة قواعد البيانات SQLServer
وبالتالي، تعتمد طبقة [DAO] على نظام إدارة قواعد البيانات المستخدم. تعمل بعض الأطر (Linq، Ibatis.net، NHibernate) على إزالة هذا القيد عن طريق إضافة طبقة إضافية بين طبقة [DAO] وموصل [ADO.NET] لنظام إدارة قواعد البيانات المستخدم. هنا، سنستخدم إطار [NHibernate].
![]() |
في الرسم البياني أعلاه، لم تعد طبقة [DAO] تتواصل مع موصل [ADO.NET] بل مع إطار عمل NHibernate، الذي يوفر لها واجهة مستقلة عن موصل [ADO.NET] المستخدم. تتيح لك هذه البنية التبديل بين أنظمة إدارة قواعد البيانات (DBMS) دون تغيير طبقة [DAO]. ما عليك سوى تغيير موصل [ADO.NET].
1.2. نموذج قاعدة البيانات
لإظهار كيفية العمل مع NHibernate، سنستخدم قاعدة بيانات MySQL التالية [dbpam_nhibernate]:
![]() |
- في [1]، تحتوي قاعدة البيانات على ثلاثة جداول:
- [employees]: جدول يسجل موظفي مركز رعاية نهارية
- [contributions]: جدول يخزن معدلات اشتراكات الضمان الاجتماعي
- [payroll]: جدول يخزن المعلومات المستخدمة لحساب رواتب الموظفين
جدول [employees]
![]() |
- في [2]، جدول الموظفين، وفي [3]، معنى حقوله
يمكن أن يكون محتوى الجدول كما يلي:
الجدول [المساهمات]
![]() |
- في [4]، جدول المساهمات، وفي [5]، معنى حقوله
يمكن أن يكون محتوى الجدول كما يلي:
الجدول [التعويضات]
![]() |
- في [6]، جدول البدلات، وفي [7]، معنى حقولها
يمكن أن يكون محتوى الجدول كما يلي:
يؤدي تصدير بنية قاعدة البيانات إلى ملف SQL إلى النتيجة التالية:
لاحظ أنه في الأسطر 6 و20 و36، تم تعيين السمة *<a id="autoincrement"></a> * للمفاتيح الأساسية ID على *autoincrement*. وهذا يعني أن MySQL سيقوم تلقائيًا بإنشاء قيم المفتاح الأساسي كلما تمت إضافة سجل جديد. ولا داعي للمطور أن يقلق بشأن هذا الأمر.
1.3. مشروع العرض التوضيحي لـ C#
لتقديم تكوين واستخدام NHibernate، سنستخدم البنية التالية:
![]() |
سيقوم برنامج وحدة التحكم [1] بمعالجة البيانات من قاعدة البيانات [2] عبر إطار عمل [NHibernate] [3]. وهذا سيقودنا إلى عرض:
- ملفات تكوين NHibernate
- واجهة برمجة تطبيقات NHibernate
سيكون مشروع C# كما يلي:
![]() |
العناصر المطلوبة للمشروع هي كما يلي:
- في [1]، ملفات DLL المطلوبة للمشروع:
- [NHibernate]: مكتبة DLL لإطار عمل NHibernate
- [MySql.Data]: مكتبة DLL لموصل ADO.NET لنظام إدارة قواعد البيانات MySQL
- [log4net]: مكتبة DLL لإطار عمل Log4net لإنشاء السجلات
- في [2]، الفئات التي تمثل جداول قاعدة البيانات
- في [3]، ملف [App.config] الذي يقوم بتكوين التطبيق بأكمله، بما في ذلك إطار عمل [NHibernate]
- في [4]، تطبيقات وحدة التحكم الاختبارية
1.3.1. تكوين اتصال قاعدة البيانات
لنعد إلى بنية الاختبار:
![]() |
كما هو موضح أعلاه، يجب أن يكون [NHibernate] قادرًا على الوصول إلى قاعدة البيانات. وللقيام بذلك، فإنه يتطلب معلومات معينة:
- نظام إدارة قواعد البيانات (DBMS) الذي يدير قاعدة البيانات (MySQL، SQL Server، Postgres، Oracle، إلخ). وقد أضافت معظم أنظمة إدارة قواعد البيانات امتداداتها الخاصة إلى لغة SQL. ومن خلال معرفة نظام إدارة قواعد البيانات، يمكن لـ NHibernate تكييف عبارات SQL التي تصدرها مع نظام إدارة قواعد البيانات المحدد. يستخدم 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>
<!---->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!-- configuration sections
<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>
<!-- configuration NHibernate -->
<!-- This section contains the log4net configuration settings
! -->
<log4net>
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated programmatically with the log4net.Config.XmlConfigurator.Configure() instruction;-->
<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] <%X{auth}> - %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] <%X{auth}> - %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] <%X{auth}> - %m%n"/>
</layout>
</appender>
<!-- Define an output appender (where the logs can go) -->
<root>
<priority value="INFO" />
<!-- Setup the root category, set the default priority level and add the appender(s) (where the logs will go)
<appender-ref ref="LogFileAppender" />
<appender-ref ref="LogDebugAppender"/>
-->
<appender-ref ref="ConsoleAppender"/>
</root>
<!-- Specify the level for some specific namespaces -->
<!-- Level can be : ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF -->
<logger name="NHibernate">
<level value="INFO" />
</logger>
</log4net>
</configuration>
- الأسطر 4–7: تحدد أقسام التكوين في ملف [App.config]. انظر السطر 6:
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
يحدد هذا السطر قسم تكوين NHibernate في ملف [App.config]. وله خاصيتان: name و type.
- تحدد السمة [name] اسم قسم التكوين. يجب أن يكون هذا القسم محددًا هنا بالعلامات <name>...</name>، وفي هذه الحالة <hibernate-configuration>...</hibernate-configuration> في الأسطر 11–24.
- تحدد السمة [type=class,DLL] اسم الفئة المسؤولة عن معالجة القسم المحدد بواسطة السمة [name]، بالإضافة إلى ملف DLL الذي يحتوي على تلك الفئة. هنا، تسمى الفئة [NHibernate.Cfg.ConfigurationSectionHandler] وتوجد في ملف DLL [NHibernate.dll]. تذكر أن ملف DLL هذا هو أحد المراجع في المشروع قيد الدراسة.
الآن دعونا نلقي نظرة على قسم تكوين NHibernate:
<!-- 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.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>
- السطر 2: يتم تضمين تكوين NHibernate داخل علامة <hibernate-configuration>. تحدد السمة xmlns (مساحة اسم XML) الإصدار المستخدم لتكوين NHibernate. مع مرور الوقت، تطورت طريقة تكوين NHibernate. هنا، يتم استخدام الإصدار 2.2.
- السطر 3: يتم تضمين تكوين NHibernate بالكامل داخل علامة <session-factory> (السطران 3 و 14). جلسة NHibernate هي الأداة المستخدمة للعمل مع قاعدة البيانات وفقًا للمخطط:
- فتح جلسة
- العمل مع قاعدة البيانات باستخدام أساليب واجهة برمجة تطبيقات NHibernate
- إغلاق الجلسة
يتم إنشاء الجلسة بواسطة مصنع، وهو مصطلح عام يشير إلى فئة قادرة على إنشاء كائنات. تقوم الأسطر 3-14 بتكوين هذا المصنع.
- الأسطر 4 و6 و8 و9: تهيئ الاتصال بقاعدة البيانات المستهدفة. تتضمن المعلومات الرئيسية اسم نظام إدارة قواعد البيانات المستخدم، واسم قاعدة البيانات، ومعرف المستخدم، وكلمة المرور.
- السطر 4: يحدد موفر الاتصال، وهو الكيان الذي يُطلب منه الاتصال بقاعدة البيانات. قيمة الخاصية [connection.provider] هي اسم فئة NHibernate. هذه الخاصية مستقلة عن نظام إدارة قواعد البيانات المستخدم.
- السطر 6: برنامج تشغيل ADO.NET المراد استخدامه. هذا هو اسم فئة NHibernate مخصصة لنظام إدارة قواعد البيانات (DBMS) معين، وهو في هذه الحالة MySQL. تم تعليق السطر 6 لأنه ليس ضروريًا.
- السطر 8: تحدد الخاصية [dialect] لهجة SQL التي سيتم استخدامها مع نظام إدارة قواعد البيانات. هنا، هي لهجة نظام إدارة قواعد البيانات MySQL.
إذا قمت بتبديل أنظمة إدارة قواعد البيانات، كيف يمكنك العثور على لهجة NHibernate المقابلة؟ ارجع إلى مشروع C# السابق وانقر نقرًا مزدوجًا على ملف DLL [NHibernate] في علامة التبويب [References]:
![]() |
- في [1]، تعرض علامة التبويب [Object Explorer] عددًا من ملفات DLL، بما في ذلك تلك التي يشير إليها المشروع.
- في [2]، ملف DLL [NHibernate]
- في [3]، مكتبة DLL الخاصة بـ [NHibernate]. هنا يمكنك رؤية مختلف مساحات الأسماء المُعرَّفة بداخلها.
- في [4]، مساحة الاسم [NHibernate.Dialect]، حيث يمكنك العثور على الفئات التي تحدد مختلف لهجات SQL التي يمكن استخدامها.
- في [5]، فئة اللهجة لنظام إدارة قواعد البيانات MySQL 5.
![]() |
- في [6]، مساحة اسم فئة [MySqlDataDriver] المستخدمة في السطر 6 أدناه:
<!-- 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>
- الأسطر 9–11: سلسلة اتصال قاعدة البيانات. تأتي هذه السلسلة بالشكل "param1=val1;param2=val2; ...". تسمح مجموعة المعلمات المحددة بهذه الطريقة لمحرك DBMS بإنشاء اتصال. يعتمد تنسيق سلسلة الاتصال هذه على نظام إدارة قواعد البيانات المستخدم. يمكن العثور على سلاسل الاتصال الخاصة بأنظمة إدارة قواعد البيانات الرئيسية على الموقع الإلكتروني [http://www.connectionstrings.com/]. هنا، السلسلة "Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;" هي سلسلة اتصال لنظام إدارة قواعد البيانات MySQL. وهي تشير إلى ما يلي:
- Server=localhost; : نظام إدارة قواعد البيانات موجود على نفس الجهاز الذي يوجد عليه العميل الذي يحاول فتح الاتصال
- Database=dbpam_nhibernate; : قاعدة بيانات MySQL المستهدفة
- Uid=root; : المستخدم الذي يفتح الاتصال هو المستخدم root
- Pwd=; : هذا المستخدم ليس لديه كلمة مرور (حالة خاصة في هذا المثال)
- السطر 12: تحدد الخاصية [show_sql] ما إذا كان يجب على NHibernate عرض عبارات SQL التي يرسلها إلى قاعدة البيانات في سجلاته. أثناء التطوير، من المفيد تعيين هذه الخاصية إلى [true] لمعرفة ما يفعله NHibernate بالضبط.
- السطر 13: لفهم العلامة <mapping>، دعونا نراجع بنية التطبيق:
![]() |
إذا كان برنامج وحدة التحكم عميلاً مباشراً لموصل ADO.NET وأراد الحصول على قائمة الموظفين، فسيطلب من الموصل تنفيذ عبارة SQL SELECT، وسيتلقى في المقابل كائن IDataReader الذي سيتعين عليه معالجته للحصول على قائمة الموظفين التي أرادها في البداية.
في المثال أعلاه، برنامج وحدة التحكم هو عميل NHibernate، وNHibernate هو عميل موصل ADO.NET. سنرى لاحقًا أن واجهة برمجة تطبيقات NHibernate ستسمح لبرنامج وحدة التحكم بطلب قائمة الموظفين. سيقوم NHibernate بترجمة هذا الطلب إلى عبارة SQL Select ليقوم موصل ADO.NET بتنفيذها. سيُرجع الموصل كائنًا من النوع IDataReader. من هذا الكائن، يجب أن يكون NHibernate قادرًا على إنشاء قائمة الموظفين التي تم طلبها. ويتم تحقيق ذلك من خلال التكوين. يرتبط كل جدول في قاعدة البيانات بفئة C#. وبالتالي، استنادًا إلى الصفوف من جدول [employees] التي أعادها IDataReader، سيتمكن NHibernate من إنشاء قائمة من الكائنات التي تمثل الموظفين وإعادتها إلى برنامج وحدة التحكم. يتم تعريف عمليات التعيين هذه من الجدول إلى الفئة في ملفات التكوين. يستخدم NHibernate مصطلح "التعيين" لوصف هذه العلاقات.
لنعد إلى السطر 13 أدناه:
<!-- 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.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>
يحدد السطر 13 أن ملفات تكوين التعيين من الجدول إلى الفئة ستوجد في التجميع [pam-nhibernate-demos]. التجميع هو الملف القابل للتنفيذ أو مكتبة DLL الناتجة عن ترجمة مشروع. هنا، سيتم وضع ملفات التعيين في تجميع المشروع النموذجي. للعثور على اسم هذا التجميع، تحقق من خصائص المشروع:
![]() |
- في [1]، خصائص المشروع
- في علامة التبويب [Application] [2]، اسم التجميع [3] الذي سيتم إنشاؤه.
- نظرًا لأن نوع الإخراج هو [تطبيق وحدة التحكم] [4]، فسيتم تسمية الملف الذي يتم إنشاؤه عند ترجمة المشروع بـ [pam-nhibernate-demos.exe]. إذا كان نوع الإخراج هو [مكتبة الفئات] [5]، فسيتم تسمية الملف الذي يتم إنشاؤه عند ترجمة المشروع بـ [pam-nhibernate-demos.dll]
- يتم إنشاء التجميع في مجلد [bin/Release] الخاص بالمشروع [6].
من الشرح السابق، لاحظ أن جدول التعيين <--> ملفات الفئات يجب تضمينه في ملف [pam-nhibernate-demos.exe] [6].
1.3.2. تكوين جدول التعيين <-->class
لنعد إلى بنية المشروع قيد الدراسة:
![]() |
- في [1]، يستخدم برنامج وحدة التحكم أساليب واجهة برمجة تطبيقات إطار عمل NHibernate. يتبادل هذان الكتلتان الكائنات.
- في [2]، يستخدم NHibernate واجهة برمجة تطبيقات (API) موصل .NET. وهو يرسل أوامر SQL إلى نظام إدارة قواعد البيانات (DBMS) المستهدف.
سيقوم برنامج وحدة التحكم بمعالجة الكائنات التي تعكس جداول قاعدة البيانات. في هذا المشروع، تم وضع هذه الكائنات والروابط التي تربطها بجداول قاعدة البيانات في المجلد [Entities] أدناه:
![]() |
- كل جدول في قاعدة البيانات يتوافق مع فئة وملف تخطيط بين الاثنين
الجدول | الفئة | التعيين |
المساهمات | MembershipFees.cs | المساهمات.hbm.xml |
الموظفون | Employee.cs | الموظف.hbm.xml |
البدلات | Allowances.cs | Allowances.hbm.xml |
1.3.2.1. تعيين جدول [contributions]
لننظر إلى جدول [contributions]:
![]() |
|
يمكن تغليف صف في هذا الجدول في كائن من النوع [ Cotisations.cs] على النحو التالي:
namespace PamNHibernateDemos {
public class Cotisations {
// automatic properties
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; }
// manufacturers
public Cotisations() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
لقد أنشأنا خاصية تلقائية لكل عمود في جدول [contributions]. يجب إعلان كل من هذه الخصائص على أنها افتراضية لأن NHibernate سيشتق الفئة ويستبدل خصائصها. لذلك، يجب أن تكون هذه الخصائص افتراضية.
لاحظ، في السطر 1، أن الفئة تنتمي إلى مساحة الاسم [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> (السطران 2 و 14)
- السطر 4: تربط علامة <class> جدول قاعدة البيانات بفئة. هنا، الجدول [COTISATIONS] (سمة الجدول) والفئة [Cotisations] (سمة الاسم). في .NET، يجب تعريف الفئة باسمها الكامل (بما في ذلك مساحة الاسم) وبالتجميع الذي يحتوي عليها. يتم توفير هاتين المعلومتين في السطر 3. يمكن العثور على الأولى (مساحة الاسم) في تعريف الفئة. والثانية (التجميع) هي اسم تجميع المشروع. وقد سبق أن أوضحنا كيفية العثور على هذا الاسم.
- الأسطر 5-7: تُستخدم العلامة <id> لتعريف تعيين المفتاح الأساسي من جدول [contributions].
- السطر 5: تحدد السمة name الحقل في فئة [Cotisations] الذي سيحتوي على المفتاح الأساسي لجدول [cotisations]. تحدد السمة column العمود في جدول [cotisations] الذي يعمل كمفتاح أساسي. تُستخدم السمة unsaved-value لتعريف مفتاح أساسي لم يتم إنشاؤه بعد. تسمح هذه القيمة لـ NHibernate بمعرفة كيفية حفظ كائن [Cotisations] في جدول [cotisations]. إذا كان لحقل Id في هذا الكائن قيمة 0، فسيتم تنفيذ عملية SQL INSERT؛ وإلا، فسيتم تنفيذ عملية SQL UPDATE. تعتمد قيمة unsaved-value على نوع حقل Id في فئة [Cotisations]. هنا، يكون من النوع int، والقيمة الافتراضية للنوع int هي 0. وبالتالي، سيتم تعيين حقل Id لكائن [Cotisations] الذي لم يتم حفظه بعد (وبالتالي ليس له مفتاح أساسي) على 0. إذا كان حقل Id من النوع Object أو نوع مشتق، لكنا قد كتبنا unsaved-value=null.
- السطر 6: عندما يحتاج NHibernate إلى حفظ كائن [Cotisations] مع حقل Id مضبوط على 0، يجب عليه إجراء عملية INSERT على قاعدة البيانات، وخلالها يجب عليه الحصول على قيمة للمفتاح الأساسي للسجل. تمتلك معظم أنظمة إدارة قواعد البيانات (DBMS) طريقة خاصة بها لتوليد هذه القيمة تلقائيًا. تُستخدم علامة <generator> لتعريف الآلية التي سيتم استخدامها لتوليد المفتاح الأساسي. تشير العلامة <generator class="native"> إلى أنه يجب استخدام الآلية الافتراضية لنظام إدارة قواعد البيانات المستخدم. رأينا في القسم 1.2 أن المفاتيح الأساسية لجداول MySQL الثلاثة لدينا تحتوي على سمة autoincrement. أثناء عمليات INSERT، لن يوفر NHibernate قيمة لعمود ID للسجل المضاف، مما يسمح لـ MySQL بإنشاء هذه القيمة.
- السطر 8: تُستخدم علامة <version> لتعريف عمود الجدول (وكذلك حقل الفئة المقابل) الذي يسمح بـ"إصدار" السجلات. في البداية، يتم تعيين الإصدار على 1. ويتم زيادته مع كل عملية UPDATE. علاوة على ذلك، يتم تنفيذ كل عملية UPDATE أو DELETE باستخدام جملة WHERE: ID=id AND VERSION=v1. وبالتالي، لا يمكن للمستخدم تعديل كائن أو حذفه إلا إذا كان لديه الإصدار الصحيح منه. إذا لم يكن الأمر كذلك، فإن NHibernate يطلق استثناءً.
- السطر 9: تُستخدم علامة <property> لتعريف تعيين عمود عادي (ليس مفتاحًا أساسيًا ولا عمود إصدار). وبالتالي، يشير السطر 9 إلى أن عمود CSGRDS في جدول [COTISATIONS] مرتبط بخاصية CsgRds في فئة [Cotisations].
1.3.2.2. تعيين جدول [indemnites]
لنأخذ جدول [indemnites] كمثال:
![]() |
|
يمكن تغليف صف في هذا الجدول في كائن من نوع [ Indemnites] على النحو التالي:
namespace PamNHibernateDemos {
public class Indemnites {
// automatic properties
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; }
// manufacturers
public Indemnites() {
}
// identity
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>
لا يوجد شيء جديد هنا مقارنة بملف التعيين الذي تم شرحه سابقًا. الفرق الوحيد هو في السطر 9. تشير السمة unique="true" إلى وجود قيد تفرد على عمود [INDICE] في جدول [indemnites]: لا يمكن أن يكون هناك صفان بنفس القيمة لعمود [INDICE].
1.3.2.3. تعيين جدول [employes]
لننظر إلى جدول [employes]:
![]() |
|
الميزة الجديدة مقارنة بالجداول السابقة هي وجود مفتاح خارجي: العمود [INDEMNITE_ID] هو مفتاح خارجي على العمود [ID] في جدول [INDEMNITES]. يشير هذا الحقل إلى الصف في جدول [INDEMNITES] الذي سيُستخدم لحساب بدلات الموظف.
يمكن أن تكون فئة [ Employe] التي تمثل الجدول [employes] كما يلي:
namespace PamNHibernateDemos {
public class Employe {
// automatic properties
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; }
// manufacturers
public Employe() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
}
}
قد يبدو ملف التعيين [Employee.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>
توجد الميزة الجديدة في السطر 15 مع إدخال علامة جديدة: <many-to-one>. تُستخدم هذه العلامة لتعيين عمود المفتاح الخارجي [INDEMNITE_ID] من الجدول [EMPLOYEES] إلى الخاصية [Benefits] لفئة [Employee]:
namespace PamNHibernateDemos {
public class Employe {
// automatic properties
..
public virtual Indemnites Indemnites { get; set; }
...
}
}
يحتوي الجدول [EMPLOYEES] على مفتاح خارجي [INDEMNITY_ID] يشير إلى العمود [ID] في الجدول [INDEMNITIES]. يمكن أن تشير عدة (العديد من) الصفوف في جدول [EMPLOYEES] إلى صف واحد (واحد) في جدول [BENEFITS]. ومن هنا جاء اسم العلامة <many-to-one>. تحتوي هذه العلامة على السمات التالية هنا:
- column: تحدد اسم العمود في جدول [EMPLOYEES] الذي يعمل كمفتاح خارجي في جدول [BENEFITS]
- name: يحدد خاصية فئة [Employee] المرتبطة بهذا العمود. يجب أن يكون نوع هذه الخاصية هو الفئة المرتبطة بالجدول الهدف للمفتاح الخارجي، وهو في هذه الحالة جدول [Compensation]. نعلم أن هذه الفئة هي فئة [Indemnites] التي تم وصفها سابقًا. وينعكس ذلك في السطر 5 أعلاه. وهذا يعني أنه عندما يسترد NHibernate كائن [Employee] من قاعدة البيانات، فإنه سيسترد أيضًا كائن [Indemnites] المقابل.
- cascade: يمكن أن تحتوي هذه السمة على قيم مختلفة:
- save-update: يجب أن تنتقل عملية الإدراج (الحفظ) أو التحديث على كائن [Employee] إلى كائن [Benefits] الذي يحتوي عليه.
- delete: يجب أن ينتقل حذف كائن [Employee] إلى كائن [Benefits] الذي يحتوي عليه.
- all: ينقل عمليات الإدراج (الحفظ) والتحديث والحذف.
- none: لا ينقل أي شيء
أخيرًا، دعونا نراجع تكوين NHibernate في ملف [App.config]:
<!-- 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.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>
يحدد السطر 13 أن ملفات التعيين *.hbm.xml ستوجد في التجميع [pam-nhibernate-demos]. هذا ليس السلوك الافتراضي. يجب عليك تكوينه في مشروع C#:
![]() |
- في [1]، حدد خصائص ملف التعيين
- في [2]، يجب أن تكون إجراء الإنشاء [Embedded Resource] [3]. وهذا يعني أنه عند إنشاء المشروع، يجب تضمين ملف التعيين في التجميع الذي تم إنشاؤه.
1.4. واجهة برمجة تطبيقات NHibernate
لنعد إلى بنية مشروعنا النموذجي:
![]() |
في الأقسام السابقة، قمنا بتكوين NHibernate بطريقتين:
- في [App.config]، قمنا بتكوين اتصال قاعدة البيانات
- لكل جدول في قاعدة البيانات، وكتبنا الفئة التي تمثل ذلك الجدول وملف التعيين الذي يسمح لنا بالتحويل بين الفئة والجدول والعكس.
لا يزال يتعين علينا استكشاف الطرق التي يوفرها NHibernate لمعالجة بيانات قاعدة البيانات: الإدراج والتحديث والحذف والإدراج.
1.4.1. كائن SessionFactory
تتم كل عملية NHibernate ضمن جلسة عمل. وفيما يلي تسلسل نموذجي لعمليات NHibernate:
- فتح جلسة NHibernate
- بدء معاملة داخل الجلسة
- تنفيذ عمليات الاستمرارية مع الجلسة (Load، Get، Find، CreateQuery، Save، SaveOrUpdate، Delete)
- تثبيت المعاملة أو التراجع عنها
- إغلاق جلسة NHibernate
يتم الحصول على الجلسة من مصنع [SessionFactory]. هذا المصنع هو الذي تم تكوينه بواسطة العلامة <session-factory> في ملف التكوين [App.config]:
<!-- 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.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{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
........................ opérations de persistance
// transaction validation
transaction.Commit();
}
}
}catch (Exception ex){
....
}
- السطر 3: يتم إنشاء جلسة عمل من SessionFactory داخل كتلة using. عند انتهاء كتلة using، سيتم إغلاق الجلسة تلقائيًا. بدون كتلة using، سيكون من الضروري إغلاق الجلسة بشكل صريح (session.Close()).
- السطر 6: سيتم تنفيذ عمليات الاستمرارية داخل معاملة. إما أن تنجح جميعها، أو لا تنجح أي منها. داخل كتلة using، يتم تثبيت المعاملة باستخدام Commit (السطر 10). إذا أطلقت عملية استمرارية داخل المعاملة استثناءً، فسيتم التراجع عن المعاملة تلقائيًا عند الخروج من كتلة using.
- تسمح كتل try/catch في السطرين 1 و 13 بالتقاط أي استثناءات يرميها الكود داخل كتلة try (الجلسة، المعاملة، الاستمرارية).
1.4.3. واجهة ISession
سنقدم الآن بعض طرق واجهة ISession التي تم تنفيذها بواسطة جلسة NHibernate:
تبدأ معاملة في الجلسة ITransaction tx = session.BeginTransaction(); | |
يُفرغ الجلسة. وتصبح الكائنات التي كانت تحتوي عليها منفصلة. session.Clear(); | |
يغلق الجلسة. تتم مزامنة الكائنات التي تحتويها مع قاعدة البيانات. يتم تنفيذ عملية المزامنة هذه أيضًا في نهاية المعاملة. الحالة الأخيرة هي الأكثر شيوعًا. session.Close(); | |
ينشئ استعلام HQL (لغة استعلام Hibernate) لتنفيذه لاحقًا. IQuery query = session.createQuery("select e from Employee e"); | |
يحذف كائنًا. قد ينتمي هذا الكائن إلى الجلسة (مرفق) أو لا ينتمي إليها (غير مرفق). عندما تتم مزامنة الجلسة مع قاعدة البيانات، سيتم تنفيذ عملية SQL DELETE على هذا الكائن. // تحميل موظف من قاعدة البيانات Employee e = session.Get<Employee>(143); // حذفه session.Delete(e); | |
يفرض على الجلسة المزامنة مع قاعدة البيانات. لا تتغير محتويات الجلسة. session.Flush(); | |
يسترد الكائن T ذي المفتاح الأساسي id من قاعدة البيانات. إذا لم يكن هذا الكائن موجودًا، يُرجع مؤشرًا فارغًا. // تحميل موظف من قاعدة البيانات Employee e = session.Get<Employee>(143); | |
يضيف الكائن obj إلى الجلسة. لا يحتوي هذا الكائن على مفتاح أساسي قبل عملية الحفظ. بعد عملية الحفظ، يصبح له مفتاح أساسي. عند مزامنة الجلسة، سيتم تنفيذ عملية SQL INSERT على قاعدة البيانات. // إنشاء موظف Employee e = new Employee(){...}; // حفظه e = session.Save(e); | |
يُجري عملية حفظ إذا لم يكن لدى obj مفتاح أساسي، أو عملية تحديث إذا كان لديه مفتاح أساسي بالفعل. | |
يقوم بتحديث الكائن obj في قاعدة البيانات. ثم يتم تنفيذ عملية SQL UPDATE على قاعدة البيانات. // تحميل موظف من قاعدة البيانات Employee e = session.Get<Employee>(143); // تغيير الاسم e.Name = ...; // تحديث الموظف في قاعدة البيانات session.Update(e); |
1.4.4. واجهة IQuery
تسمح لك واجهة IQuery بالاستعلام عن قاعدة البيانات لاستخراج البيانات. وقد رأينا كيفية إنشاء مثيل لها:
المعلمة الخاصة بأسلوب 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:
تُرجع نتيجة الاستعلام كقائمة من كائنات T IList<Employee> employees = session.createQuery("select e from Employee e order by e.Name asc").List<Employee>(); | |
تُرجع نتيجة الاستعلام كقائمة حيث يمثل كل عنصر في القائمة صفًا من استعلام SELECT في شكل مصفوفة من الكائنات. IList rows = session.createQuery("select e.LastName, e.FirstName, e.SS from Employee e").List(); يمثل lines[i][j] العمود j من الصف i ككائن. وبالتالي، فإن lines[10][1] هو كائن يمثل الاسم الأول لشخص ما. عادةً ما يكون تحويل النوع مطلوبًا لاسترداد البيانات بنوعها الدقيق. | |
تُرجع الكائن الأول من نتيجة الاستعلام Employee e = session.createQuery("select e from Employee e where e.LastName='MARTIN'").UniqueResult<Employee>(); |
يمكن تعيين معلمات لاستعلام HQL:
في استعلام HQL في السطر 3، :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 لنظام إدارة قواعد البيانات 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;
// main program
static void Main(string[] args)
{
// factory initialization NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// database content display
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase1();
}
catch (Exception ex)
{
// we display the exception
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// keyboard wait
Console.ReadLine();
}
// test1
static void ShowDataBase1()
{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
// retrieve the list of employees
IList<Employe> employes = session.CreateQuery(@"select e from Employe e order by e.Nom asc").List<Employe>();
// we display it
Console.WriteLine("--------------- liste des employés");
foreach (Employe e in employes)
{
Console.WriteLine(e);
}
// retrieve the list of benefits
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i order by i.Indice asc").List<Indemnites>();
// we display it
Console.WriteLine("--------------- liste des indemnités");
foreach (Indemnites i in indemnites)
{
Console.WriteLine(i);
}
// retrieve the list of contributions
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- tableau des taux de cotisations");
Console.WriteLine(cotisations);
// commit transaction
transaction.Commit();
}
}
}
}
}
التفسيرات:
- السطر 19: يتم إنشاء كائن SessionFactory. وهذا ما سيسمح لنا بالحصول على كائنات Session.
- السطر 24: يتم عرض محتويات قاعدة البيانات
- الأسطر 31–37: يتم إغلاق SessionFactory في جملة finally في كتلة try.
- السطر 43: الطريقة التي تعرض محتويات قاعدة البيانات
- السطر 46: نحصل على Session من SessionFactory.
- السطر 49: بدء معاملة
- السطر 52: استعلام HQL لاسترداد قائمة الموظفين. نظرًا للمفتاح الخارجي الذي يربط كيان Employee بكيان Compensation، سيكون لكل موظف تعويضه الخاص.
- السطر 60: استعلام HQL لاسترداد قائمة البدلات.
- السطر 68: استعلام HQL لاسترداد الصف الوحيد من جدول المساهمات.
- السطر 72: نهاية المعاملة
- السطر 73: نهاية `using ITransaction` من السطر 49 – يتم إغلاق المعاملة تلقائيًا
- السطر 74: نهاية `using Isession` من السطر 46 – يتم إغلاق الجلسة تلقائيًا.
عرض الشاشة:
لاحظ أنه في الصفين 3 و 4، عند الاستعلام عن موظف، تم أيضًا إرجاع أجره.
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;
// main program
static void Main(string[] args)
{
// factory initialization NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// delete database contents
Console.WriteLine("Effacement base -------------------------------------");
ClearDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
Console.WriteLine("Remplissage base -------------------------------------");
FillDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
}
catch (Exception ex)
{
// exception is displayed
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// keyboard wait
Console.ReadLine();
}
// test1
static void ShowDataBase()
{
// see previous example
}
// ClearDataBase1
static void ClearDataBase1()
{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
// retrieve the list of employees
IList<Employe> employes = session.CreateQuery(@"select e from Employe e").List<Employe>();
// we cut all employees
Console.WriteLine("--------------- suppression des employés associés");
foreach (Employe e in employes)
{
session.Delete(e);
}
// retrieve the list of allowances
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i").List<Indemnites>();
// we do away with allowances
Console.WriteLine("--------------- suppression des indemnités");
foreach (Indemnites i in indemnites)
{
session.Delete(i);
}
// retrieve the list of contributions
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- suppression des taux de cotisations");
if (cotisations != null)
{
session.Delete(cotisations);
}
// commit transaction
transaction.Commit();
}
}
}
// FillDataBase
static void FillDataBase1()
{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
// two allowances are created
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 };
// we create two employees
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 };
// we create the contribution rates
Cotisations cotisations = new Cotisations() { Id = 0, CsgRds = 3.49, Csgd = 6.15, Secu = 9.39, Retraite = 7.88 };
// save it all
session.Save(e1);
session.Save(e2);
session.Save(cotisations);
// commit transaction
transaction.Commit();
}
}
}
}
}
تفسيرات
- السطر 19: يتم إنشاء SessionFactory
- الأسطر 37–43: يتم إغلاقه داخل جملة `finally` في كتلة `try`
- السطر 55: طريقة ClearDataBase1، التي تقوم بمسح قاعدة البيانات. وتتم العملية على النحو التالي:
- نسترد جميع الموظفين (السطر 64) في قائمة
- نحذفهم واحدًا تلو الآخر (الأسطر 67–70)
- السطر 93: تقوم طريقة FillDataBase1 بإدراج بعض البيانات في قاعدة البيانات
- نقوم بإنشاء كيانين Indemnites (السطران 102 و 103)
- ننشئ موظفين اثنين مع هذه البدلات (السطران 105 و 106)
- نقوم بإنشاء كائن Cotisations في السطر 108.
- السطران 110 و 111: يتم حفظ كيانَي Employee في قاعدة البيانات
- السطر 112: يتم حفظ كيان Cotisations بدوره
- قد يبدو من المفاجئ أن كيانات «Indemnities» في السطرين 102 و103 لم يتم حفظها. في الواقع، تم حفظها في نفس الوقت الذي تم فيه حفظ كيانات «Employee». لفهم ذلك، علينا أن نلقي نظرة على تخطيط كيان «Employee»:
<?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>
السطر 15، الذي يربط علاقة المفتاح الخارجي بين كيان Employee وكيان Allowances، يحتوي على السمة cascade="save-update"، مما يعني أن عمليات "الحفظ" و"التحديث" على كيان Employee تنتقل إلى كيان Allowances الداخلي.
عرض الشاشة الناتج:
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]
[3.49|6.15|9.39|7.88]
1.5.3. البحث عن موظف
يحتوي البرنامج [Program.cs] على طرق متنوعة توضح كيفية الوصول إلى بيانات قاعدة البيانات ومعالجتها. نقدم هنا بعضًا منها.
تسمح لك الطريقة [FindEmployee] بالبحث عن موظف باستخدام رقم الضمان الاجتماعي الخاص به:
// FindEmployee
static void FindEmployee() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// start of transaction
using (ITransaction transaction = session.BeginTransaction()) {
// search for an employee using his SS number
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é...");
}
// commit transaction
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
تفسيرات
- السطر 10: استعلام Select الذي تم تكوينه بواسطة numSecu ليتم تنفيذه
- السطر 11: تعيين قيمة للمعلمة 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. إدراج كيانات غير صالحة
تحاول الطريقة التالية حفظ كيان [Employee] غير مهيأ.
// SaveEmptyEmployee
static void SaveEmptyEmployee() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// start of transaction
using (ITransaction transaction = session.BeginTransaction()) {
// create an empty employee
Employe e = new Employe();
// we create a non-existent indemnity
Indemnites i = new Indemnites() { Id = 0, Indice = 3, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// associated with the employee
e.Indemnites = i;
// save the employee, leaving the other fields empty
session.Save(e);
// commit transaction
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
تفسيرات
دعونا نراجع كود فئة [Employee]:
namespace PamNHibernateDemos {
public class Employe {
// automatic properties
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; }
// manufacturers
public Employe() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
}
}
سيكون للكائن [Employee] غير المُهيأ قيمة فارغة لجميع حقول السلسلة الخاصة به. عند إدراج السجل في الجدول [employees]، سيترك NHibernate الأعمدة المطابقة لهذه الحقول فارغة. ومع ذلك، في الجدول [employees]، تحتوي جميع الأعمدة على السمة not null، والتي تمنع الأعمدة من عدم وجود قيمة. سيقوم برنامج تشغيل ADO.NET بعد ذلك بإصدار استثناء:
1.5.5. إنشاء بدلين بنفس الرقم التسلسلي ضمن معاملة واحدة
في جدول [indemnites]، تم تعريف عمود [index] بالسمة الفريدة، مما يمنع وجود صفين لهما نفس الفهرس. تقوم الطريقة التالية بإنشاء بدلين بنفس الفهرس ضمن معاملة واحدة:
// CreateIndemnites1
static void CreateIndemnites1() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// start of transaction
using (ITransaction transaction = session.BeginTransaction()) {
// we create two allowances with the same index
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 };
// we save them
session.Save(i1);
session.Save(i2);
// commit transaction
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
التفسيرات
- في السطرين 9 و 10، يتم إنشاء كيانين Indemnites بنفس الفهرس. ومع ذلك، في قاعدة البيانات، يحتوي عمود INDEX على قيد UNIQUE.
- تضع السطران 12 و 13 كيانَي Indemnites في سياق الاستمرارية. تتم مزامنة هذا السياق مع قاعدة البيانات عند تثبيت المعاملة في السطر 15. ستؤدي هذه المزامنة إلى تشغيل جملتي INSERT. ستتسبب الجملة الثانية في حدوث استثناء بسبب قيد التفرد على عمود INDEX. ولأننا داخل معاملة، سيتم التراجع عن جملة INSERT الأولى.
والنتيجة هي كما يلي:
السطر 9: يمكننا أن نرى أن جدول [indemnites] فارغ. لم يتم إدخال أي بيانات.
1.5.6. إنشاء بدلين بنفس المؤشر بدون معاملة
تنشئ الطريقة التالية بدلين بنفس المؤشر دون استخدام معاملة:
// CreateIndemnites2
static void CreateIndemnites2() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// we create two allowances with the same index
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 };
// we save them
session.Save(i1);
session.Save(i2);
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
تفسيرات
- لدينا نفس الكود كما في السابق، ولكن بدون معاملة.
- ستحدث مزامنة سياق الاستمرارية مع قاعدة البيانات عند إغلاق هذا السياق، في السطر 13 (إغلاق الجلسة). ستؤدي المزامنة إلى تشغيل جملتي INSERT. ستفشل الجملة الثانية بسبب قيد التفرد على عمود INDICE. ومع ذلك، نظرًا لأننا لسنا في معاملة، لن يتم التراجع عن جملة INSERT الأولى.
والنتيجة هي كما يلي:
كانت قاعدة البيانات فارغة قبل تنفيذ الطريقة. في السطر 6، يمكننا أن نرى أن جدول [indemnites] يحتوي على صف واحد.























