iT邦幫忙

12

[C#][ASP.NET] Web API 開發心得 (6) - 輕量級的 ORM 工具 Dapper + Slapper.AutoMapper

寫了這麼多篇終於要進入資料庫的部分,今天要和大家分享的是輕量級 ORM 框架 Dapper,自己從最開始的 ADO.NET 配合 DataTable 到後來流行的 Entity Framework 都使用過,而第一次接觸 Entity Framework 就可以很明顯感受到它的優點,不必寫 SQL 用 LINQ 就可以產生語法,並且會將結果映射到 C# 物件上,這對當時只寫過 ADO.NET 的我來說是非常不可思議的。

不過程式框架不可能樣樣好,慢慢就會發現一些問題,例如我們碰上複雜查詢時,用 LINQ 寫出來的程式可讀性會遠比 SQL 來的差。

再來就像第一篇提到的,Entity Framework 對資料庫的侵入性太強,不適合導入舊系統,例如導航屬性必須建立資料表關聯,而舊系統資料庫通常不會做太大的變動,怕引響到現有功能,因此導入 Entity Framework 會有其限制在。

不過對我來說最大的困難點是在團隊上,相信很多人都遇過,有些工程師很排斥寫 SQL,有些很排斥寫 LINQ,而我自己的團隊中剛好都是喜歡寫 SQL 的工程師 XD
所以...我還是只能乖乖的寫 SQL...。

不過對 Web 工程師而言 SQL 是很重要的工具,還是要逼著自己學習,接著我就開始搜尋一些輔助型的 ORM 工具,看到很多前輩推薦 Dapper,它可以將查詢的結果 Mapping 到物件上,比起 DataTable 多了強型別的優點,並且後續還可以接著 LINQ 做其他的處理,使用後覺得很不錯。

而我自己是不排斥 Entity Framework 的,未來有機會也會寫一些 EF 的心得分享給大家,接下來就進入今天的主題,Dapper + Slapper.AutoMapper

安裝套件

使用 Nuget 安裝 Dapper。
https://ithelp.ithome.com.tw/upload/images/20190303/20106865paRC5mBNrl.jpg

建立資料表

開啟 SQL Server Management 新增資料庫和四張資料表。

Student 學生

https://ithelp.ithome.com.tw/upload/images/20190303/20106865fwGUXMMHlz.jpg

Junior 畢業國中

https://ithelp.ithome.com.tw/upload/images/20190303/20106865HZ8RdpQKxZ.jpg

Exam 考試

https://ithelp.ithome.com.tw/upload/images/20190303/20106865JZGWuA6zNR.jpg

ExamType 考試類型

https://ithelp.ithome.com.tw/upload/images/20190303/20106865TSvwaDdayL.jpg

建立 Model

新增 Model 資料夾,並新增 StudentJuniorExamExamType 四個 DTO 類別,接著在 Student 類別建立兩個導航屬性 Junior 和 Exams,分別代表學生和畢業國中是屬於多對一關係,學生和考試是一對多關係,然後在 Exam 類別建立 ExamType 導航屬性,代表考試和考試類型是多對一關係,最後加上欄位說明,我通常會把欄位說明寫在 Model 上,因為這樣寫程式時 Visual Sudio 會自動帶出,非常方便。

程式碼:

namespace Model
{
    /// <summary>
    /// 學生
    /// </summary>
    public class Student
    {
        public int Id { get; set; }

        /// <summary>
        /// 學號
        /// </summary>
        public string Sid { get; set; }

        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 畢業國中 Id
        /// </summary>
        public int JuniorId { get; set; }

        public Junior Junior { get; set; }

        public List<Exam> Exams { get; set; }
    }
}
namespace Model
{
    /// <summary>
    /// 國中
    /// </summary>
    public class Junior
    {
        public int Id { get; set; }

        /// <summary>
        /// 校代碼
        /// </summary>
        public string Code { get; set; }

        /// <summary>
        /// 學校名稱
        /// </summary>
        public string Name { get; set; }
    }
}
namespace Model
{
    /// <summary>
    /// 考試
    /// </summary>
    public class Exam
    {
        public int Id { get; set; }

        /// <summary>
        /// 考試名稱
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 分數
        /// </summary>
        public decimal Score { get; set; }

        /// <summary>
        /// 學生 Id
        /// </summary>
        public int StudentId { get; set; }

        /// <summary>
        /// 考試類型 Id
        /// </summary>
        public int ExamTypeId { get; set; }

        public ExamType ExamType { get; set; }
    }
}
namespace Model
{
    /// <summary>
    /// 考試類型
    /// </summary>
    public class ExamType
    {
        public int Id { get; set; }

        /// <summary>
        /// 考試類型名稱
        /// </summary>
        public string Name { get; set; }
    }
}

建立連線字串

到 Web.config 內新增資料庫的連線字串 ConnectionString

<configuration>
  <connectionStrings>
    <add name="SQLConnectionString" connectionString="
         Data Source=.;
         Initial Catalog=WebApiTest;
         Connection Timeout=300000;
         Persist Security Info=True;
         User ID=帳號;
         Password=密碼" providerName="System.Data.SqlClient" />
  </connectionStrings>
</configuration>

新增 Connection 資料夾,並新增 ConnectionFactory 類別,我們會使用這個類別來統一管理 SqlConnection 的產生,而不直接在 Controller 用 new 去產生物件,Controller 只相依於 IDbConnection 介面,降低程式耦合度,CreateConnection 方法也保留了連線多個資料庫的彈性,可透過參數 name 去產生不同資料庫的 SqlConnection。

程式碼:

namespace Connection
{
    public class ConnectionFactory
    {
        public IDbConnection CreateConnection(string name = "default")
        {
            switch (name)
            {
                case "default":
                    {
                        var ConnectionString = System.Web.Configuration.WebConfigurationManager.ConnectionStrings["SQLConnectionString"].ConnectionString;

                        return new SqlConnection(ConnectionString);
                    }
                default:
                    {
                        throw new Exception("name 不存在。");
                    }
            }
        }
    }
}

資料夾結構

https://ithelp.ithome.com.tw/upload/images/20190303/20106865bzzw7511ES.jpg

Dapper 用法

執行沒有回傳的 SQL 命令 Execute

Execute 用於沒有回傳結果的 SQL 命令,例如 INSERTUPDATEDELETE,Dapper 會將傳入的物件 Mapping 到具名參數上。

api/student/insert

var cn = _connectionFactory.CreateConnection();

var sql = "INSERT INTO Student ([Sid], Name, JuniorId) VALUES (@Sid, @Name, @JuniorId)";

var newStudent = new Student
{
    Sid = "100001",
    Name = "小明",
    JuniorId = 1,
};

cn.Execute(sql, newStudent);

批次執行 Execute

在 Execute 方法,傳入實作 IEnumerable 介面的物件,Dapper 會批次執行這段 SQL 語法。

api/student/batch/insert

var cn = _connectionFactory.CreateConnection();

var sql = "INSERT INTO Student ([Sid], Name, JuniorId) VALUE(@Sid, @Name, @JuniorId)";

var newStudents = new List<Student>
{
    new Student
    {
        Sid = "100001",
        Name = "小明",
        JuniorId = 1,
    },
    new Student
    {
        Sid = "100002",
        Name = "小華",
        JuniorId = 2,
    }
};

cn.Execute(sql, newStudents);

參數化查詢 Query

可傳入匿名物件,方便參數命名,可指定查詢結果要 Mapping 的物件型別,如果未指定預設傳回 IEnumerable<dynamic>

api/student/query

var cn = _connectionFactory.CreateConnection();

var sql = @"SELECT * FROM Student AS A WHERE A.Name=@Name";

var studentList = cn.Query<Student>(sql, new { Name = "小明" }).ToList();
{
    "success": true,
    "msg": null,
    "data": [
        {
            "Id": 1,
            "Sid": "100001",
            "Name": "小明",
            "JuniorId": 1,
            "Junior": null,
            "Exams": null
        }
    ]
}

參數陣列展開 Query

在 Query 方法,可直接傳入陣列 (IEnumerable) 型態的具名參數,Dapper 會自動將其展開,例如 WHERE Id IN @Ids 自動展開成 WHERE Id IN (@Ids1, @Ids2, @Ids3),這是我非常喜歡的功能,以往要用迴圈去繞陣列,再串出 SQL 語法,現在 Dapper 都幫我們做完了,程式變的很簡潔。

api/student/query/in

var cn = _connectionFactory.CreateConnection();

var sql = @"SELECT * FROM Student AS A WHERE A.Name IN @Names";

var studentList = cn.Query<Student>(sql, new { Names = new string[] { "小明", "小華" } }).ToList();
{
    "success": true,
    "msg": null,
    "data": [
        {
            "Id": 1,
            "Sid": "100001",
            "Name": "小明",
            "JuniorId": 1,
            "Junior": null,
            "Exams": null
        },
        {
            "Id": 2,
            "Sid": "100002",
            "Name": "小華",
            "JuniorId": 3,
            "Junior": null,
            "Exams": null
        }
    ]
}

一對一或多對一映射 Mapping

Dapper 能像 Entity Framework 一樣將查詢映射到導航屬性上,預設會使用 Id 去做切割,如果想用其他名稱切割可以使用 splitOn 參數。

api/student/mapping/manyToOne

var cn = _connectionFactory.CreateConnection();

var sql = @"SELECT * 
            FROM Student AS A
            LEFT JOIN Junior AS B ON B.Id=A.JuniorId";

var studentList = cn.Query<Student, Junior, Student>(sql, (s, j) =>
{
    s.Junior = j;
    return s;
})
.ToList();
{
    "success": true,
    "msg": null,
    "data": [
        {
            "Id": 1,
            "Sid": "100001",
            "Name": "小明",
            "JuniorId": 1,
            "Junior": {
                "Id": 1,
                "Code": "000001",
                "Name": "學校1"
            },
            "Exams": null
        },
        {
            "Id": 2,
            "Sid": "100002",
            "Name": "小華",
            "JuniorId": 3,
            "Junior": {
                "Id": 3,
                "Code": "000002",
                "Name": "學校2"
            },
            "Exams": null
        }
    ]
}

一對多映射 Mapping

Dapper 在處理一對多映射比較麻煩,需要用 Dictionary 去過濾主表因 Join 而產生重複的部分。

api/student/mapping/oneToMany

var cn = _connectionFactory.CreateConnection();

var sql = @"SELECT * 
            FROM Student AS A
            LEFT JOIN Exam AS B ON B.StudentId=A.Id";

var studentDictionary = new Dictionary<int, Student>();

var studentList = cn.Query<Student, Exam, Student>(sql, (s, e) =>
{
    var entity = null as Student;

    if (!studentDictionary.TryGetValue(s.Id, out entity))
    {
        entity = s;
        entity.Exams = new List<Exam>();
        studentDictionary.Add(entity.Id, entity);
    }

    s.Exams.Add(e);
    return s;
})
.Distinct().ToList();
{
    "success": true,
    "msg": null,
    "data": [
        {
            "Id": 1,
            "Sid": "100001",
            "Name": "小明",
            "JuniorId": 1,
            "Junior": null,
            "Exams": [
                {
                    "Id": 1,
                    "Name": "國文",
                    "Score": 60,
                    "StudentId": 1,
                    "ExamTypeId": 2,
                    "ExamType": null
                },
                {
                    "Id": 4,
                    "Name": "英文",
                    "Score": 70,
                    "StudentId": 1,
                    "ExamTypeId": 3,
                    "ExamType": null
                }
            ]
        },
        {
            "Id": 2,
            "Sid": "100002",
            "Name": "小華",
            "JuniorId": 3,
            "Junior": null,
            "Exams": [
                {
                    "Id": 6,
                    "Name": "國文",
                    "Score": 50,
                    "StudentId": 2,
                    "ExamTypeId": 2,
                    "ExamType": null
                },
                {
                    "Id": 8,
                    "Name": "英文",
                    "Score": 60,
                    "StudentId": 2,
                    "ExamTypeId": 3,
                    "ExamType": null
                }
            ]
        }
    ]
}

看到這裡,前面幾個還好,最後一個例子,很多人一定會覺得怎麼這麼難用,沒錯我自己也有發現,一對多映射是 Dapper 的弱點,由其在實務上不太可能都是單層關係,常常會碰到 Join 一大堆資料表的情況,Entity Framework 可以很容易的用 Include 完成好幾層的複雜映射,而用 Dapper 就會發現,自己怎麼寫出了一大堆複雜難懂的程式碼,大半的程式都在做映射,這個問題也困擾了我好久,後來也是爬了很多文章後,發現 Slapper.AutoMapper 這個套件,它可以利用 SQL 語法來輔助 Dapper 完成多層複雜的映射,雖然還是有缺點,不過比起原來的作法已經好很多,這個套件好像國內比較少人用,所以想分享給大家。

安裝套件

使用 Nuget 安裝 Slapper.AutoMapper。
https://ithelp.ithome.com.tw/upload/images/20190303/20106865ofgUzYiAPY.jpg

Slapper.AutoMapper 用法

Slapper.AutoMapper 是一個映射庫,可以將動態類型 dynamic,轉換為靜態類型並填充其複雜的關聯子物件,Slapper 主要會先將 dynamic 轉換為 IDictionary<string, object>,接著透過下劃線表示法,將下劃線的部分填充到子物件中,下面來看實際用法。

多層複雜映射

api/student/mapping/slapper

下面我 Join 了四張資料表並將結果映射到 Student 類別,類別內 一對多多對一多層嵌套的關係都有,然後可以看到 SQL 語法內,使用了下劃線來表示物件彼此的層級關係,Slapper 依據此關係就可以自動完成複雜的映射動作,邏輯比上面用 Dapper 簡單多了。

var cn = _connectionFactory.CreateConnection();

var sql = @"SELECT A.*,
                   B.Id AS Junior_Id,
            	   B.Code AS Junior_Code, 
            	   B.Name AS Junior_Name,
            	   C.Id AS Exams_Id,
            	   C.Name AS Exams_Name,
            	   C.Score AS Exams_Score,
            	   C.StudentId AS Exams_StudentId,
            	   C.ExamTypeId AS Exams_ExamTypeId,
            	   D.Id AS Exams_ExamType_Id,
            	   D.Name AS Exams_ExamType_Name
            FROM Student AS A
            LEFT JOIN Junior AS B ON B.Id=A.JuniorId
            LEFT JOIN Exam AS C ON C.StudentId=A.Id
            LEFT JOIN ExamType AS D ON D.Id=C.ExamTypeId";

var dy = cn.Query<dynamic>(sql);

var studentList = Slapper.AutoMapper.MapDynamic<Student>(dy, false).ToList();
{
    "success": true,
    "msg": null,
    "data": [
        {
            "Id": 1,
            "Sid": "100001",
            "Name": "小明",
            "JuniorId": 1,
            "Junior": {
                "Id": 1,
                "Code": "000001",
                "Name": "學校1"
            },
            "Exams": [
                {
                    "Id": 1,
                    "Name": "國文",
                    "Score": 60,
                    "StudentId": 1,
                    "ExamTypeId": 2,
                    "ExamType": {
                        "Id": 2,
                        "Name": "期中考"
                    }
                },
                {
                    "Id": 4,
                    "Name": "英文",
                    "Score": 70,
                    "StudentId": 1,
                    "ExamTypeId": 3,
                    "ExamType": {
                        "Id": 3,
                        "Name": "期末考"
                    }
                }
            ]
        },
        {
            "Id": 2,
            "Sid": "100002",
            "Name": "小華",
            "JuniorId": 3,
            "Junior": {
                "Id": 3,
                "Code": "000002",
                "Name": "學校2"
            },
            "Exams": [
                {
                    "Id": 6,
                    "Name": "國文",
                    "Score": 50,
                    "StudentId": 2,
                    "ExamTypeId": 2,
                    "ExamType": {
                        "Id": 2,
                        "Name": "期中考"
                    }
                },
                {
                    "Id": 8,
                    "Name": "英文",
                    "Score": 60,
                    "StudentId": 2,
                    "ExamTypeId": 3,
                    "ExamType": {
                        "Id": 3,
                        "Name": "期末考"
                    }
                }
            ]
        }
    ]
}

Slapper.AutoMapper 在做映射時會需要指定每個類別的鍵值
預設的鍵值有下面三種:

  1. Id
  2. TypeName + Id
  3. TypeName + Nbr

範例:

  1. Id
  2. StudentId
  3. StudentNbr

如果想要指定其他鍵值可以透過四種方法:

  1. 新增通用設定
Slapper.AutoMapper.Configuration.IdentifierConventions.Add(type => type.Name + "_Id");
  1. 個別指定,可支援多鍵值
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(Student), new List <string> { "StudentId","StudentType" });
  1. 使用 [Slapper.AutoMapper.Id],可支援多鍵值
