يوضح هذا التطبيق كيفية إجراء عمليات الإنشاء والتحديث والحذف على جداول نموذجية للعملاء ومعاملات العملاء في علاقة رأس بأطراف، باستخدام Dapper micro-ORM وFluentMap ونمط المستودع/وحدة العمل. تتكون قاعدة البيانات من ملفات DBF ويمكن الوصول إليها باستخدام برنامج تشغيل Visual FoxPro OleDb. لا توجد قواعد أو مشغلات أو ما شابه ذلك يتم تنفيذها على مستوى قاعدة البيانات.
من خلال أمثلة الاستخدام، يتم توفير بعض اختبارات التكامل باستخدام XUnit في مشروع "الاختبارات"، ويوجد أيضًا تطبيق وحدة تحكم بسيط في مشروع "SimpleExample".
يعتمد هذا على النهج الذي أوضحه إيان روفوس في إدخال المدونة هذا.
مهم! يتطلب هذا التطبيق موفر Microsoft Visual FoxPro 9.0 OleDb. هذا موفر 32 بت فقط، ولا يوجد إصدار 64 بت. ونتيجة لذلك يجب أن يتم تجميع هذا التطبيق لـ x86 فقط.
Dapper هو مخطط كائن micro-ORM شائع يعمل على توسيع IDbConnection بطرق ملائمة تُرجع نتائج قاعدة البيانات المعينة لأنواع الكيانات.
فيما يلي جدول قاعدة بيانات بتنسيق DBF:
Field Field Name Type Width
1 CU_CODE Character 10
2 CU_NAME Character 50
3 CU_ADDR1 Character 50
4 CU_ADDR2 Character 50
5 CU_POSTCODE Character 10
6 CU_BALANCE Numeric 12
وهنا كيان C# الذي يمثله.
public class Customer
{
[ Key ]
public string Code { get ; set ; } = string . Empty ;
public string Name { get ; set ; } = string . Empty ;
public string Address1 { get ; set ; } = string . Empty ;
public string ? Address2 { get ; set ; } = string . Empty ;
public string ? Postcode { get ; set ; }
///
/// Gets or sets the customer balance. Not writable. It can only
/// be updated by inserting, deleting or updating a
/// transaction or transactions for this customer.
///
[ Write ( false ) ]
public float Balance { get ; set ; }
public override string ToString ( )
{
return $ " { Code } { Name } " ;
}
}
ثم يوفر Dapper القدرة على القيام بأشياء مثل:
public Customer GetByCode ( string code )
{
var cmd = @"select cu_code, cu_name, cu_addr1, cu_addr2, cu_postcode, cu_balance " ;
cmd += "from Customers where cu_code = ?" ;
return _connection . QueryFirstOrDefault < Customer > ( cmd , param : new { c = code } , transaction ) ;
}
لاحظ الطريقة التي يتم بها تنفيذ معلمات الاستعلام - لا يدعم OleDB المعلمات المسماة، بل المعلمات الموضعية فقط. لذلك، عندما يتم استخدام معلمات متعددة، يكون الترتيب أمرًا حيويًا:
public void Update ( Customer customer )
{
var cmd = @"update Customers set cu_name=?, cu_addr1=?, cu_addr2=?, cu_postcode=? where cu_code=?" ;
_connection . ExecuteScalar ( cmd , param : new
{
n = customer . Name ,
add1 = customer . Address1 ,
add2 = customer . Address2 ,
pc = customer . Postcode ,
acc = customer . Code ,
} ,
transaction ) ;
}
FluentMap هو امتداد Dapper يسمح بالإعلان بوضوح عن التعيين بين خصائص كيان C# وحقول جدول قاعدة البيانات المرتبطة.
public class CustomerEntityMap : EntityMap < Customer >
{
public CustomerEntityMap ( )
{
Map ( c => c . Code ) . ToColumn ( "cu_code" , caseSensitive : false ) ;
Map ( c => c . Name ) . ToColumn ( "cu_name" , caseSensitive : false ) ;
Map ( c => c . Address1 ) . ToColumn ( "cu_addr1" , caseSensitive : false ) ;
Map ( c => c . Address2 ) . ToColumn ( "cu_addr2" , caseSensitive : false ) ;
Map ( c => c . Postcode ) . ToColumn ( "cu_postcode" , caseSensitive : false ) ;
Map ( c => c . Balance ) . ToColumn ( "cu_balance" , caseSensitive : false ) ;
}
}
يسمح نمط وحدة العمل بتنفيذ عمليات إنشاء قاعدة البيانات وتحديثها وحذفها أو التراجع عنها كمعاملة واحدة، مما يتيح "ذرية" قاعدة البيانات حيث تحدث جميع التحديثات، أو لا تحدث أي تحديثات.
يعزل نمط المستودع عمليات قاعدة البيانات عن واجهة المستخدم ويسمح بإجراء عمليات قاعدة البيانات عن طريق إضافة عناصر أو تحديثها أو حذفها من مجموعة من الكائنات.
هناك فئتان من المستودعات في التطبيق، CustomerRepositoty
و CustomerTransactionRepository
. يتم تمرير كل معلمة من النوع IDbConnection من خلال المنشئ. يتم بعد ذلك استرداد اتصال قاعدة البيانات المراد استخدامه من تلك المعلمة:
private IDbConnection _connection { get => databaseTransaction . Connection ! ; }
لاحظ القيمة الفارغة '!' مشغل. إن حقن التبعية هذا بالطبع يجعل مزود قاعدة بيانات الفصل مستقلاً. تحتوي الفئة بعد ذلك على أساليب مختلفة لعمليات CRUD لقاعدة البيانات، مثل الطريقة التالية التي ستعيد قائمة كائنات العملاء:
public List < Customer > GetAll ( )
{
var cmd = @"select cu_code, cu_name, cu_addr1, cu_addr2, cu_postcode, cu_balance " ;
cmd += "from Customers " ;
return _connection . Query < Customer > ( cmd , transaction : transaction ) . ToList ( ) ;
}
في هذا التطبيق، يتم تمثيل وحدة العمل بواسطة فئة DapperUnitOfWork
. هذه فئة تطبق IDisposable . لديها مثيلات لكلا النوعين من المستودعات. يأخذ المنشئ سلسلة الاتصال كمعلمة ويقوم بتكوين تعيين FluentMap إذا لم يكن كذلك بالفعل. ثم يفتح اتصال OleDb ويبدأ معاملة جديدة.
public class DapperUnitOfWork : IDisposable
{
private readonly IDbConnection databaseConnection ;
private IDbTransaction databaseTransaction ;
///
/// Initializes a new instance of the class.
/// Sets up the unit of work and configures the FluentMap mappings.
/// Opens the OleDb connection.
///
/// The OleDb connection string.
public DapperUnitOfWork ( string connString )
{
ConnectionString = connString ;
if ( ! FluentMapper . EntityMaps . Any ( m => m . Key == typeof ( Entities . Customer ) ) )
{
FluentMapper . Initialize ( config =>
{
config . AddMap ( new CustomerEntityMap ( ) ) ;
config . AddMap ( new CustomerTransactionEntityMap ( ) ) ;
} ) ;
}
databaseConnection = new OleDbConnection ( ConnectionString ) ;
databaseConnection . Open ( ) ;
// Some default setup items for the connection.
// 'Set null off' - any inserts will insert the relevant empty value for the database field type instead of a null
// where a value is not supplied.
// 'set exclusive off' - tables will be opened in shared mode.
// 'set deleted on' - unintuitively this means that table rows marked as deleted will be ignored in SELECTs.
var cmd = $ "set null off { Environment . NewLine } set exclusive off { Environment . NewLine } set deleted on { Environment . NewLine } " ;
databaseConnection . Execute ( cmd ) ;
databaseTransaction = databaseConnection . BeginTransaction ( ) ;
}
تقوم خصائص المستودع الموجودة في الفصل بإدخال معاملة قاعدة البيانات في المُحضر الخاص بها، وسيقوم الكود أدناه إما بإرجاع مستودع موجود أو إنشاء مستودع جديد كما هو مطلوب:
public CustomerRepository ? CustomerRepository
{
get
{
return customerRepository ??= new CustomerRepository ( dbTransaction ) ;
}
}
يحاول الأسلوب Commit()
في الفصل تنفيذ المعاملة الحالية. سيؤدي أي استثناء إلى التراجع، وسيتم طرح الاستثناء. هناك أيضًا طريقة Rollback()
يمكن استخدامها لاستعادة المعاملة مرة أخرى بشكل صريح. في جميع الاحتمالات، سيتم التخلص من المعاملة الحالية وإنشاء معاملة جديدة وإعادة تعيين أعضاء المستودع.
public void Commit ( )
{
try
{
databaseTransaction . Commit ( ) ;
}
catch
{
databaseTransaction . Rollback ( ) ;
throw ;
}
finally
{
databaseTransaction . Dispose ( ) ;
databaseTransaction = databaseConnection . BeginTransaction ( ) ;
ResetRepositories ( ) ;
}
}
نظرًا لأن كلاً من كائنات مستودع Customer
و CustomerTransaction
تستخدم نفس المعاملة، فإن الالتزام أو التراجع يكون ذريًا ويمثل وحدة عمل واحدة.
ستقوم كل من طريقتي Commit()
و Rollback()
باستدعاء طريقة Dispose()
بشكل صريح للفئة. تهتم هذه الطريقة بالتخلص من المعاملة والاتصال الحاليين وإعادة تعيين أعضاء المستودع.
public void Dispose ( )
{
dbTransaction ? . Dispose ( ) ;
dbConnection ? . Dispose ( ) ;
GC . SuppressFinalize ( this ) ;
}
هام يعد التخلص من المعاملة والاتصال عند الانتهاء دائمًا أمرًا في غاية الأهمية في قاعدة البيانات المستندة إلى الملفات مثل DBF. يمكن لأي مقابض ملفات تُركت مفتوحة على ملف القرص أن تسبب مشكلات للتطبيقات الأخرى./
هذا مثال بسيط - وحدة فئة العمل هنا هي نوع من "كائن الله" حيث يجب أن تحتوي دائمًا على مثيل لكل نوع من فئات المستودعات. لذلك فهو مرشح لمزيد من التجريد.