このアプリケーションでは、Dapper マイクロ ORM と FluentMap、および Repository/Unit Of Work パターンを使用して、1 対多の関係にある一般的な Customer テーブルと Customer Transaction テーブルに対してアトミックな作成、更新、削除操作を実行する方法を示します。データベースは DBF ファイルで構成されており、Visual FoxPro OleDb ドライバーを使用してアクセスします。データベース レベルで実装されるルール、トリガーなどはありません。
使用例として、XUnit を使用したいくつかの統合テストが「Tests」プロジェクトで提供されています。また、「SimpleExample」プロジェクトには単純なコンソール アプリケーションもあります。
これは、Ian Rufus がこのブログ エントリで詳しく説明したアプローチに基づいています。
重要!このアプリケーションには、Microsoft Visual FoxPro 9.0 OleDb Provider が必要です。これは 32 ビットのみのプロバイダーであり、64 ビット バージョンはありません。そのため、このアプリケーションは x86 専用にコンパイルする必要があります。
Dapper は、エンティティ タイプにマップされたデータベース結果を返す便利なメソッドで IDbConnection を拡張する、人気のあるマイクロ ORM オブジェクト マッパーです。
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 は、C# エンティティ プロパティと関連するデータベース テーブル フィールド間のマッピングを明示的に宣言できるようにする Dapper 拡張機能です。
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 ) ;
}
}
Unit Of Work パターンでは、データベースの作成、更新、削除の操作を 1 つのトランザクションとして実行またはロールバックできるため、すべての更新が行われるか、まったく更新が行われないデータベースの「アトミック性」が可能になります。
リポジトリ パターンは、データベース操作をユーザー インターフェイスから分離し、オブジェクトのコレクションに対して項目を追加、更新、または削除することによってデータベース操作を実行できるようにします。
アプリケーションには、 CustomerRepositoty
とCustomerTransactionRepository
という 2 つのリポジトリ クラスがあります。それぞれに、コンストラクターを通じてIDbConnection型のパラメーターが渡されます。次に、使用するデータベース接続がそのパラメータから取得されます。
private IDbConnection _connection { get => databaseTransaction . Connection ! ; }
Null を許容する「!」に注意してください。オペレーター。もちろん、この依存関係の注入により、クラス データベース プロバイダーは独立します。このクラスには、Customer オブジェクトのリストを返す次のメソッドなど、データベース 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
リポジトリ オブジェクトは両方とも同じトランザクションを使用しているため、コミットまたはロールバックはアトミックであり、1 つの作業単位を表します。
Commit()
とRollback()
メソッドは両方とも、クラスのDispose()
メソッドを明示的に呼び出します。このメソッドは、現在のトランザクションと接続の破棄、およびリポジトリ メンバーのリセットを処理します。
public void Dispose ( )
{
dbTransaction ? . Dispose ( ) ;
dbConnection ? . Dispose ( ) ;
GC . SuppressFinalize ( this ) ;
}
重要DBF などのファイルベースのデータベースでは、終了時にトランザクションと接続を常に破棄することが非常に重要です。ディスク ファイル上でファイル ハンドルを開いたままにしておくと、他のアプリケーションに問題が発生する可能性があります。/
これは単純な例です。ここでの作業単位クラスは、常に各タイプのリポジトリ クラスのインスタンスを含む必要があるため、一種の「神オブジェクト」です。したがって、これはさらなる抽象化の候補です。