[料理佳餚] Dapper 自定義欄位對應的三種方式

看到一段某公司對外服務的系統的程式碼,這段程式碼寫好不到一年,而這段程式碼在做一件事情,把從資料庫撈到的資料轉成物件集合,做法就是用 ADO.NET 產生 SqlDataReader,再將 SqlDataReader 丟到一個靜態方法,在靜態方法裡面逐筆讀取資料,接著透過 Reflection 動態地產生物件集合,但是物件的 Property Name 就遷就 ColumnName,一整個怪啊!

如果我們要練習 Reflection 的用法,這是個非常好的練習,但是要用在 Production 上我們還有 Dapper 可以選擇,搭配我接下來要介紹的三種自定義欄位對應方式,我相信怎樣都比自己寫 Reflection 來得好。

關於 Dapper 的用法,黑大的文章是必讀的,我就不贅述了。

首先我有這樣一個資料表,我拿這個資料表來當範例。

然後,我要把上頭的資料表對應成下面這個類別。

Dapper.FluentColumnMapping

Dapper.FluentColumnMapping 是我要介紹的第一種,這個用起來就非常簡單,提供的功能也很單純,就是做欄位的對應而已,話不多說,來看一下範例程式碼。

[TestMethod]
public void Test_FluentColumnMapping()
{
    var columnMappings = new ColumnMappingCollection();

    columnMappings.RegisterType<Product>()
                  .MapProperty(x => x.Id).ToColumn("MyId")
                  .MapProperty(x => x.Name).ToColumn("MyName")
                  .MapProperty(x => x.Price).ToColumn("Num_Price");

    columnMappings.RegisterWithDapper();

    IEnumerable<Product> products;
    using (SqlConnection sql = new SqlConnection(connectionString))
    {
        products = sql.Query<Product>(@"SELECT * FROM TestTable");
    }

    Assert.AreEqual("無線網路分享器", products.Single().Name);
}

使用上,資料型態一定要注意一下,至少邏輯上要可以轉型轉得過去,舉個例子,資料庫內的資料型態是 nvarchar(50),資料內容是 2016/01/01 00:00:00,那麼 Property 的資料型別可以是 DateTime,工具會自動幫你轉型,但不要來個資料內容是 我死硬要轉,硬要轉成 DateTime,那就是來亂的。

Dapper.FluentMap

第二個要介紹的是 Dapper.FluentMap,這個工具要多做一件事情,要先產生一個繼承自 EntityMap<T> 類別,用來定義欄位對應的規則。

public class ProductMap : EntityMap<Product>
{
    public ProductMap()
    {
        Map(x => x.Id).ToColumn("myid", false);
        Map(x => x.Name).ToColumn("MyName");
        Map(x => x.Price).ToColumn("Num_Price");
    }
}

ToColumn() 方法中把 caseSensitive 參數設為 false 就可以不管欄位名稱的大小寫,接著初始化之後就可以了,我們看下面使用範例。

[TestMethod]
public void Test_FluentMap()
{
    FluentMapper.Initialize(cfg =>
    {
        cfg.AddMap(new ProductMap());
    });

    IEnumerable<Product> products;
    using (SqlConnection sql = new SqlConnection(connectionString))
    {
        products = sql.Query<Product>(@"SELECT * FROM TestTable");
    }

    Assert.AreEqual("無線網路分享器", products.Single().Name);
}

這個工具也會像前面一個工具一樣自動幫忙轉型,雖然操作上較為繁瑣,但是它可以提供像是 Convetion Mapping、Transformation 的功能,搭配 Dommel 套件還可以做簡單的 CRUD,詳情就參考作者在 GitHub 上的說明。

實作 SqlMapper.ITypeMap

如果你覺得使用別人做好的工具,是看輕您的程式撰寫功力,Dapper 裡面有一個介面 SqlMapper.ITypeMap,您可以實作它,國外也有神人幫您弄好了 kalebpederson/ColumnAttributeTypeMapper.cs,使用這種方式需要搭配 ColumnAttribute,怎麼搭配請看下面。

public class Product
{
    [Column("MyId")]
    public int Id { get; set; }

    [Column("MyName")]
    public DateTime Name { get; set; }

    [Column("Num_Price")]
    public int Price { get; set; }

    public string Memo { get; set; }
}

接著呼叫 SqlMapper.SetTypeMap 把要對應的類別跟欄位映射丟進去就可以了。

[TestMethod]
public void TestMethod1()
{
    Dapper.SqlMapper.SetTypeMap(
        typeof(Product),
        new ColumnAttributeTypeMapper<Product>());

    IEnumerable<Product> products;
    using (SqlConnection sql = new SqlConnection(connectionString))
    {
        products = sql.Query<Product>(@"SELECT * FROM TestTable");
    }

    Assert.AreEqual("無線網路分享器", products.Single().Name);
}

這種做法一樣會自動幫忙轉型,以上就 Dapper 可以支援的三種自定義欄位的方式做個介紹,善用工具可以事半功倍,這個「功」倍不只是撰寫 Production Code 當下的功,也有後續維護的功。

 < Source Code >

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學