public class Student
{
    [Slapper.AutoMapper.Id]
    public int Id { get; set; }
}
  1. 如果想要使用自訂的 Attribute
Slapper.AutoMapper.Configuration.IdentifierAttributeType = typeof(CustomAttribute);

結語

今天終於進入到資料庫的部分,不過寫的東西不是主流的 Entity Framework,雖然 Dapper 蠻多人在用的,不過 Slapper 目前還沒有看到國內有相關的文章,所以不知道大家的接受度如何,我自己是覺得蠻好用的,到這裡 Dapper 在查詢方面已經算完成,配合 Slapper 多層複雜的物件映射也都可以簡單完成,不過在新增、修改、刪除方面,每次操作還是需要為每個 Table 寫 SQL 語法,使用上不是很方便,所以下一篇我會介紹如何將 Insert、Update、Delete 抽離成共用的方法,操作起來就像 Entity Framework 一樣方便,今天就介紹到這裡,感謝大家觀看。

最後附上完整的測試程式

namespace Api
{
    [Result]
    [Exception]
    [RoutePrefix("api/student")]
    public class StudentController : BaseController
    {
        private ConnectionFactory _connectionFactory;
        public StudentController()
        {
            _connectionFactory = new ConnectionFactory();
        }

        //Insert
        [HttpGet]
        [Route("insert")]
        public void Insert()
        {
            var cn = _connectionFactory.CreateConnection();

            var sql = "INSERT INTO Student ([Sid], Name, JuniorId) VALUES (@Sid, @Name, @JuniorId)";

            var newStudent = new Student
            {
                Sid = "100001",
                Name = "小明",
                JuniorId = 1,
            };

            cn.Execute(sql, newStudent);
        }

