Pages

2015-05-27

提供自訂ORM物件的直覺式語法

從ADO.NET/DataTable跨入Data Entity時代之初,我選擇了微軟EF+LINQ架構,然而EF v6.1.3在對SQL字串執行的函式雖夠用但語法不夠好,因此我使用自己的Helper Class來執行SQL指令,並映射成POCO物件,如此進可攻退可守。

先來看看EF6下提供的SQL字串執行語法:

// 使用params順序參數
string sql = "INSERT INTO [t] (id,name) VALUES ({0}, {1})";
// 它會轉成@p0, @p1等Parameter避免SQL Injection
dbContext.Database.SqlQuery<POCO>(sql, 123, "abc");


// 若要使用具名DB參數,就得使用:
sql = "INSERT INTO [t] (id,name) VALUES (@id, @name)";
DbParameter[] param = new DbParameter[]
{
new DbParameter("@id", 123),
new DbParameter("@name", "abc")
};
dbContext.Database.SqlQuery<POCO>(sql, param);

其中傳入的POCO類別,必須和SQL裏SELECT出來的欄位數要完全一致才不會出錯,這要求很不合理,畢竟查詢時能共用大POCO宣告類別才會方便。雖然這些用完即丟的POCO類別有產生器可產生,但垃圾一多就不好管理。為此,我仿照Dapper元件的直覺式語法優點,使用匿名類別來產生具名DbParameter參數,並支援讀入欄位數不一致的POCO物件裏,甚至可以dynamic型別來傳回查詢資料集合。

SqlConnection con = new SqlConnection("ConnectionString");
Database db = new Database(con, true); // Try to open connection.

string sql = "SELECT * FROM [t] WHERE id = @id AND name = @name";
var param = new
{
id = 123,
name = "abc"
};

// IEnumerable<POCO>
var result1 = db.QueryEntity(sql, param);
//db.QueryEntity(cmd); // 可執行自己的DbCommand

result1 = from r in result1
where r.id > 2 && r.id < 5
select r;

foreach (var item in result1)
{
int id = item.id;
string name = item.name;
}
實作了dynamic動態類別傳入,直接得到dynamic的資料集合,省去了用完即拋的POCO類別的宣告手續。
// IEnumerable<dynamic>
var result2 = db.QueryDynamic(sql, param, buffered: false);
//db.QueryDynamic(cmd);

foreach (dynamic item in result2)
{
int id = item.id; // Dynamic free coding.
string name = item.name;
}

以上我所包裝的DB Helper類別,全是Dapper也能做到的功能,但Dapper不能使用DbCommand,而且對於底層DbParameter參數無法再修改其值,當大量執行SQL修改指令時,Dapper底層必得重新綁定DB參數,徒耗效能。所以我利用Refection實作了類似Dapper般使用Anonymous Class來宣右DbParameter參數,並增加對既有DbCommand的支援及優化。


為什麼我寧可自己實作整套的ORM Helper Class,卻不直接修改調整Dapper的開源程式碼呢?其實是個人能力有限,實在看不懂它在寫什麼東西。雖然Dapper號稱高效能,其實我們查詢取回的資料筆數都不大,使用自訂的ORM映射方式並不會導致效能太差,最重要是自己的物件自己做主,簡單又好用。

No comments: