이 애플리케이션은 Dapper micro-ORM 및 FluentMap과 RepositoryUnit Of Work 패턴을 사용하여 일대다 관계의 일반적인 고객 및 고객 트랜잭션 테이블에서 원자성 생성, 업데이트 및 삭제 작업을 수행하는 방법을 보여줍니다. 데이터베이스는 DBF 파일로 구성되며 Visual FoxPro OleDb 드라이버를 사용하여 액세스됩니다. 데이터베이스 수준에서는 규칙, 트리거 또는 유사한 구현이 없습니다.
사용 예제를 통해 XUnit을 사용한 일부 통합 테스트가 'Tests' 프로젝트에 제공되며 'SimpleExample' 프로젝트에도 간단한 콘솔 애플리케이션이 있습니다.
이는 이 블로그 항목에서 Ian Rufus가 자세히 설명한 접근 방식을 기반으로 합니다.
중요한! 이 응용 프로그램에는 Microsoft Visual FoxPro 9.0 OleDb 공급자가 필요합니다. 이는 32비트 전용 공급자이며 64비트 버전은 없습니다. 결과적으로 이 응용 프로그램은 x86용으로만 컴파일되어야 합니다.
Dapper는 엔터티 유형에 매핑된 데이터베이스 결과를 반환하는 편리한 메서드로 IDbConnection을 확장하는 인기 있는 micro-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 ) ;
}
}
작업 단위 패턴을 사용하면 데이터베이스 생성, 업데이트 및 삭제 작업을 단일 트랜잭션으로 수행하거나 롤백할 수 있으므로 모든 업데이트가 발생하거나 전혀 발생하지 않는 데이터베이스 '원자성'이 가능해집니다.
저장소 패턴은 사용자 인터페이스에서 데이터베이스 작업을 분리하고 개체 컬렉션에서 항목을 추가, 업데이트 또는 삭제하여 데이터베이스 작업을 수행할 수 있도록 합니다.
애플리케이션에는 CustomerRepositoty
및 CustomerTransactionRepository
두 개의 저장소 클래스가 있습니다. 각각은 생성자를 통해 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 ( ) ;
}
클래스의 저장소 속성은 getter에 주입된 데이터베이스 트랜잭션을 가져오며, 아래 코드는 기존 저장소를 반환하거나 필요에 따라 새 저장소를 생성합니다.
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와 같은 파일 기반 데이터베이스에서 매우 중요합니다. 디스크 파일에 열려 있는 파일 핸들은 다른 응용 프로그램에 문제를 일으킬 수 있습니다./
이것은 간단한 예입니다. 여기서 작업 단위 클래스는 항상 각 저장소 클래스 유형의 인스턴스를 포함해야 하기 때문에 일종의 '신 개체'입니다. 따라서 이는 추가 추상화의 후보입니다.