1. مقدمة إلى ORM NHibernate
يتوفر PDF لهذا المستند |HERE|.
الأمثلة الواردة في الوثيقة متاحة |HERE|.
هذه الوثيقة هي مقدمة موجزة لـ NHibernate، وهو المكافئ لـ .NET في إطار عمل Java Hibernate. للحصول على مقدمة كاملة، يمكن قراءة:
العنوان: NHibernate in Action، المؤلف: Pierre-Henri Kuaté، الناشر: Manning، ISBN-13: 978-1932394924
ORM (Object Relational Mapper) هو مجموعة من المكتبات التي تسمح لبرنامج يستخدم قاعدة بيانات باستغلالها دون إصدار أوامر SQL صريحة ودون معرفة خصائص SGBD المستخدم.
المتطلبات المسبقة
في نطاق [débutant-intermédiaire-avancé]، يقع هذا المستند في الجزء [intermédiaire]. يتطلب فهمه عدة متطلبات مسبقة يمكن العثور عليها في بعض المستندات التي قمت بكتابتها:
- لغة C# 2008: [تعلم لغة C# الإصدار 3.0 باستخدام Framework .NET 3.5 (2008)]
- [Spring IoC]، متاح على الرابط [Spring IoC لـ .NET (2005)]. يعرض أساسيات عكس التحكم (Inversion of Control) أو حقن التبعيات (Dependency Injection) في إطار العمل Spring.Net [Spring.NET | Homepage ].
يتم أحيانًا تقديم نصائح للقراءة في بداية فقرات هذا المستند. وهي تشير إلى المستندات السابقة.
الأدوات
الأدوات المستخدمة في دراسة الحالة هذه متاحة مجانًا على الويب. وهي كما يلي (ديسمبر 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 لـ SGBD MySQL 5 متاح على الرابط [http://dev.mysql.com/downloads/connector/net]
تم تجميع جميع ملفات DLL اللازمة لمشاريع Visual Studio 2010 في مجلد [libnet4]:
![]() |
1.1. مكان NHIBERNATE في بنية .NET ذات الطبقات
يمكن تصميم تطبيق .NET يستخدم قاعدة بيانات في طبقات بالطريقة التالية:
![]() |
يتواصل الطبقة [dao] مع SGBD عبر API ADO.NET. دعونا نستعرض الطرق الرئيسية لهذه API.
في وضع الاتصال، يقوم التطبيق بما يلي:
- يفتح اتصالاً بمصدر البيانات
- يعمل مع مصدر البيانات في وضع القراءة/الكتابة
- يغلق الاتصال
هناك ثلاث واجهات 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 (تحديث، إدراج، حذف). يتم الحصول على عدد السطور التي تأثرت بالعملية (التي تم تحديثها، إدراجها، أو حذفها). | |
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 كنوع object. | |
M | IsDBNull(i) يُرجع True إذا كان العمود رقم i في السطر الحالي من الجدول IDataReader لا يحتوي على قيمة، وهو ما يُرمز إليه بالقيمة SQL NULL. |
غالبًا ما يكون استخدام كائن IDataReader كما يلي:
في البنية السابقة،
![]() |
الموصل [ADO.NET] مرتبط بـ SGBD. وبالتالي، فإن الفئة التي تنفذ الواجهة [IDbConnection] هي:
- الفئة [MySQLConnection] لـ SGBD MySQL
- الفئة [SQLConnection] لـ SGBD SQLServer
وبالتالي، فإن الطبقة [dao] تعتمد على SGBD المستخدم. بعض الأطر (Linq، Ibatis.net، NHibernate) تزيل هذا القيد بإضافة طبقة إضافية بين الطبقة [dao] والموصل [ADO.NET] الخاص بـ SGBD المستخدم. سنستخدم هنا إطار العمل [NHibernate].
![]() |
فيما سبق، لم تعد الطبقة [dao] تتصل بالموصل [ADO.NET] بل بالإطار NHibernate الذي سيقدم لها واجهة مستقلة عن الموصل [ADO.NET] المستخدم. تسمح هذه البنية بتغيير SGBD دون تغيير الطبقة [dao]. في هذه الحالة، يجب تغيير الموصل [ADO.NET] فقط.
1.2. قاعدة البيانات النموذجية
لإظهار كيفية العمل مع NHibernate، سنستخدم قاعدة البيانات MySQL [dbpam_nhibernate] التالية:
![]() |
- في [1]، تحتوي قاعدة البيانات على ثلاثة جداول:
- [employes]: جدول يسجل الموظفات في حضانة
- [cotisations]: جدول يسجل معدلات الاشتراكات الاجتماعية
- [indemnites]: جدول يسجل المعلومات التي تسمح بحساب رواتب الموظفات
الجدول [employes]
![]() |
- في [2]، جدول الموظفين وفي [3]، معنى حقوله
قد يكون محتوى الجدول كما يلي:
الجدول [cotisations]
![]() |
- في [4]، جدول الاشتراكات، وفي [5]، معنى حقولها
قد يكون محتوى الجدول كما يلي:
الجدول [indemnites]
![]() |
- في [6]، جدول التعويضات، وفي [7]، معنى حقولها
قد يكون محتوى الجدول كما يلي:
يؤدي تصدير بنية قاعدة البيانات إلى ملف SQL إلى النتيجة التالية:
يُلاحظ في الأسطر 6 و20 و36 أن المفاتيح الأساسية ID لها السمة autoincrement. وهذا يعني أن MySQL ستقوم تلقائيًا بإنشاء قيم المفاتيح الأساسية عند إضافة كل سجل. ولا داعي للمطور أن يقلق بشأن ذلك.
1.3. مشروع C# التوضيحي
لتقديم تكوين واستخدام NHibernate، سنستخدم البنية التالية:
![]() |
سيقوم برنامج وحدة التحكم [1] بمعالجة بيانات قاعدة البيانات السابقة [2] عبر إطار العمل [NHibernate] [3]. وهذا سيقودنا إلى عرض:
- ملفات التكوين الخاصة بـ NHibernate
- API من NHibernate
سيكون مشروع C# كما يلي:
![]() |
العناصر اللازمة للمشروع هي التالية:
- في [1]، ملفات DLL التي يحتاجها المشروع:
- [NHibernate]: DLL الخاص بإطار العمل NHibernate
- [MySql.Data]: DLL الخاص بالموصل ADO.NET الخاص بـ SGBD MySQL
- [log4net]: DLL الخاص بإطار عمل Log4net الذي يسمح بإنشاء سجلات
- في [2]، فئات الصور لجداول قاعدة البيانات
- في [3]، الملف [App.config] الذي يقوم بتكوين التطبيق بأكمله، بما في ذلك إطار العمل [NHibernate]
- في [4]، وتطبيقات وحدة التحكم للاختبار
1.3.1. تكوين الاتصال بقاعدة البيانات
لنعد إلى بنية الاختبار:
![]() |
فيما سبق، يجب أن يتمكن [NHibernate] من الوصول إلى قاعدة البيانات. ولهذا الغرض، يحتاج إلى بعض المعلومات:
- SGBD الذي يدير قاعدة البيانات (MySQL، SQLServer، Postgres، Oracle، ...). أضاف معظم SGBD إلى لغة SQL امتدادات خاصة بهم. من خلال معرفة SGBD، يمكن لـ NHibernate تكييف أوامر SQL التي يصدرها مع هذا SGBD. يستخدم NHibernate مفهوم لهجة SQL.
- معلمات الاتصال بقاعدة البيانات (اسم قاعدة البيانات، اسم المستخدم المالك للاتصال، كلمة مروره)
يمكن وضع هذه المعلومات في ملف التكوين [App.config]. فيما يلي الملف الذي سيتم استخدامه مع قاعدة بيانات MySQL 5:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- أقسام التكوين -->
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
</configSections>
<!-- التكوين NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
<!-- يحتوي هذا القسم على إعدادات تكوين log4net -->
<!-- NOTE IMPORTANTE: السجلات غير نشطة بشكل افتراضي. يجب تفعيلها برمجياً باستخدام الأمر log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
<!-- تحديد ملحق الإخراج (المكان الذي يمكن أن تذهب إليه السجلات) -->
<appender name="LogFileAppender" type="log4net.Appender.FileAppender, log4net">
<param name="File" value="log.txt" />
<param name="AppendToFile" value="false" />
<layout type="log4net.Layout.PatternLayout, log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%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>
<!-- إعداد الفئة الجذرية، وتعيين مستوى الأولوية الافتراضي وإضافة الملحقات (المكان الذي ستذهب إليه السجلات) -->
<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>
- الأسطر 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=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>
- السطر 2: تكوين NHibernate موجود داخل علامة <hibernate-configuration>. يحدد السمة xmlns (Xml NameSpace) الإصدار المستخدم لتكوين NHibernate. في الواقع، تطورت طريقة تكوين NHibernate بمرور الوقت. هنا، يتم استخدام الإصدار 2.2.
- السطر 3: تضم علامة <session-factory> (السطران 3 و14) هنا تكوين NHibernate بالكامل. جلسة NHibernate هي الأداة المستخدمة للعمل مع قاعدة بيانات وفقًا للمخطط:
- فتح الجلسة
- العمل مع قاعدة البيانات عبر طرق API NHibernate
- إغلاق الجلسة
يتم إنشاء الجلسة بواسطة factory، وهو مصطلح عام يشير إلى فئة قادرة على إنشاء كائنات. تقوم الأسطر 3-14 بتكوين هذا factory.
- الأسطر 4 و6 و8 و9: تهيئ الاتصال بقاعدة البيانات المستهدفة. المعلومات الرئيسية هي اسم SGBD المستخدم، واسم قاعدة البيانات، وهوية المستخدم وكلمة مروره.
- السطر 4: يحدد مزود الاتصال، وهو الجهة التي يتم طلب الاتصال بها للوصول إلى قاعدة البيانات. قيمة الخاصية [connection.provider] هي اسم فئة NHibernate. هذه الخاصية لا تعتمد على SGBD المستخدم.
- السطر 6: برنامج التشغيل ADO.NET المطلوب استخدامه. وهو اسم فئة NHibernate مخصصة لـ SGBD معين، وهو هنا MySQL. تم تعليق السطر 6 لأنه ليس ضروريًا.
- السطر 8: تحدد الخاصية [dialect] اللهجة SQL المراد استخدامها مع SGBD. وهنا هي لهجة SGBD MySQL.
إذا قمنا بتغيير SGBD، كيف يمكننا العثور على اللهجة NHibernate الخاصة به؟ لنعد إلى مشروع C# السابق ونضغط مرتين على DLL [NHibernate] في علامة التبويب [References]:
![]() |
- في [1]، تعرض علامة التبويب [Explorateur d'objets] عددًا من DLL، بما في ذلك تلك المشار إليها في المشروع.
- في [2]، DLL [NHibernate]
- في [3]، تم تطوير DLL و [NHibernate]. نجد فيها مختلف مساحات الأسماء (namespace) المحددة فيها.
- في [4]، مساحة الأسماء [NHibernate.Dialect] حيث توجد الفئات التي تحدد اللهجات المختلفة SQL القابلة للاستخدام.
- في [5]، فئة لهجة SGBD MySQL 5.
![]() |
- في [6]، مساحة اسم فئة [MySqlDataDriver] المستخدمة في السطر 6 أدناه:
<!-- التكوين 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; ...". تسمح مجموعة المعلمات المحددة بهذه الطريقة لمحرك 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=;: هذا المستخدم ليس لديه كلمة مرور (حالة خاصة في هذا المثال)
- السطر 12: تشير الخاصية [show_sql] إلى ما إذا كان يجب على NHibernate عرض الأوامر SQL التي يرسلها إلى قاعدة البيانات في سجلاته. في مرحلة التطوير، من المفيد تعيين هذه الخاصية إلى [true] لمعرفة بالضبط ما يفعله NHibernate.
- السطر 13: لفهم العلامة <mapping>، لنعد إلى بنية التطبيق:
![]() |
إذا كان برنامج وحدة التحكم عميلاً مباشراً للموصل ADO.NET وأراد الحصول على قائمة الموظفين، فسيقوم بتنفيذ أمر SQL Select على الموصل، وسيتلقى في المقابل كائنًا من النوع IDataReader سيتعين عليه معالجته للحصول على قائمة الموظفين المطلوبة في البداية.
فيما سبق، برنامج وحدة التحكم هو عميل NHibernate و NHibernate هو عميل الموصل ADO.NET. سنرى لاحقًا أن API من NHibernate سيسمح لبرنامج وحدة التحكم بطلب قائمة الموظفين. سيقوم NHibernate بترجمة هذا الطلب إلى أمر SQL Select الذي سيقوم بتنفيذه على الموصل ADO.NET. وسيقوم هذا الموصل بإرجاع كائن من النوع IDataReader. ومن هذا الكائن، يجب أن يكون Nhibernate قادرًا على إنشاء قائمة الموظفين التي طُلبت منه. ويتم تحقيق ذلك من خلال التكوين. ترتبط كل جدول في قاعدة البيانات بفئة C#. وبالتالي، انطلاقًا من صفوف الجدول [employes] التي يعيدها IDataReader، سيتمكن NHibernate من إنشاء قائمة من الكائنات التي تمثل الموظفين وإرجاعها إلى برنامج وحدة التحكم. يتم إنشاء علاقات الجداول <--> الفئات هذه في ملفات التكوين. يستخدم NHibernate مصطلح "التعيين" لتعريف هذه العلاقات.
لنعد إلى السطر 13 أدناه:
<!-- تكوين 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] الذي سيتم إنشاؤه.
- لأن نوع الإخراج هو [Application console] [4]، فإن الملف الذي سيتم إنشاؤه عند ترجمة المشروع سيُسمى [pam-nhibernate-demos.exe]. إذا كان نوع الإخراج هو [Bibliothèque de classes] [5]، فسيُسمى الملف الذي يتم إنشاؤه عند ترجمة المشروع [pam-nhibernate-demos.dll]
- يتم إنشاء التجميع في المجلد [bin/Release] التابع للمشروع [6].
من المهم ملاحظة ما ورد في الشرح السابق من أن ملفات جداول التعيين <--> الفئات يجب أن تكون موجودة في الملف [pam-nhibernate-demos.exe] و [6].
1.3.2. تكوين ملف التعيين <--> الفئات ( )
لنعد إلى بنية المشروع قيد الدراسة:
![]() |
- في [1]، يستخدم برنامج وحدة التحكم أساليب API من إطار العمل NHibernate. يتبادل هذان الكتلتان الكائنات.
- في [2]، يستخدم NHibernate API لموصل .NET. وهي ترسل أوامر SQL إلى الهدف SGBD.
سيتعامل برنامج وحدة التحكم مع كائنات تعكس جداول قاعدة البيانات. في هذا المشروع، تم وضع هذه الكائنات والروابط التي تربطها بجداول قاعدة البيانات في المجلد [Entites] أدناه:
![]() |
- كل جدول في قاعدة البيانات يمثل فئة وملف تخطيط بين الاثنين
جدول | فئة | التعيين |
1.3.2.1. تعيين الجدول [cotisations]
لنأخذ الجدول [cotisations] كمثال:
![]() |
|
يمكن تضمين سطر من هذا الجدول في كائن من النوع [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) لأن 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] (السمة table) والفئة [Cotisations] (السمة name). في .NET، يجب تعريف الفئة باسمها الكامل (بما في ذلك مساحة الاسم) وبالتجميع الذي يحتوي عليها. يتم تقديم هاتين المعلومتين في السطر 3. يمكن العثور على الأولى (مساحة الاسم) في تعريف الفئة. أما الثانية (التجميع) فهي اسم تجميع المشروع. وقد سبق أن أوضحنا كيفية العثور على هذا الاسم.
- الأسطر 5-7: تُستخدم العلامة <id> لتعريف تعيين المفتاح الأساسي للجدول [cotisations].
- السطر 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=0 لكائن [Cotisations] الذي لم يتم حفظه بعد (وبالتالي بدون مفتاح أساسي). إذا كان الحقل Id من النوع Object أو مشتقًا منه، لكنا كتبنا unsaved-value=null.
- السطر 6: عندما يتعين على NHibernate حفظ كائن [Cotisations] يحتوي على حقل Id=0، يجب عليه إجراء عملية INSERT في قاعدة البيانات، حيث يتعين عليه خلالها الحصول على قيمة للمفتاح الأساسي للسجل. تحتوي معظم كائنات SGBD على طريقة خاصة بها لتوليد هذه القيمة تلقائيًا. تُستخدم العلامة <generator> لتحديد الآلية التي يجب استخدامها لتوليد المفتاح الأساسي. تشير العلامة <generator class="native"> إلى ضرورة استخدام الآلية الافتراضية لكائن SGBD المستخدم. لقد رأينا في الفقرة 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 {
// خصائص تلقائية
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>
لا يوجد هنا أي شيء جديد مقارنة بملف التعيين الموضح سابقًا. الفرق الوحيد موجود في السطر 9. تشير السمة unique="true" إلى وجود قيد فريد في الجدول [indemnites] على العمود [INDICE]: لا يمكن أن يكون هناك سطران بنفس القيمة للعمود [INDICE].
1.3.2.3. تعيين الجدول [employes]
لننظر إلى الجدول [employes]:
![]() |
|
الجديد مقارنة بالجداول السابقة هو وجود مفتاح خارجي: العمود [INDEMNITE_ID] هو مفتاح خارجي على العمود [ID] من الجدول [INDEMNITES]. يشير هذا الحقل إلى السطر في الجدول [INDEMNITES] الذي سيتم استخدامه لحساب تعويضات الموظف.
قد تكون صورة الفئة [Employe] للجدول [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>
يكمن الجديد في السطر 15 مع ظهور علامة جديدة: <many-to-one>. تُستخدم هذه العلامة لتعيين عمود مفتاح أجنبي [INDEMNITE_ID] من الجدول [EMPLOYES] إلى الخاصية [Indemnites] للفئة [Employe]:
namespace PamNHibernateDemos {
public class Employe {
// الخصائص التلقائية
..
public virtual Indemnites Indemnites { get; set; }
...
}
}
تحتوي الجدول [EMPLOYES] على مفتاح خارجي [INDEMNITE_ID] يشير إلى العمود [ID] في الجدول [INDEMNITES]. يمكن أن تشير عدة (many) سجلات من الجدول [EMPLOYES] إلى نفس السجل (one) في الجدول [INDEMNITES]. ومن هنا جاء اسم العلامة <many-to-one>. تحتوي هذه العلامة هنا على السمات التالية:
- column: يشير إلى اسم عمود الجدول [EMPLOYES] الذي يمثل مفتاحًا خارجيًا في الجدول [INDEMNITES]
- name: يشير إلى خاصية الفئة [Employe] المرتبطة بهذا العمود. يجب أن يكون نوع هذه الخاصية هو الفئة المرتبطة بالجدول الهدف للمفتاح الأجنبي، وهو هنا الجدول [INDEMNITES]. نعلم أن هذه الفئة هي الفئة [Indemnites] التي سبق وصفها. وهذا ما يعكسه السطر 5 أعلاه. وهذا يعني أنه عندما تسترد NHibernate كائن [Employe] من قاعدة البيانات، فإنها ستسترد أيضًا الكائن [Indemnites] المرتبط به.
- cascade: يمكن أن يكون لهذا السمة قيم مختلفة:
- save-update: يجب أن يتم ترحيل عملية الإدراج (save) أو التحديث (update) على الكائن [Employe] إلى الكائن [Indemnites] الذي يحتوي عليه.
- delete: يجب أن ينتقل حذف الكائن [Employe] إلى الكائن [Indemnites] الذي يحتوي عليه.
- all: ينقل عمليات الإدراج (save) والتحديث (update) والحذف (delete).
- 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>
تشير السطر 13 إلى أن ملفات التعيين *.hbm.xml ستوجد في التجميع [pam-nhibernate-demos]. لا يتم ذلك بشكل افتراضي. يجب تكوينه في مشروع C#:
![]() |
- في [1]، يتم تحديد خصائص ملف التعيين
- في [2]، يجب أن تكون عملية الإنشاء هي [Ressource incorporée] [3]. وهذا يعني أنه عند إنشاء المشروع، يجب دمج ملف التعيين في التجميع الذي تم إنشاؤه.
1.4. من API إلى NHibernate
لنعد إلى بنية مشروعنا النموذجي:
![]() |
في الفقرات السابقة، قمنا بتكوين NHibernate بطريقتين:
- في [App.config]، قمنا بتكوين الاتصال بقاعدة البيانات
- وكتبنا لكل جدول في قاعدة البيانات، فئة الصورة لهذا الجدول وملف التعيين الذي يسمح بالانتقال من الفئة إلى الجدول والعكس.
يبقى لنا اكتشاف الطرق التي يوفرها NHibernate لمعالجة بيانات قاعدة البيانات: الإدراج، التحديث، الحذف، القائمة.
1.4.1. الكائن SessionFactory
تتم جميع عمليات NHibernate داخل جلسة عمل. وتسلسل العمليات النموذجي لـ NHibernate هو كما يلي:
- فتح جلسة عمل NHibernate
- بدء معاملة في الجلسة
- إجراء عمليات الاستمرارية مع الجلسة (تحميل، الحصول، البحث، CreateQuery، حفظ، SaveOrUpdate، حذف)
- تأكيد (commit) أو إلغاء (rollback) المعاملة
- إغلاق الجلسة NHibernate
يتم الحصول على الجلسة من مصنع من النوع [SessionFactory]. هذا المصنع هو الذي تم تكوينه بواسطة العلامة <session-factory> في ملف التكوين [App.config]:
<!-- التكوين NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
في كود C#، يمكن الحصول على SessionFactory بالطريقة التالية:
ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
فئة Configuration هي فئة تابعة لإطار العمل NHibernate. تستخدم التعليمات السابقة قسم التكوين الخاص بـ NHibernate في [App.config]. ويحتوي الكائن [ISessionFactory] الذي تم الحصول عليه على:
- المعلومات اللازمة لإنشاء اتصال بقاعدة البيانات المستهدفة
- ملفات التعيين بين جداول قاعدة البيانات والفئات الدائمة التي يتم التعامل معها بواسطة NHibernate.
1.4.2. الجلسة NHibernate
بمجرد إنشاء SessionFactory (يتم ذلك مرة واحدة فقط)، يمكن الحصول على الجلسات التي تسمح بإجراء عمليات الاستمرارية NHibernate. فيما يلي رمز شائع:
try{
// تسجيل الدخول
using (ISession session = sessionFactory.OpenSession())
{
// بدء المعاملة
using (ITransaction transaction = session.BeginTransaction())
{
........................ opérations de persistance
// تأكيد المعاملة
transaction.Commit();
}
}
}catch (Exception ex){
....
}
- السطر 3: يتم إنشاء جلسة عمل من SessionFactory داخل جملة using. عند الخروج من جملة using، سيتم إغلاق الجلسة تلقائيًا. بدون جملة using، سيكون من الضروري إغلاق الجلسة بشكل صريح (session.Close()).
- السطر 6: ستتم عمليات الاستمرارية داخل معاملة. إما أن تنجح جميعها، أو لا تنجح أي منها. داخل الجملة using,، يتم التحقق من صحة المعاملة بواسطة Commit (السطر 10). إذا أطلقت عملية استمرارية استثناءً داخل المعاملة، فسيتم إبطال المعاملة تلقائيًا بواسطة Rollback عند الخروج من 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 Employe e); | |
يحذف كائنًا. وقد يكون هذا الكائن مرتبطًا بالجلسة (مرفق) أو غير مرتبط بها (غير مرفق). عند مزامنة الجلسة مع قاعدة البيانات، ستُجرى عملية SQL DELETE على هذا الكائن. // يتم تحميل موظف من BD الموظف e=session.Get<الموظف>(143); // يتم حذفه session.Delete(e); | |
يفرض مزامنة الجلسة مع قاعدة البيانات. لا يتغير محتوى الجلسة. session.Flush(); | |
يبحث في قاعدة البيانات عن الكائن T ذي المفتاح الأساسي id. إذا لم يكن هذا الكائن موجودًا، يجعل المؤشر null. // يتم تحميل موظف من BD Employe e=session.Get<Employe>(143); | |
يضع الكائن obj في الجلسة. لا يحتوي هذا الكائن على مفتاح أساسي قبل Save. وبعد Save، يصبح له مفتاح أساسي. عند مزامنة الجلسة، ستُجرى عملية SQL INSERT على قاعدة البيانات. // ننشئ موظفًا موظف e=new موظف(){...}; // يتم حفظه e=session.Save(e); | |
يتم تنفيذ عملية Save إذا لم يكن لدى obj مفتاح أساسي أو عملية Update إذا كان لديه مفتاح بالفعل. | |
يقوم بتحديث الكائن obj في قاعدة البيانات. ثم يتم تنفيذ عملية SQL UPDATE على قاعدة البيانات. // يتم تحميل موظف من BD الموظف e=session.Get<الموظف>(143); // نغير اسمه e.Nom=...; // يتم تحديثه في قاعدة البيانات 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<Employe> employes=session.createQuery("select e from Employe e order by e.Nom asc").List<Employe>(); | |
يعرض نتيجة الاستعلام في شكل قائمة حيث يمثل كل عنصر في القائمة سطرًا من نتائج Select في شكل مصفوفة من الكائنات. IList lines=session.createQuery("select e.Nom, e.Prenom, e.SS from Employees").List(); lignes[i][j] يمثل العمود j من السطر i في نوع object. وبالتالي، فإن lignes[10][1] هو نوع object يمثل الاسم الأول لشخص ما. عادةً ما تكون عمليات التحويل بين الأنواع ضرورية لاسترداد البيانات بنوعها الدقيق. | |
يعرض الكائن الأول من نتيجة الاستعلام Employe e=session.createQuery("select e from Employe e where e.Nom='MARTIN'").UniqueResult<Employe>(); |
يمكن تهيئة استعلام HQL:
في الاستعلام HQL في السطر 3، :num هو معلمة يجب أن تتلقى قيمة قبل تنفيذ الاستعلام. فيما سبق، يتم استخدام الطريقة SetString لهذا الغرض. تحتوي الواجهة IQuery على طرق Set متنوعة لتعيين قيمة لمعلمة:
- - SetBoolean(string name, bool value)
- - SetSingle(string name, single value)
- - SetDouble(سلسلة اسم، عدد مزدوج القيمة)
- - SetInt32(اسم سلسلة، قيمة int32)
- ..
1.5. بعض أمثلة الكود
تستند الأمثلة التالية إلى البنية التي تمت دراستها سابقًا والمذكورة أدناه. قاعدة البيانات هي قاعدة البيانات MySQL [dbpam_nhibernate] التي تم عرضها أيضًا. الأمثلة عبارة عن برامج وحدة التحكم [1] التي تستخدم إطار العمل NHibernate [3] لمعالجة قاعدة البيانات [2].
![]() |
مشروع C# الذي تتضمنه الأمثلة التالية هو المشروع الذي تم عرضه سابقًا:
![]() |
- في [1]، و DLL التي يحتاجها المشروع:
- [NHibernate]: DLL الخاص بإطار العمل NHibernate
- [MySql.Data]: DLL لموصل ADO.NET الخاص بـ SGBD MySQL 5
- [log4net]: DLL لأداة تسمح بإنشاء سجلات
- في [2]، فئات الصور لجداول قاعدة البيانات
- في [3]، الملف [App.config] الذي يقوم بتكوين التطبيق بأكمله، بما في ذلك إطار العمل [NHibernate]
- في [4]، وتطبيقات وحدة التحكم للاختبار. هذه هي التطبيقات التي سنقدمها جزئيًا.
1.5.1. الحصول على محتوى قاعدة البيانات
يتيح البرنامج [ShowDataBase.cs] عرض محتوى قاعدة البيانات:
using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;
namespace PamNHibernateDemos
{
public class ShowDataBase
{
private static ISessionFactory sessionFactory = null;
// البرنامج الرئيسي
static void Main(string[] args)
{
// تهيئة المصنع NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// عرض محتوى قاعدة البيانات
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase1();
}
catch (Exception ex)
{
// عرض الاستثناء
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// انتظار لوحة المفاتيح
Console.ReadLine();
}
// اختبار 1
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();
}
}
}
}
}
التفسيرات:
- السطر 19: يتم إنشاء الكائن SessionFactory. وهو الذي سيسمح لنا بالحصول على الكائنات Session.
- السطر 24: يتم عرض محتوى قاعدة البيانات
- الأسطر 31-37: يتم إغلاق SessionFactory في جملة finally التابعة لـ try.
- السطر 43: الطريقة التي تعرض محتوى قاعدة البيانات
- السطر 46: يتم الحصول على Session من SessionFactory.
- السطر 49: نبدأ معاملة
- السطر 52: استعلام HQL لاسترداد قائمة الموظفين. وبسبب المفتاح الخارجي الذي يربط الكيان Employe بالكيان Indemnite، فمع كل موظف، سنحصل على تعويضه.
- السطر 60: استعلام HQL للحصول على قائمة التعويضات.
- السطر 68: استعلام HQL للحصول على السطر الوحيد من جدول الاشتراكات.
- السطر 72: نهاية المعاملة
- السطر 73: نهاية استخدام Itransaction في السطر 49 – يتم إغلاق المعاملة تلقائيًا
- السطر 74: نهاية استخدام 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;
// البرنامج الرئيسي
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();
}
// اختبار 1
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();
}
}
}
}
}
تفسيرات
- السطر 19: تم إنشاء SessionFactory
- الأسطر 37-43: يتم إغلاقه في جملة finally التابعة لـ try
- السطر 55: الطريقة ClearDataBase1 التي تفرغ قاعدة البيانات. المبدأ هو كما يلي:
- يتم استرداد جميع الموظفين (السطر 64) في قائمة
- يتم حذفهم واحدًا تلو الآخر (الأسطر 67-70)
- السطر 93: الطريقة FillDataBase1 التي تُدرج بعض البيانات في قاعدة البيانات
- يتم إنشاء كيانين Indemnites (السطران 102 و 103)
- يتم إنشاء موظفين اثنين يحصلان على هذه التعويضات (السطران 105 و 106)
- يتم إنشاء كائن Cotisations في السطر 108.
- السطران 110 و 111: يتم حفظ الكيانين Employe في قاعدة البيانات
- السطر 112: يتم حفظ الكيان Cotisations بدوره
- قد يثير الدهشة أن الكيانات Indemnités في السطرين 102 و103 لم يتم حفظها. في الواقع، تم حفظها في نفس الوقت مع الكيانات 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>
السطر 15 الذي يربط علاقة المفتاح الأجنبي بين الكيان Employe والكيان Indemnites يحتوي على السمة cascade= "save-update "، مما يؤدي إلى أن العمليات " save " و " update " للكيان Employe تنتقل إلى الكيان الداخلي Indemnites.
الشاشة المعروضة:
Effacement base -------------------------------------
--------------- suppression des employés et des indemnités associées
--------------- suppression des indemnités restantes
--------------- suppression des taux de cotisations
Affichage base -------------------------------------
--------------- liste des employés
--------------- liste des indemnités
--------------- tableau des taux de cotisations
Remplissage base -------------------------------------
Affichage base -------------------------------------
--------------- liste des employés
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[1|1,93|2|3|12]]
--------------- liste des indemnités
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tableau des taux de cotisations
[3,49|6,15|9,39|7,88]
1.5.3. البحث عن موظف
يحتوي البرنامج [Program.cs] على طرق متنوعة توضح كيفية الوصول إلى بيانات قاعدة البيانات ومعالجتها. نقدم هنا بعضًا منها.
تسمح الطريقة [FindEmployee] بالعثور على موظف بناءً على رقم الضمان الاجتماعي الخاص به:
// FindEmployee
static void FindEmployee() {
try {
// فتح الجلسة
using (ISession session = sessionFactory.OpenSession()) {
// بدء المعاملة
using (ITransaction transaction = session.BeginTransaction()) {
// البحث عن موظف باستخدام رقمه SS
String numSecu = "254104940426058";
IQuery query = session.CreateQuery(@"select e from Employe e where e.SS=:numSecu");
Employe employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
if (employe != null) {
Console.WriteLine("Employe[" + numSecu + "]=" + employe);
} else {
Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
}
numSecu = "xx";
employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
if (employe != null) {
Console.WriteLine("Employe[" + numSecu + "]=" + employe);
} else {
Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
}
// تثبيت المعاملة
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
التفسيرات
- السطر 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. إدراج كيانات غير صالحة
تحاول الطريقة التالية حفظ كيان [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 لجميع حقوله من النوع string. عند إدراج السجل في الجدول [employes]، سيترك NHibernate الأعمدة المطابقة لهذه الحقول فارغة. ولكن في الجدول [employes]، تحتوي جميع الأعمدة على السمة not null، مما يمنع وجود أعمدة بدون قيمة. سيقوم برنامج التشغيل ADO.NET عندئذٍ بإطلاق استثناء:
1.5.5. إنشاء تعويضين بنفس المؤشر داخل معاملة واحدة
في الجدول [indemnites]، تم تعريف العمود [indice] بالسمة unique، مما يمنع وجود سطرين بنفس المؤشر. تقوم الطريقة التالية بإنشاء تعويضين بنفس المؤشر داخل معاملة واحدة:
// CreateIndemnites1
static void CreateIndemnites1() {
try {
// تسجيل الدخول
using (ISession session = sessionFactory.OpenSession()) {
// بدء المعاملة
using (ITransaction transaction = session.BeginTransaction()) {
// يتم إنشاء تعويضين بنفس المؤشر
Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// يتم حفظهما
session.Save(i1);
session.Save(i2);
// تثبيت المعاملة
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
تفسيرات
- في السطرين 9 و 10، يتم إنشاء كيانين Indemnites لهما نفس الفهرس. ولكن في قاعدة البيانات، يحتوي العمود INDICE على السمة UNIQUE.
- السطران 12 و 13 يضعان الكيانين Indemnites في سياق الاستمرارية. ويتم مزامنة هذا السياق مع قاعدة البيانات عند التحقق من صحة المعاملة في السطر 15. ستؤدي هذه المزامنة إلى إنشاء كيانين INSERT. سيؤدي الثاني إلى استثناء بسبب تفرد العمود INDICE. ولأننا داخل معاملة، سيتم إلغاء INSERT الأول.
والنتيجة التي تم الحصول عليها هي كما يلي:
في السطر 9، يمكننا أن نرى أن الجدول [indemnites] فارغ. لم يتم إدراج أي بيانات.
1.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);
}
}
توضيحات
- لدينا نفس الكود السابق ولكن بدون معاملة.
- سيتم مزامنة سياق الاستمرارية مع قاعدة البيانات عند إغلاق هذا السياق، السطر 13 (إغلاق Session). ستؤدي المزامنة إلى ظهور اثنين من INSERT. سيفشل الثاني بسبب تفرد عمود INDICE. ولكن بما أننا لسنا في معاملة، فإن INSERT الأول لن يتم إلغاؤه.
والنتيجة التي تم الحصول عليها هي كما يلي:
كانت قاعدة البيانات فارغة قبل تنفيذ الطريقة. في السطر 6، يمكننا أن نرى أن الجدول [indemnites] يحتوي على سطر واحد.























