三層架構的一點理解以及Dapper一對多查詢

1.首先說一下本身對三層架構的一點理解算法

論壇裏常常說會出現喜歡面相對象的寫法,因此使用EF的,我我的以爲他倆沒啥關係,先別反對,先聽聽我怎麼說吧.sql

三層架構,基本都快說爛了,但今天仍是說三層架構:UI,BLL,DAL.以前每每強調的是三層架構各司其職,但沒有說三層架構中每層之間怎麼交互的,以及人員之間的分工合做問題.今天重點說這個,從而回答面向對象和EF的關係.數據庫

今天說的與數據庫有關,那就先說BLL和DAL之間數據的傳遞.有很多都是按照如下兩種方式寫的:編程

(1)以Datatable傳遞.可想一想,一個datatable對象,裏面是什麼呢?對於這樣的方式做爲業務層和數據庫之間的橋樑不直觀,而且沒法應對變化,還不如寫成一層.後端

(2)那我定義成對象不就直觀了,因此有些就一張表定義一個對象,對象和數據庫表如出一轍.這樣寫呢,沒比第一種好到哪去.首先數據庫修改以後,Model得修改,Dal層也得修改.還有,這也是最重要的,若是兩我的分別開發BLL和DAL,那這兩我的必須都得熟悉數據庫以及他們之間的對應關係.架構

那怎麼樣比較合適呢?app

隨着編程愈來愈成熟,細分不可避免.在公司中每一個人只關心本身的那部分,三層分別劃分爲不一樣的人或團隊編寫.這樣不僅是從代碼層面,從管理層面更符合術業有專攻,每層專一於本身的功能:框架

(1)業務人員更應該瞭解業務的編寫,業務算法的實現,計算性能等,而無需關心數據怎麼存儲,無需學習數據庫,無需關心索引,主鍵等等.前後端分離

(2)UI更多的關心界面怎麼佈局,怎麼作的更好看,更符合用戶的使用等,而無需關心數據怎麼存儲,業務怎麼計算等.ide

(3)數據庫服務層(注意如今多加了服務兩個字,很關鍵)更應該關心數據用什麼去存儲去知足高性能的數據增刪改查,在這我沒說非是關係型數據庫,仍是NoSql什麼的,其實若是text,excel存儲方便且能知足性能要求,又有何不可呢.

三層有了,那他們之間怎麼銜接呢,答案就是業務,每一個業務都須要展現,計算(簡單業務不須要),存儲數據,這不就是三層人員須要乾的事嗎,根據展現的數據定義好對象做爲UI和BLL層之間的傳輸對象.存儲也同樣,根據業務須要存儲數據的特徵定義好存儲的對象,做爲BLL和DAL之間交互的對象.實際這兩個對象就是三層交互的公約,有了這個公約,每層開發根據公約實現每層的功能,每層的編程人員也再也不須要關心其餘層怎麼實現的.DAl層已經不只僅是數據庫這麼簡單了,而是對對象增刪改查.

這樣作的好處:

(1)人員更加專業,分工明確後,每一個人專業技能容易提高

(2)只要交互的對象不發生改變,三層隨意修改,都不會影響到其餘兩層.

(3)編程人員更容易造成本身的模式,加快開發速度.

(4)編寫人員接觸多了,踩過的坑就越多,編寫的代碼也就越健康.等等吧,好處大大滴.

說了這麼多,如今回答EF的問題,根據以上劃分的三層,EF必然歸數據服務層了.若是是寫UI或業務的人員,那面不面向對象與EF沒有任何關係.數據服務層就是實現對象的增刪改查,若是使用的關係型數據庫,那使用EF到底能簡化多少行代碼,我以爲通常般吧.

微軟總喜歡給用戶一整套解決方案,好比MVC,用微軟的VS直接能夠生成MVC框架,但MVC不是真正意義上的先後端分離.一樣EF更偏向於一我的來完成全部層的編寫.

 2.性能

