Dapper-Command Join Table Demo

  • 3413
  • 0
  • ORM
  • 2018-05-17

因為對 Join Table的結果不太熟,想說做個紀錄

順便把Dapper對 Join 和 一般ADO直接取出結果來做個比較

以下內容簡單測試,命名或是Table只是做Demo使用,不要太計較正規性

對Dapper的操作及 SQL的指令,請自行上網研究

Join的觀念可以參考這篇文章,寫得算清楚

https://dotblogs.com.tw/caubekimo/2010/07/28/16874

 

首先資料庫裡兩張表格的內容如下圖

Dapper 所需要使用的 DataClass

// Join的表格
public class JoinTable
{
    public int MemberId { getset; }
    public string Name { getset; }
    public int BookId { getset; }
    public string BookName { getset; }
    public int Price { getset; }
}
 
// 原始的Table
public class MemberTable
{
    public int MemberId { getset; }
    public string Name { getset; }
    public int BookId { getset; }
}
 
// 原始的Table
public class BookTable
{
    public int BookId { getset; }
    public string Name { getset; }
    public int Price { getset; }
}

ADO版本的 Left Join
using (var connection = new SqlConnection(connectionStr))
{
    string commandStr = "SELECT * FROM MemberTable LEFT JOIN BookTable ON MemberTable.BookId = BookTable.BookId";
 
    using (SqlCommand command = new SqlCommand(commandStr, connection))
    {
        SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
        // 透過 SqlDataAdapter 執行 Command內容將資料填入 dataTable
        // P.S.忘記在哪本書有看過 SqlDataAdapter 會自動將 SqlConnection-Open 
        DataTable dataTable = new DataTable();
        dataAdapter.Fill(dataTable);
 
        // 將DB的資料Select出來後跟UI做Binding
        dataGridView1.DataSource = dataTable;
    }
}

結果如下圖

Dapper 版本的 Left Join

使用方法參閱 http://dapper-tutorial.net/result-multi-mapping

using (var connection = new SqlConnection(connectionStr))
{
    connection.Open();
 
    string commandStr = "SELECT * FROM MemberTable LEFT JOIN BookTable ON MemberTable.BookId = BookTable.BookId";
 
    //var dapperData = connection.Query(commandStr);
    //取出結果是dynamic,不知道為什麼 設定DataSource無效果
 
    var dapperData = connection.Query<MemberTableBookTableJoinTable>(commandStr,
    (member, book) =>
    {
        return new JoinTable
        {
            MemberId = member.MemberId,
            Name = member.Name,
            BookId = member.BookId,
            BookName = (book == null) ? null : book.Name,
            Price = book == null ? 0 : book.Price,
        };
    },
    splitOn: "BookId")
    .ToList();
 
    // 將DB的資料Select出來後跟UI做Binding
    dataGridView1.DataSource = dapperData;
}

結果如下圖

理論上Dapper 使用上還有其它的變化
如這篇教學 http://mirchetoshevski.com/blog/2017/08/08/dapper-tutorial-2/

可惜,我看不懂他的DataClass結構方式,所以他第二種方式,我測試不出來

另外查詢指令上直接給 as 變數名稱,也可以直接把結果配回,只是就變成Table每個欄位都要填寫

另外官方在Github 的範例,做法如下
public class MemberTable
{
    public int MemberId { getset; }
    public string Name { getset; }
    public int BookId { getset; }
    public BookTable BooKData { getset; }
}

將 BookTable 放進MemberTable 的DataClass裡面

之後再查詢結果把Book配進去即可

var dapperData = connection.Query<MemberTableBookTableMemberTable>(commandStr,
(member, book) =>
{
    member.BooKData = book;
    return member;
},
splitOn: "BookId")
.ToList();

只是這種作法如果直接將  dataGridView1.DataSource = dapperData;

做設定,會出現如下圖的問題,跑出來的表格格式並不項是我想要的

WPF的話,可以從DataTemplate去設定Binding的資料。

Winform的話,應該是可以從 dataGridView1.DataBindings 內部去設定

不過我還沒研究出來,而且這種做法會不會比較方便,我也不清楚

所以我基本上會再做一個顯示用的DataClass

Google 搜尋 dataGridView.DataSource --> Binding class的文章

看到有一種做法,提供參考如下

public class MemberTable
{
    public int MemberId { getset; }
    public string Name { getset; }
    public int BookId { getset; }
    private BookTable BookData { getset; }
 
    public void SetBook (BookTable booKData)
    {
        BookData = booKData;
    }

    [DisplayName("BookName")]
    public string BName
    {
        get { return BookData?.Name; }
        set { BookData.Name = value; }
    }
 
    [DisplayName("BookPrice")]
    public int? BPrice
    {
        get { return BookData?.Price; }
        set { BookData.Price = value; }
    }
}
 
public class BookTable
{
    public int BookId { getset; }
    public string Name { getset; }
    public int? Price { getset; }
}

因為book查出來會有可能是null,所以在顯示要用到 BPrice這屬性的時候

要把int改成 Nullable<int>,否則會出問題,理論上我參數設定應該在Set也會出問題才對

只是不知道為什麼在搭配dataGridView.DataSource在存取資料 Set沒跳錯誤,我猜應該是在Binding時不會使用到Set

var dapperData = connection.Query<MemberTableBookTableMemberTable>(commandStr,
(member, book) =>
{
    member.SetBook(book);
    return member;
},
splitOn: "BookId")
.ToList();

結果如下圖

整體作法就是把實際資料改成private 這樣在Binding的時候就不會拿這屬性來用

之後再創建要顯示的屬性去存取實際資料。順便使用 [DisplayName("Name")]

這Attribute,來做UI顯示的名稱。剛好在網路上有看到順便紀錄一下這用法

重點就是在資料Null的時候,存取不發生錯誤的處理


Join三張表的Demo,簡單的給予參考

新增一張表為出版社

然後Book的Table新增一個欄位

此時如果直接用ADO
using (var connection = new SqlConnection(connectionStr))
{
    string commandStr = "SELECT * FROM MemberTable LEFT JOIN BookTable ON MemberTable.BookId = BookTable.BookId ";
    commandStr += "LEFT JOIN PublisherTable ON BookTable.PublisherId = PublisherTable.Id ";
    using (SqlCommand command = new SqlCommand(commandStr, connection))
    {
        SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
        DataTable dataTable = new DataTable();
        dataAdapter.Fill(dataTable);
        dataGridView1.DataSource = dataTable;
    }
}

查詢後結果會如下圖

如果要用 Dapper的話,資料結構就要修改,簡單如下

(有一些Nullable的要自己去補,我這只是簡單做個參考而已,很有可能會跳錯誤)

public class MemberTable
{
    public int MemberId { getset; }
    public string Name { getset; }
    public int BookId { getset; }
    private BookTable BookData { getset; }
 
    public void SetBook(BookTable booKData)
    {
        BookData = booKData;
    }
    [DisplayName("BookName")]
    public string BName
    {
        get { return BookData?.Name; }
        set { BookData.Name = value; }
    }
    [DisplayName("BookPrice")]
    public int? BPrice
    {
        get { return BookData?.Price; }
        set { BookData.Price = value; }
    }
    public string PublisherName
    {
        get { return BookData?.PublisherName; }
        set { BookData.PublisherName = value; }
    }
    public string PublisherAddress
    {
        get { return BookData?.PublisherAddress; }
        set { BookData.PublisherAddress = value; }
    }
}

public class BookTable
{
    public int BookId { getset; }
    public string Name { getset; }
    public int? Price { getset; }
    public int? PublisherId { getset; }
 
    private PublisherTable PublisherData { getset; }
 
    public void SetPublisher(PublisherTable publisherData)
    {
        PublisherData = publisherData;
    }
    public string PublisherName
    {
        get { return PublisherData?.Name; }
        set { PublisherData.Name = value; }
    }
    public string PublisherAddress
    {
        get { return PublisherData?.Address; }
        set { PublisherData.Address = value; }
    }
}
 
public class PublisherTable
{
    public int Id { getset; }
    public string Name { getset; }
    public string Address { getset; }
}

查詢的方法修改如下

var dapperData = connection.Query<MemberTableBookTablePublisherTableMemberTable>(commandStr,
(member, book, publisher) =>
{
    book?.SetPublisher(publisher);
    member.SetBook(book);
    return member;
},
splitOn: "BookId, PublisherId")
.ToList();

注意 splitOn: "BookId, PublisherId" 的設定,

Dapper是裡用這邊給的參數去將查詢的結果分割然後Mapping到DataClass裡面

最後查詢結果如下圖


除了 Inner Join之外,其餘都要注意DataClass是Null的時候

因資料結構上的關係做Right Join的時候 Dapper理論上不太可能直接達到

變成Right的DataClass需要做初始值,當NULL的時候直接使用初始值

這就是都看使用情況去做調整,如果只是要簡單做表格,或許直接使用ADO反而簡單使用

不過大部分的時候,DataClass基礎的處理方式訂好,把Null的規則定義好,未來擴充也還算方便。

此篇大致上到這樣,如果未來有想到什麼再補充了