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
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 }
(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 }
(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 }
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 }
沒有作級聯處理,因此每一行結果就是一個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 }
作級聯處理,一共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 }
查詢結果與第一種一致.
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 }
運行了好幾回,結果大體就是這樣:
(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.