2017/05/28

透過 Dapper 建立導覽屬性

使用過 Entity Framework 的開發者應該很難回到弱型別的 DataReader 或 DataTable 取值方式吧。因為目前有些專案使用傳統 WebForm 開發,而資料的讀取方式也採用傳統的弱型別方式存取,因此免不了手殘打錯字造成頁面噴掉的情形發生。

還好有了 Dapper,讓在舊的專案開發架構中,可以漸進導入強型別方式與資料庫進行溝通,再也不用擔心資料欄位 Key 錯或是型別給錯這種問題發生 (呃…至少在偵錯過程前,Visual Studio 會派出紅色毛毛蟲提示啦)。不過畢竟 Dapper 只是個替代方案,仍無法完整取代完整 ORM 框架的 Entity Framework,因此目前還沒辦法像 EF 一樣,透過
include()
的方式將關聯的類別資料一同載入以建立完整的導覽屬性。若要使用導覽屬性,目前得要透過「手動後製」方式完成囉。

首先,先定義我們的資料情境:


我們的目的是要將目前既有的所有課程以清單方式列出,而每筆課程資訊 (Course) 中,可一併取出該課程授課老師 (Teacher) 資訊及修課學生 (Student) 清單,而每個修課學生資訊中,還要取出該學生所住的地區資訊。完整的類別定義如下:

public class CourseStudent
{
    public int CID { get; set; }
    public int SID { get; set; }
    public virtual Course CourseClass { get; set; }
    public virtual IList<student> StudentClass { get; set; }
}

public class Course
{
    public int CID { get; set; }
    public int TID { get; set; }
    public string CourseName { get; set; }
    public virtual Teacher TeacherClass { get; set; }
    public virtual IList<student> StudentClass { get; set; }
}

public class Teacher
{
    public int TID { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }
}

public class Student
{
    public int SID { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }
    public int LID { get; set; }
    public virtual Location LocationClass { get; set; }
}

public class Location
{
    public int LID { get; set; }
    public string Name { get; set; }
}

接著要如何透過 Dapper 將完整的資訊放到 Course 類別呢? 以下展示兩種方式。

方法一

將所有資訊透過
QueryMultiple
將所需的所有資訊全部取出,再透過後製方式,建立導覽屬性:

//使用 QueryMultiple 將所需的資料全部取出
var results = conn.QueryMultiple(@"SELECT * FROM Course;
                                   SELECT * FROM Course c LEFT JOIN Teacher t on c.TID = T.TID;
                                   SELECT * FROM CourseStudent cs LEFT JOIN Student s ON cs.SID=s.SID;
                                   SELECT * FROM Location;
                                   SELECT * FROM Student;");
//依照順序讀取資料
var courses = results.Read<course>();
var teachers = results.Read<teacher>();
var course_students = results.Read<coursestudent>();
var locations = results.Read<location>();
var students = results.Read<student>();

//進行後製 => 手動建立導覽屬性
foreach (var course in courses)
{
    //取得這筆課程的 Teacher 資訊
    course.TeacherClass = teachers.Where(x => x.TID == course.TID).FirstOrDefault();
    //取得這筆課程的修課學生清單
    course.StudentClass = students.Where(s => (course_students.Where(x => x.CID == course.CID)
                                                               .Select(x => x.SID)).Contains(s.SID)).ToList();
    //將 Location 資訊一筆筆寫至每個 Student 資訊內
    foreach (var s in course.StudentClass)
    {
        s.LocationClass = locations.Where(l => l.LID == s.LID).FirstOrDefault();
    }
}

方法二

透過一個 SQL 將所有資訊全部取出,透過指定
splitOn
方式拆解取得的內容以自動建立導覽屬性:

var lookup = new Dictionary<int, Course>();
conn.Query<Course, Student, Location, Course>(@"
                SELECT c.*, s.*, l.*
                FROM Course c
                LEFT JOIN CourseStudent cs ON cs.CID = c.CID
                LEFT JOIN Student s ON cs.SID = s.SID
                LEFT JOIN Location l ON s.LID = l.LID
                ", (c, s, l) =>
{
    Course course;
    if (!lookup.TryGetValue(c.CID, out course))
    {
        lookup.Add(c.CID, course = c);
    }

    if (s != null && l != null)
    {
        s.LocationClass = l;
    }
    if (s!=null && course.StudentClass == null)
    {
        course.StudentClass = new List<student>();
    }
    if (s != null)
    {
        course.StudentClass.Add(s);
    }
    
    return course;
}, splitOn: "SID, LID").AsQueryable();
var resultList = lookup.Values;

這裡值得一提的是
splitOn
的用法。
splitOn
是告訴 Dapper 要將哪個欄位當作資料切割的起始點。預設的切割點為 ID 或 id。假設我們的切割點非預設,就必須要告訴 Dapper 切割點是哪個欄位 (多欄位以逗號分隔)。若取得的欄位如下:

OrderId | OrderDate | OrderOwerId | MemberId | MemberName | ProductId | ProductName

假設我們將 MemberId 及 ProductId 作為分割點,這樣 Dapper 就會依照分割點的欄位,將資料切割為三個物件 (Object)

目前只知道有這兩種方法,期待還有其他更方便的方法!


參考資料:
  1. Correct use of Multimapping in Dapper
  2. How do I map lists of nested objects with Dapper 
  3. Mapping Dapper Query to a Collection of Objects (which itself has a couple of Collections)
  4. Dapper簡明教程

沒有留言 :

張貼留言

注意:只有此網誌的成員可以留言。