一、什么是DTO
先来看看百度百科的解释:
数据传输对象(DTO)(Data Transfer Object),是一种设计模式之间传输数据的软件应用系统。数据传输目标往往是数据访问对象从数据库中检索数据。数据传输对象与数据交互对象或数据访问对象之间的差异是一个以不具有任何行为除了存储和检索的数据(访问和存取器)。
二、为什么需要DTO
在一个软件系统的实现中,我们常常需要访问数据库,并将从数据库中所取得的数据显示在用户界面上。这样做的一个问题是:用于在用户界面上展示的数据模型和从数据库中取得的数据模型常常具有较大区别。在这种情况下,我们常常需要向服务端发送多个请求才能将用于在页面中展示的数据凑齐。
三、使用Dapper实现DTO
使用Dapper可以直接返回DTO类型,包括两种方式:
新建Category、ProductDetail和ProductDTO实体类:
Category实体类定义如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace DapperConvertDto 8 { 9 public class Category10 {11 public int CategoryId { get; set; }12 public string CategoryName { get; set; }13 }14 }
ProductDetail实体类定义如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace DapperConvertDto 8 { 9 public class ProductDetail10 {11 public int ProductId { get; set; }12 13 public string ProductName { get; set; }14 15 public double Price { get; set; }16 17 public int CategoryId { get; set; }18 }19 }
ProductDTO实体类定义如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace DapperConvertDto 8 { 9 public class ProductDto10 {11 public int ProductId { get; set; }12 13 public string ProductName { get; set; }14 15 public double ProductPrice { get; set; }16 17 public string CategoryName { get; set; }18 }19 }
ProductDTO实体类中的ProductPrice对应ProductDetail表的Price,CategoryName对应Category表的CategoryName。
方式一:直接在SQL语句中使用as
1 using System; 2 using System.Collections.Generic; 3 using System.Data.SqlClient; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using Dapper; 8 9 namespace DapperConvertDto10 {11 class Program12 {13 static void Main(string[] args)14 {15 // 数据库连接16 string strCon = @"Initial Catalog=StudentSystem; Integrated Security=False;User Id=sa;Password=1qaz@WSX;Data Source=127.0.0.1;Failover Partner=127.0.0.1;Application Name=TransForCCT";17 SqlConnection conn = new SqlConnection(strCon);18 19 // 方式一:直接在SQL语句中使用as,将查询的字段转换成DTO类型的属性20 string strSql = @" SELECT p.ProductId,p.ProductName,p.Price AS ProductPrice,c.CategoryName FROM Category c INNER JOIN ProductDetail p ON c.CategoryId=p.CategoryId "; 21 ProductDto product = conn.Query(strSql).FirstOrDefault ();22 }23 }24 }
结果:
从截图中看出,返回的就是想要的DTO类型。
方式二:使用委托的方式进行映射,分别把Category和ProductDetail实体类里的属性,映射成ProductDTO类型的属性:
1 using System; 2 using System.Collections.Generic; 3 using System.Data.SqlClient; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using Dapper; 8 9 namespace DapperConvertDto10 {11 class Program12 {13 static void Main(string[] args)14 {15 // 数据库连接16 string strCon = @"Initial Catalog=StudentSystem; Integrated Security=False;User Id=sa;Password=1qaz@WSX;Data Source=127.0.0.1;Failover Partner=127.0.0.1;Application Name=TransForCCT";17 SqlConnection conn = new SqlConnection(strCon);18 19 // 方式一:直接在SQL语句中使用as,将查询的字段转换成DTO类型的属性20 string strSql = @" SELECT p.ProductId,p.ProductName,p.Price AS ProductPrice,c.CategoryName FROM Category c INNER JOIN ProductDetail p ON c.CategoryId=p.CategoryId "; 21 ProductDto product = conn.Query(strSql).FirstOrDefault ();22 23 // 方式二:使用委托进行自定义映射24 string strSql2 = @" SELECT p.ProductId,p.ProductName,p.Price,c.CategoryName FROM Category c INNER JOIN ProductDetail p ON c.CategoryId=p.CategoryId ";25 // 定义映射的委托26 Func map = (p, c) =>27 {28 ProductDto dto = new ProductDto();29 dto.ProductId = p.ProductId;30 dto.ProductName = p.ProductName;31 dto.ProductPrice = p.Price;32 dto.CategoryName = c.CategoryName;33 return dto;34 };35 // splitOn表示查询的SQL语句中根据哪个字段进行分割36 string splitOn = "CategoryName";37 List list = conn.Query (strSql2, map, splitOn: splitOn).ToList ();38 }39 }40 }
结果:
注意:
1、splitOn
splitOn表示查询的SQL语句中按照哪个字段进行分割,splitOn的顺序是从右向左的,遇到splitOn设置的字段接结束,把从右边开始到设置的这个字段归为同一个实体。例如:上面的例子中,splitOn设置为CategoryName,则表示从右边开始,到CategoryName为止的所有字段都是属于Category这个实体的,剩余的字段都是属于ProductDetail实体的。
2、注意委托中实体类的前后顺序
委托中实体类的前后顺序一定要和查询的SQL语句中字段的前后顺序一致,上面的例子中先查询的ProductDetail、后查询的Category,那么定义委托的时候,要先写ProductDetail,后写Category,如果委托中实体类的顺序错了,那么不会得到映射的数据,看下面的例子:
1 using System; 2 using System.Collections.Generic; 3 using System.Data.SqlClient; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using Dapper; 8 9 namespace DapperConvertDto10 {11 class Program12 {13 static void Main(string[] args)14 {15 // 数据库连接16 string strCon = @"Initial Catalog=StudentSystem; Integrated Security=False;User Id=sa;Password=1qaz@WSX;Data Source=127.0.0.1;Failover Partner=127.0.0.1;Application Name=TransForCCT";17 SqlConnection conn = new SqlConnection(strCon);18 19 // 方式一:直接在SQL语句中使用as,将查询的字段转换成DTO类型的属性20 string strSql = @" SELECT p.ProductId,p.ProductName,p.Price AS ProductPrice,c.CategoryName FROM Category c INNER JOIN ProductDetail p ON c.CategoryId=p.CategoryId "; 21 ProductDto product = conn.Query(strSql).FirstOrDefault ();22 23 // 方式二:使用委托进行自定义映射24 string strSql2 = @" SELECT p.ProductId,p.ProductName,p.Price,c.CategoryName FROM Category c INNER JOIN ProductDetail p ON c.CategoryId=p.CategoryId ";25 // 定义映射的委托26 //Func map = (p, c) =>27 //{28 // ProductDto dto = new ProductDto();29 // dto.ProductId = p.ProductId;30 // dto.ProductName = p.ProductName;31 // dto.ProductPrice = p.Price;32 // dto.CategoryName = c.CategoryName;33 // return dto;34 //};35 36 // 错误的委托37 Func map = (c,p) =>38 {39 ProductDto dto = new ProductDto();40 dto.ProductId = p.ProductId;41 dto.ProductName = p.ProductName;42 dto.ProductPrice = p.Price;43 dto.CategoryName = c.CategoryName;44 return dto;45 };46 47 // splitOn表示查询的SQL语句中根据哪个字段进行分割48 string splitOn = "CategoryName";49 List list = conn.Query< Category, ProductDetail, ProductDto>(strSql2, map, splitOn: splitOn).ToList ();50 }51 }52 }
结果: