Aplikasi ini mendemonstrasikan cara melakukan operasi pembuatan, pembaruan, dan penghapusan atomik pada tabel Pelanggan dan Transaksi Pelanggan biasa dalam hubungan satu-ke-banyak, menggunakan micro-ORM Dapper dan FluentMap serta pola RepositoryUnit Of Work. Basis data terdiri dari file DBF dan diakses menggunakan driver Visual FoxPro OleDb. Tidak ada aturan, pemicu, atau penerapan serupa di tingkat database.
Melalui contoh penggunaan, beberapa pengujian integrasi menggunakan XUnit disediakan di proyek 'Tes', dan ada juga aplikasi konsol sederhana di proyek 'SimpleExample'.
Hal ini didasarkan pada pendekatan yang dirinci oleh Ian Rufus dalam entri blog ini.
Penting! Aplikasi ini memerlukan Penyedia OleDb Microsoft Visual FoxPro 9.0. Ini hanya penyedia 32-bit, tidak ada versi 64-bit. Akibatnya aplikasi ini harus dikompilasi hanya untuk x86.
Dapper adalah micro-ORM object mapper populer yang memperluas IDbConnection dengan metode praktis yang mengembalikan hasil database yang dipetakan ke tipe entitas.
Berikut tabel database berformat 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
dan inilah entitas C# yang mewakilinya.
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 ; }
/// <summary>
/// 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.
/// </summary>
[ Write ( false ) ]
public float Balance { get ; set ; }
public override string ToString ( )
{
return $ " { Code } { Name } " ;
}
}
Dapper kemudian memberikan kemampuan untuk melakukan hal-hal seperti:
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 ) ;
}
Perhatikan cara penerapan parameter kueri - OleDB tidak mendukung parameter bernama, hanya parameter posisi. Jadi jika beberapa parameter digunakan, urutannya sangat penting:
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 adalah ekstensi Dapper yang memungkinkan pemetaan antara properti entitas C# dan bidang tabel database terkait dideklarasikan secara eksplisit.
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 ) ;
}
}
Pola Unit Kerja memungkinkan operasi pembuatan, pembaruan, dan penghapusan basis data dilakukan atau dibatalkan sebagai satu transaksi, memungkinkan 'atomisitas' basis data di mana semua pembaruan terjadi, atau tidak ada pembaruan yang terjadi.
Pola repositori mengisolasi operasi database dari antarmuka pengguna dan memungkinkan operasi database dilakukan dengan menambahkan, memperbarui, atau menghapus item dari kumpulan objek.
Ada dua kelas repositori dalam aplikasi, CustomerRepositoty
dan CustomerTransactionRepository
. Masing-masing melewati parameter tipe IDbConnection melalui konstruktor. Koneksi database yang akan digunakan kemudian diambil dari parameter itu:
private IDbConnection _connection { get => databaseTransaction . Connection ! ; }
Perhatikan '!' yang memaafkan nol. operator. Injeksi ketergantungan ini tentu saja membuat penyedia database kelas menjadi mandiri. Kelas tersebut kemudian berisi berbagai metode untuk operasi CRUD database, seperti metode berikut yang akan mengembalikan objek Daftar Pelanggan:
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 ( ) ;
}
Pada aplikasi ini unit kerja diwakili oleh kelas DapperUnitOfWork
. Ini adalah kelas yang mengimplementasikan IDisposable . Ini memiliki contoh dari kedua jenis repositori. Konstruktor mengambil string koneksi sebagai parameter dan mengonfigurasi pemetaan FluentMap jika belum dilakukan. Ini kemudian membuka koneksi OleDb dan memulai transaksi baru.
public class DapperUnitOfWork : IDisposable
{
private readonly IDbConnection databaseConnection ;
private IDbTransaction databaseTransaction ;
/// <summary>
/// Initializes a new instance of the <see cref="DapperUnitOfWork"/> class.
/// Sets up the unit of work and configures the FluentMap mappings.
/// Opens the OleDb connection.
/// </summary>
/// <param name="connString">The OleDb connection string.</param>
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 ( ) ;
}
Properti repositori di kelas memasukkan transaksi database ke pengambilnya, kode di bawah ini akan mengembalikan repositori yang sudah ada atau membuat yang baru sesuai kebutuhan:
public CustomerRepository ? CustomerRepository
{
get
{
return customerRepository ??= new CustomerRepository ( dbTransaction ) ;
}
}
Metode Commit()
pada kelas mencoba melakukan transaksi saat ini. Pengecualian apa pun akan menyebabkan rollback, dan pengecualian tersebut akan dimunculkan. Ada juga metode Rollback()
yang dapat digunakan untuk mengembalikan transaksi secara eksplisit. Dalam segala kemungkinan, transaksi saat ini akan dibuang dan transaksi baru akan dibuat, dan anggota repositori akan direset.
public void Commit ( )
{
try
{
databaseTransaction . Commit ( ) ;
}
catch
{
databaseTransaction . Rollback ( ) ;
throw ;
}
finally
{
databaseTransaction . Dispose ( ) ;
databaseTransaction = databaseConnection . BeginTransaction ( ) ;
ResetRepositories ( ) ;
}
}
Karena objek repositori Customer
dan CustomerTransaction
menggunakan transaksi yang sama, penerapan atau rollback bersifat atomik dan mewakili satu unit kerja.
Baik metode Commit()
dan Rollback()
akan secara eksplisit memanggil metode Dispose()
kelas. Metode ini menangani pembuangan transaksi dan koneksi saat ini, dan mengatur ulang anggota repositori.
public void Dispose ( )
{
dbTransaction ? . Dispose ( ) ;
dbConnection ? . Dispose ( ) ;
GC . SuppressFinalize ( this ) ;
}
Penting Selalu membuang transaksi dan koneksi setelah selesai sangat penting dalam database berbasis file seperti DBF. Pegangan file apa pun yang dibiarkan terbuka pada file disk dapat menyebabkan masalah pada aplikasi lain./
Ini adalah contoh sederhana - unit kelas kerja di sini adalah semacam 'objek dewa' karena selalu harus berisi turunan dari setiap jenis kelas repositori. Jadi ini adalah kandidat untuk abstraksi lebih lanjut.