แอปพลิเคชันนี้สาธิตวิธีดำเนินการสร้าง อัปเดต และลบการดำเนินการบนตารางธุรกรรมของลูกค้าและลูกค้าทั่วไปในความสัมพันธ์แบบหนึ่งต่อกลุ่ม โดยใช้ Dapper micro-ORM และ FluentMap และรูปแบบ RepositoryUnit Of Work ฐานข้อมูลประกอบด้วยไฟล์ DBF และเข้าถึงได้โดยใช้ไดรเวอร์ Visual FoxPro OleDb ไม่มีกฎ ทริกเกอร์ หรือสิ่งที่คล้ายกันถูกนำไปใช้ในระดับฐานข้อมูล
ตามตัวอย่างการใช้งาน การทดสอบการรวมบางส่วนโดยใช้ XUnit มีให้ในโปรเจ็กต์ 'การทดสอบ' และยังมีแอปพลิเคชันคอนโซลอย่างง่ายในโปรเจ็กต์ 'SimpleExample'
สิ่งนี้เป็นไปตามแนวทางที่ให้รายละเอียดโดย Ian Rufus ในรายการบล็อกนี้
สำคัญ! แอปพลิเคชันนี้ต้องการผู้ให้บริการ Microsoft Visual FoxPro 9.0 OleDb นี่เป็นผู้ให้บริการแบบ 32 บิตเท่านั้น ไม่มีเวอร์ชัน 64 บิต ด้วยเหตุนี้จึงต้องคอมไพล์แอปพลิเคชันนี้สำหรับ x86 เท่านั้น
Dapper เป็นตัวทำแผนที่ micro-ORM object ยอดนิยมที่ขยาย 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 ( ) ;
}
คุณสมบัติพื้นที่เก็บข้อมูลบนคลาสได้รับธุรกรรมฐานข้อมูลที่ถูกฉีดเข้าไปใน 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 ตัวจัดการไฟล์ใดๆ ที่เปิดทิ้งไว้ในไฟล์ดิสก์อาจทำให้เกิดปัญหากับแอปพลิเคชันอื่นๆ/
นี่เป็นตัวอย่างง่ายๆ - หน่วยของคลาสงานที่นี่เป็นประเภท 'god object' เนื่องจากจะต้องมีอินสแตนซ์ของคลาสพื้นที่เก็บข้อมูลแต่ละประเภทเสมอ ดังนั้นจึงเป็นผู้สมัครชิงความเป็นนามธรรมต่อไป