        //BatchInsert
        [HttpGet]
        [Route("batch/insert")]
        public void BatchInsert()
        {
            var cn = _connectionFactory.CreateConnection();

            var sql = "INSERT INTO Student ([Sid], Name, JuniorId) VALUES (@Sid, @Name, @JuniorId)";

            var newStudents = new List<Student>
            {
                new Student
                {
                    Sid = "100001",
                    Name = "小明",
                    JuniorId = 1,
                },
                new Student
                {
                    Sid = "100002",
                    Name = "小華",
                    JuniorId = 2,
                }
            };

            cn.Execute(sql, newStudents);
        }

        //Query
        [HttpGet]
        [Route("query")]
        public List<Student> Query()
        {
            var cn = _connectionFactory.CreateConnection();

            var sql = @"SELECT * FROM Student AS A WHERE A.Name=@Name";

            var studentList = cn.Query<Student>(sql, new { Name = "小明" }).ToList();

            return studentList;
        }

        //QueryIn
        [HttpGet]
        [Route("query/in")]
        public List<Student> QueryIn()
        {
            var cn = _connectionFactory.CreateConnection();

            var sql = @"SELECT * FROM Student AS A WHERE A.Name IN @Names";

            var studentList = cn.Query<Student>(sql, new { Names = new string[] { "小明", "小華" } }).ToList();

            return studentList;
        }