性能都說Dapper與原生ADO.NET差很少,其處理過程是同樣的(我的理解,沒有深究,有不對的請指導.):

ADO.NET是把查詢的賦值給datatable或dataset.

Dapper是利用委託的方式,把每行的數據處理以後再賦值給對象.若是不用委託,那就直接把每行查出來的數據賦值給對象便可.最後生成一個集合.

EF應該是根據設置的主外鍵關係生成sql語句,而後再去查詢.但對於複雜的查詢,自動生成sql語句就沒那麼容易了,在考慮索引的問題,那就是難上加難了,可能會常常出現不使用索引的狀況,致使查詢速度慢.

3.Dapper實現一對多查詢

 既然說一對多,那就整個複雜的,兩級一對多查詢.有更復雜的也但願你們提供.

 公司分爲多個部門,每一個部門又有不少員工.

 3.1先創建表,並添加示例數據

 1 USE [test]
 2 GO
 3 /****** Object:  Table [dbo].[staff]    Script Date: 11/12/2019 10:18:55 ******/
 4 SET ANSI_NULLS ON
 5 GO
 6 SET QUOTED_IDENTIFIER ON
 7 GO
 8 CREATE TABLE [dbo].[staff](
 9     [id] [int] IDENTITY(1,1) NOT NULL,
10     [part_id] [int] NOT NULL,
11     [name] [nvarchar](50) NULL
12 ) ON [PRIMARY]
13 GO
14 SET IDENTITY_INSERT [dbo].[staff] ON
15 INSERT [dbo].[staff] ([id], [part_id], [name]) VALUES (1, 1, N'員工1')
16 INSERT [dbo].[staff] ([id], [part_id], [name]) VALUES (2, 1, N'員工2')
17 INSERT [dbo].[staff] ([id], [part_id], [name]) VALUES (3, 1, N'員工3')
18 INSERT [dbo].[staff] ([id], [part_id], [name]) VALUES (4, 2, N'員工4')
19 INSERT [dbo].[staff] ([id], [part_id], [name]) VALUES (6, 2, N'員工5')
20 INSERT [dbo].[staff] ([id], [part_id], [name]) VALUES (7, 3, N'員工6')
21 SET IDENTITY_INSERT [dbo].[staff] OFF
22 /****** Object:  Table [dbo].[department]    Script Date: 11/12/2019 10:18:55 ******/
23 SET ANSI_NULLS ON
24 GO
25 SET QUOTED_IDENTIFIER ON
26 GO
27 CREATE TABLE [dbo].[department](
28     [id] [int] IDENTITY(1,1) NOT NULL,
29     [com_id] [int] NOT NULL,
30     [name] [nvarchar](50) NULL
31 ) ON [PRIMARY]
32 GO
33 SET IDENTITY_INSERT [dbo].[department] ON
34 INSERT [dbo].[department] ([id], [com_id], [name]) VALUES (1, 1, N'部門1')
35 INSERT [dbo].[department] ([id], [com_id], [name]) VALUES (2, 1, N'部門2')
36 INSERT [dbo].[department] ([id], [com_id], [name]) VALUES (3, 2, N'部門1')
37 INSERT [dbo].[department] ([id], [com_id], [name]) VALUES (4, 2, N'部門3')
38 SET IDENTITY_INSERT [dbo].[department] OFF
39 /****** Object:  Table [dbo].[company]    Script Date: 11/12/2019 10:18:55 ******/
40 SET ANSI_NULLS ON
41 GO
42 SET QUOTED_IDENTIFIER ON
43 GO
44 CREATE TABLE [dbo].[company](
45     [id] [int] IDENTITY(1,1) NOT NULL,
46     [name] [nvarchar](50) NULL
47 ) ON [PRIMARY]
48 GO
49 SET IDENTITY_INSERT [dbo].[company] ON
50 INSERT [dbo].[company] ([id], [name]) VALUES (1, N'公司1')
51 INSERT [dbo].[company] ([id], [name]) VALUES (2, N'公司2')
52 INSERT [dbo].[company] ([id], [name]) VALUES (3, N'公司3')
53 SET IDENTITY_INSERT [dbo].[company] OFF
View Code