        //MappingManyToOne
        [HttpGet]
        [Route("mapping/manyToOne")]
        public List<Student> MappingManyToOne()
        {
            var cn = _connectionFactory.CreateConnection();

            var sql = @"SELECT * 
                        FROM Student AS A
                        LEFT JOIN Junior AS B ON B.Id=A.JuniorId";

            var studentList = cn.Query<Student, Junior, Student>(sql, (s, j) =>
            {
                s.Junior = j;
                return s;
            })
            .ToList();

            return studentList;
        }

        //MappingOneToMany
        [HttpGet]
        [Route("mapping/oneToMany")]
        public List<Student> MappingOneToMany()
        {
            var cn = _connectionFactory.CreateConnection();

            var sql = @"SELECT * 
                        FROM Student AS A
                        LEFT JOIN Exam AS B ON B.StudentId=A.Id";

            var studentDictionary = new Dictionary<int, Student>();

            var studentList = cn.Query<Student, Exam, Student>(sql, (s, e) =>
            {
                var entity = null as Student;

                if (!studentDictionary.TryGetValue(s.Id, out entity))
                {
                    entity = s;
                    entity.Exams = new List<Exam>();
                    studentDictionary.Add(entity.Id, entity);
                }

                entity.Exams.Add(e);
                return entity;
            })
            .Distinct().ToList();

            return studentList;
        }

        //MappingSlapper
        [HttpGet]
        [Route("mapping/slapper")]
        public List<Student> MappingSlapper()
        {
            var cn = _connectionFactory.CreateConnection();

            var sql = @"SELECT A.*,
                               B.Id AS Junior_Id,
                        	   B.Code AS Junior_Code, 
                        	   B.Name AS Junior_Name,
                        	   C.Id AS Exams_Id,
                        	   C.Name AS Exams_Name,
                        	   C.Score AS Exams_Score,
                        	   C.StudentId AS Exams_StudentId,
                        	   C.ExamTypeId AS Exams_ExamTypeId,
                        	   D.Id AS Exams_ExamType_Id,
                        	   D.Name AS Exams_ExamType_Name
                        FROM Student AS A
                        LEFT JOIN Junior AS B ON B.Id=A.JuniorId
                        LEFT JOIN Exam AS C ON C.StudentId=A.Id
                        LEFT JOIN ExamType AS D ON D.Id=C.ExamTypeId";

            var dy = cn.Query<dynamic>(sql);

            var studentList = Slapper.AutoMapper.MapDynamic<Student>(dy, false).ToList();
            
            return studentList;
        }
    }
}

2019/03/03 補充:

最近發現 Slapper.AutoMapper 如果 JOIN 的表太多會有效能上的問題,尤其在 JOIN 的副表資料很多的情況,例如: 學生 JOIN 考試成績
如果考試成績的資料很多,接著又 JOIN 五、六張表,這種情況下 Slapper 的處理效率就會很差,而我自己優化的方法是將筆數多的表拉出去另外查詢,最後在組合起來,這樣就能避免少部分表造成 JOIN 後資料筆數過多的問題。
而 Entity Framework 好像沒有類似的問題,真好奇 EF 底層是如何處理映射的。


參考資料

Dapper - 使用 LINQPad 快速產生相對映 SQL Command 查詢結果的類別
短小精悍的.NET ORM神器 -- Dapper
Dapper - Result Multi-Mapping
StackExchange/Dapper
SlapperAutoMapper/Slapper.AutoMapper


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
1
暐翰
iT邦大師 1 級 ‧ 2018-04-08 01:27:30

畢竟 SQL 才是真正操作資料庫的核心 推這點
/images/emoticon/emoticon12.gif

看過一些用了EF+LINQ,SQL都忘記的工程師

我蠻討厭寫 SQL 的,哈哈哈,
平常寫程式也盡量只用簡單的語法,
不過不可否認,對於 Web 工程師來說 SQL 的確非常重要,
在一些特殊情況下,不能寫程式,必需直接用 SQL 操作資料庫,
所以還是要慢慢練習,平常也有在關注大大的回答,學到好多厲害的解法。
/images/emoticon/emoticon32.gif

1
Homura
iT邦高手 1 級 ‧ 2018-12-18 14:21:50

話說參數直接new 一個物件塞入

var cn = _connectionFactory.CreateConnection();

var sql = @"SELECT * FROM Student AS A WHERE A.Name=@Name";

var studentList = cn.Query<Student>(sql, new { Name = "小明" }).ToList();

和new 一個DynamicParameters差在哪邊?

var cn = _connectionFactory.CreateConnection();
var sql = @"SELECT * FROM Student AS A WHERE A.Name=@Name";

DynamicParameters p = new DynamicParameters();
p.Add("@Name", "小明");

var studentList = cn.Query<Student>(sql, p).ToList();

最近接到新專案是下面的寫法

後者比較像傳統 ADO.NET 的 Parameters
可以設定 DbType、ParameterDirection 等參數

parameters.Add("@AAA", AAA, 
    DbType.String, ParameterDirection.Output);
  • new object: 簡單易用
  • DynamicParameters: 較嚴謹

專案上我也是比較常用 DynamicParameters。

Homura iT邦高手 1 級 ‧ 2018-12-18 15:33:54 檢舉

fysh711426
原來如此
感謝解答
剛問同事是說都行
但建議用專案使用DynamicParameters的方式XD

應該是沒什麼差別
我自己是在需要動態組Where條件時,才會用DynamicParameters

DynamicParameters p = new DynamicParameters();
if (a > 1)
  p.Add("@aa", "小明");
0
garychan1212
iT邦新手 5 級 ‧ 2018-12-28 19:14:49

可否提供程式碼 幫助我學習,謝謝

我已經成功了 不需要程式碼了

以前寫文章沒有整理程式的習慣
以後會考慮放 GitHub 讓大家下載

0
hugo8319
iT邦新手 4 級 ‧ 2019-01-08 11:20:39