3.2對象定義

(1)公司

 1  /// <summary>
 2     /// 公司
 3     /// </summary>
 4     public class Company
 5     {
 6         /// <summary>
 7         /// 公司ID
 8         /// </summary>
 9         public int CompanyID { get; set; }
10         /// <summary>
11         ///公司名稱
12         /// </summary>
13         public string CompanyName { get; set; }
14         /// <summary>
15         /// 公司包含的部門
16         /// </summary>
17         public List<Department> Department { get; set; }
18     }
View Code

(2)部門

 1   /// <summary>
 2     /// 部門
 3     /// </summary>
 4     public class Department
 5     {
 6         /// <summary>
 7         /// 部門ID
 8         /// </summary>
 9         public int DepartmentID { get; set; }
10         /// <summary>
11         /// 部門名稱
12         /// </summary>
13         public string DepartmentName { get; set; }
14         /// <summary>
15         /// 部門包含的員工
16         /// </summary>
17         public List<Staff> Staff { get; set; }
18     }
View Code

(3)員工

 1   /// <summary>
 2     /// 員工
 3     /// </summary>
 4     public class Staff
 5     {
 6         /// <summary>
 7         /// 員工ID
 8         /// </summary>
 9         public int StaffID { get; set; }
10         /// <summary>
11         /// 員工姓名
12         /// </summary>
13         public string StaffName { get; set; }
14     }
View Code

3.3不使用委託

 1         /// <summary>
 2         /// 不用委託
 3         /// </summary>
 4         public static List<Company> NoDelegate()
 5         {
 6             using (IDbConnection conn = new SqlConnection("Data Source = .;Initial Catalog = test;User Id = sa;Password = sa;"))
 7             {
 8                 string sql = "SELECT com.id CompanyID,com.name CompanyName,part.id DepartmentID, part.name DepartmentName,st.ID StaffID, st.name StaffName FROM company com LEFT JOIN department part ON part.com_id=com.id LEFT JOIN staff st ON st.part_id=part.id";
 9                 return conn.Query<Company>(sql).AsList();
10             }
11         }
View Code

 

 沒有作級聯處理,因此每一行結果就是一個Company對象,一共8行,結果就是8個Company對象的集合.

3.4使用委託

 1         /// <summary>
 2         /// 使用委託
 3         /// </summary>
 4         /// <returns></returns>
 5         public static List<Company> UseDelegate()
 6         {
 7             using (IDbConnection conn = new SqlConnection("Data Source = .;Initial Catalog = test;User Id = sa;Password = sa;"))
 8             {
 9                 string sql = "SELECT com.id CompanyID,com.name CompanyName,part.id DepartmentID, part.name DepartmentName,st.ID StaffID, st.name StaffName FROM company com LEFT JOIN Department part ON part.com_id=com.id LEFT JOIN staff st ON st.part_id=part.id";
10                 var res = new List<Company>();
11                 conn.Query<Company, Department, Staff, Company>(sql, (com, part, staff) =>
12                 {
13                     //查找是否存在該公司
14                     var t = res.Where(p => p.CompanyID == com.CompanyID).FirstOrDefault();
15                     //若是沒有該公司,則添加該公司
16                     if (t == null)
17                     {
18                         t = new Company() { CompanyID = com.CompanyID, CompanyName = com.CompanyName };
19                         res.Add(t);
20                     }
21                     //先判斷該行數據的part是否爲空,若是爲空則不作任何處理.
22                     Department parttmp = null;
23                     if (part != null)
24                     {
25                         if (t.Department == null)
26                             t.Department = new List<Department>();
27                         parttmp = t.Department.Where(p => p.DepartmentID == part.DepartmentID).FirstOrDefault();
28                         if (parttmp == null)
29                         {
30                             parttmp = part;
31                             t.Department.Add(part);
32                         }
33                     }
34                     //判斷該行數據的員工是否爲空
35                     if (staff != null)
36                     {
37                         if (parttmp.Staff == null)
38                             parttmp.Staff = new List<Staff>();
39                         parttmp.Staff.Add(staff);
40                     }
41                     return com;//沒有任何意義,不過委託須要有返回值.
42                 }, splitOn: "DepartmentID,StaffID");
43                 return res;
44             }
45         }
View Code

 

 作級聯處理,一共3個公司,因此結果是3個Company的集合.每一個Company分別包含下屬的部門和員工.

  3.5使用ADO.NET查詢DataTable,並轉換成對象

 1        /// <summary>
 2         /// 利用datatable實現,爲了測試性能,因此再也不整理成級聯的形式.
 3         /// </summary>
 4         /// <returns></returns>
 5         public static List<Company> UseTable()
 6         {
 7             List<Company> res = new List<Company>();
 8             DataTable dt = new DataTable();
 9             using (SqlConnection conn = new SqlConnection("Data Source = .;Initial Catalog = test;User Id = sa;Password = sa;"))
10             {
11                 if (conn.State != ConnectionState.Open)
12                     conn.Open();
13                 SqlCommand sqlCommand = new SqlCommand("SELECT com.id CompanyID,com.name CompanyName,part.id DepartmentID, part.name DepartmentName,st.ID StaffID, st.name StaffName FROM company com LEFT JOIN Department part ON part.com_id=com.id LEFT JOIN staff st ON st.part_id=part.id", conn)
14                 {
15                     CommandType = CommandType.Text
16                 };
17                 SqlDataAdapter da = new SqlDataAdapter
18                 {
19                     SelectCommand = sqlCommand
20                 };
21                 da.Fill(dt);
22             }
23             for (int i = 0; i < dt.Rows.Count; i++)
24             {
25                 res.Add(new Company()
26                 {
27 
28                     CompanyID = Convert.ToInt32(dt.Rows[i]["CompanyID"]),
29                     CompanyName = dt.Rows[i]["CompanyName"].ToString(),
30                     Department = new List<Department>() {
31 
32                        dt.Rows[i]["DepartmentID"]!=DBNull.Value?  new Department() {
33                             DepartmentID =  Convert.ToInt32(dt.Rows[i]["DepartmentID"]),
34                             DepartmentName = dt.Rows[i]["DepartmentName"].ToString(),
35                             Staff = new List<Staff>() {
36                                dt.Rows[i]["StaffID"]!=DBNull.Value? new Staff() {
37                                     StaffID =Convert.ToInt32(dt.Rows[i]["StaffID"]),
38                                     StaffName =dt.Rows[i]["StaffName"].ToString() }:null
39                             }
40                         }:null
41                     }
42                 });
43             }
44             return res;
45         }
46     }
View Code

查詢結果與第一種一致.

3.6性能對比

每一個循環執行10000次,記錄執行的時間.在本身的電腦上測試的.不一樣電腦有所差別,每次運行所用時間也不一樣,非專業測試.但能說明問題.

 1 static void Main(string[] args)
 2 {
 3 Stopwatch sw = new Stopwatch();
 4 sw.Restart();
 5 for (int i = 0; i < 10000; i++)
 6 {
 7 var t = NoDelegate();
 8 }
 9 sw.Stop();
10 Console.Write("不用委託的時間:" + sw.ElapsedMilliseconds);
11 Console.WriteLine();
12 sw.Restart();
13 for (int i = 0; i < 10000; i++)
14 {
15 var t1 = UseDelegate();
16 }
17 sw.Stop();
18 Console.Write("使用委託的時間:" + sw.ElapsedMilliseconds);
19 Console.WriteLine();
20 sw.Restart();
21 for (int i = 0; i < 10000; i++)
22 {
23 var t2 = UseTable();
24 }
25 sw.Stop();
26 Console.Write("使用DataTable的時間:" + sw.ElapsedMilliseconds);
27 Console.ReadLine();
28 }
View Code

 運行了好幾回,結果大體就是這樣:

(1)使用Dapper,但不作級聯處理,時間最長.

(2)使用Dapper,作級聯處理,耗時最短.

(3)使用DataTable,耗時在二者之間.

緣由分析(我的理解):

以上三種狀況的區別能夠分爲兩點:

第一點執行過程:Dapper執行一次.ADO.NET先查詢出DataTable,而後再轉換爲對象.

第二點轉換對象方式:Dapper是利用Emit把數據轉換成對象,使用DataTable實例化對象是先初始化對象再賦值;

基於以上的區別,三種方式執行耗時的緣由以下:

第一種方式雖然不像第三種方式通過兩步,但因爲數據轉換對象的次數較多,而且使用Emit轉換而不是直接定義對象的方式賦值,因此耗時較長.

第二種方式即在每行查詢的過程當中給對象賦值,而且因爲判斷了公司和部門是否存在,數據轉換到對象的次數明顯少了不少.

第三種方式因爲先把查詢的結果賦值給DataTable,而後再把DataTable轉換成對象,通過兩步,比第二種方式耗時多一點,但優於第一種.

3.7第二種方式編寫知識點說明

 (1)執行過程,分爲三步:

先定義一個Company集合,也就是返回值;

Dapper逐行處理SQL語句的查詢結果,並添加到返回值的集合中;

最後返回Company集合.

(2) 解釋Query中的四個對象Company,Department,Staff,Company

Dapper每行查詢後會根據SQL語句和splitOn定義的分隔造成前三個對象(Company,Department,Staff)

最後一個Company定義了該Query查詢的結果是Company的集合.

(3)委託的做用

委託就是定義Dapper以什麼樣的方式處理每行數據.本例中,處理流程:

Dapper查詢出每行數據之後,定義的委託先判斷在結果集合中(res:Company集合)判斷是否存在該行數據指定的公司,若是不存在該公司,利用該行的公司數據初始化一個新的公司對象,並添加到res中.

而後判斷該公司中是否存在該行數據指定的部門,若是不存在一樣利用該行的部門數據實例化一個部門,並添加到公司對象中.

最後把員工添加到該行數據指定的部門中,員工不會出現重複.完成一行數據的處理.

以上過程能夠在委託中打斷點,看整個執行過程.

(4)splitOn

splitOn是爲了幫助Dapper在查詢每行的數據後,劃分爲指定的三個對象(Company,Department,Staff).在該實例中三個對象包含的字段分別是:

com.id CompanyID,com.name CompanyName;

part.id PartMentID, part.name PartMentName;

st.ID StaffID, st.name StaffName;

所以利用每一個對象的第一個字段做爲分隔符區分三個對象.例如splitOn: "DepartmentID,StaffID"中DepartmentID表示第二個對象(Department)的第一個字段,StaffID是第三個對象(Staff)的第一個字段.

(5)null值處理

在初始化Company和Department時,都未同時實例化其包含的部門集合和員工集合.以Company說明:

在查找結果集合中未包含當前查詢行的公司時,須要實例化一個Company,而後利用該行的數據實例化一個Company對象,並添加到結果集合中.

 t = new Company() { CompanyID = com.CompanyID, CompanyName = com.CompanyName };

沒有寫成 t = new Company() { CompanyID = com.CompanyID, CompanyName = com.CompanyName,Department = new List<Department>() };

也就是沒有對t的Department 進行初始化.而是先判斷該行的Department是否存在( if (part != null)),若是存在再判斷Department 是否爲空,爲空則進行實例化,不然就讓Department 爲空.

這樣是爲了能夠直接經過Department 是否爲null判斷數據的完整性,若是先實例化,那麼判斷數據的完整性的時候還須要判斷Department 個數是否大於0.

相關文章
相關標籤/搜索