先感謝樓主的這篇文章,最近改版剛好在找ORM,看到這篇先跪了/images/emoticon/emoticon02.gif

這邊有一些關於 Slapper.AutoMapper 的問題
有關 Slapper.AutoMapper 的鍵值,有點不太清楚鍵值這個東西的用途
我個人的認知是今天跑完SQL指令,拿到資料後會各自放入對應到Model(這個行為稱作映射嗎?)
目前測試把樓主的範例Id都改成Serno,且不做任何的設定,Slapper也能正常帶入資料,他這是根據DB的primary key 還是他會去抓 SQL Join 用的Key?

看更多先前的回應...收起先前的回應...
hugo8319 iT邦新手 4 級 ‧ 2019-01-08 11:37:53 檢舉

另外想問一下下一篇我會介紹如何將 Insert、Update、Delete 抽離成共用的方法 這篇有出了嗎/images/emoticon/emoticon43.gif

謝謝大大,大家互相學習 /images/emoticon/emoticon37.gif

將 SQL 查詢回來的資料填入物件,這個動作就稱為映射 (Mapping)

鍵值用於子物件的映射
例如假設班級和學生的關係是一對多 (一個班級會有多位學生)
class會這樣定義

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int ClassId { get; set; }
}
public class Class
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Student> Students { get; set; }
}

Students 類似於 Entity 的導航屬性
接著看 SQL 語法,通常會用 JOIN 將班級和學生一起查出來

SELECT A.Id
      ,A.Name
	  ,B.Id AS Students_Id
	  ,B.Name AS Students_Name
	  ,B.ClassId AS Students_ClassId
FROM Class AS A
LEFT JOIN Student AS B ON B.ClassId=A.Id

有沒有注意到 Students_List<Student> 的關係,Slapper會用 XXX_ 這個前綴名將資料填入物件內

接著看查詢結果

1, 一年一班, 1, 學生1, 1
1, 一年一班, 2, 學生2, 1
1, 一年一班, 3, 學生3, 1
2, 一年二班, 4, 學生4, 2
2, 一年二班, 5, 學生5, 2

可以發現主表的資料被展開了,例如一年一班會有三筆重複的資料,如果沒有設定 [Slapper.AutoMapper.Id] Slapper 就不知道該用哪幾個欄位當鍵值來做資料合併

沒設定的話應該是不會合併,這邊我沒有仔細測試過,不過確定會有問題。

如果有鍵值就會將資料 Mapping 好,類似 Entity 的 Include 功能。

{
    "Id": 1,
    "Name": "一年一班",
    "Students": [
        {
            "Id": 1,
            "Name": "學生1",
            "ClassId": 1
        },
        {
            "Id": 2,
            "Name": "學生2",
            "ClassId": 1
        },
        {
            "Id": 3,
            "Name": "學生3",
            "ClassId": 1
        }
    ]
}

下一篇喔...
近期應該不會寫這系列 /images/emoticon/emoticon16.gif

給大大一個思考方向
我自己的經驗,SELECT 需要很靈活,所以用 Linq 反而不好寫,不如直接下 SQL
但 Insert、Update、Delete 基本上都差不多
所以可以寫一個函數,利用反射將 model 的欄位取出並組合成 SQL 語法,這樣就不用每次都重複寫基本語法。

public void Insert(T model)
{
    //利用反射產生 insert 語法
}

接著要考慮交易問題,我們希望可以將一連串的操作包成一個交易,所以需要設計一個 UnitOfWork 物件將資料存取層(Repository)串聯起來,類似 Entity SaveChange() 的做用。

hugo8319 iT邦新手 4 級 ‧ 2019-01-08 13:43:43 檢舉

了解/images/emoticon/emoticon42.gif
再次感次大大的回覆

2020/05/23 更新:

我後來改成 EF + Dapper 一起使用
因為利用反射組語法,這本來就是 EF 的強項
自己寫反而重複造輪子了

且 EF 也沒有 Slapper JOIN 的效能問題

如果條件允許,程式主架構我會使用 EF 先快速打底
需要 花式 SQL效能調整 的地方再搭配 Dapper 使用

文章開頭提到的 EF 心得分享
我寫在鐵人賽文章中摟
(́◕◞౪◟◕‵)*

有興趣的朋友可以參考這篇
[Day07] 使用 EF Core 讀取 Azure 上的 MySQL 資料庫

我要留言

立即登入留言