請注明轉載地址:http://www.cnblogs.com/arhat
昨天晚上老魏配的機器終於到了,可是拿回來之后什么都組裝好了,唯獨差一個非常重要的組件”電源線”,老魏那個汗啊。於是從朋友那里借來一根電源線終於折騰到凌晨兩點多才把所有的軟件和系統裝好,今天早上順便裝了一個MAC(嘻嘻)。最讓老魏興奮的是換了VS2013,在使用VS2013的時候感覺非常的不錯,對EF的支持真的是大大的增強了,不用再跑到網上找這個插件那個插件的,直接通過T4文件來生成Context是實體類了。那么今天老魏就用VS2013和SQL Server2008來繼續學習EF把,至於創建的過程老魏在第一章中已經介紹過了,基本過程是一樣的。前兩章老魏用的是MySQL,那么本章用的是SQL Server,本質上是一樣的,老魏就是把MySQl中的數據導入到SQL Server中。
說到這里了,老魏不得不提一下在SQL Server表設計器中是無法對表建立多對多關系的,需要到數據庫關系圖中建立,這點很讓老魏不爽!更讓了老魏不爽的是使用HeidiSQL竟然無法對SQL Server建立表關系,我那個擦啊!弄得老魏都崩潰了,如果各位朋友那個有好一點的SQL Server的客戶端工具可以推薦一下。
好了,開始本章的內容!在上一章中我們只是對Clazz,Student兩個表進行一個單獨的插入,刪除和更新以及簡單的使用了Where擴展方法來得到數據。那么本章將會繼續對這兩個表的操作來進一步的說明,因為在本章主要是利用EF的懶加載來對數據操作。好了,看看本章的第一個需求。
如果我們想在插入班級的時候就把改班級的學生也插入到數據庫中,這樣可以不可以呢?答案是可以的,其實在平時我們的項目中就已經有這樣的需求,那么下面我們來看看用EF怎么實現呢!
更改Program.cs代碼如下:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); //定義一個Student實體對象 Model.Student student = new Model.Student() { SName="大話濟公",SAge=56,SMail="single_jie@163.com"}; //定義一個Clazz實體對象,這實體對象是要插入到數據庫中的 Model.Clazz clazz = new Model.Clazz() { CName="泡妞課程"}; //通過Clazz的導航屬性把student對象加入到Clazz的Students集合中 //這點EF的導航屬性做的非常不錯,傳統的ADO.NET實現起來比較麻煩 clazz.Students.Add(student); //更改數據庫 context.SaveChanges(); }
當我們執行的時候沒有發現問題產生,難道真得能夠插入到數據中嗎?我們打開數據庫來看看結果,驗證一下我們的猜想。
發現在數據庫中Student表和Clazz表都已經正確的插入了,那么說明沒有問題,但是我們在高興的同時要多思考一下。如果是在插入學生的時候能不能直接選擇一個班級呢?這個問題是在項目中經常操作的,比如發表一篇文章的時候要選擇分類。
好,我們既然發現了問題,那么我們在更改一下代碼:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); //定義一個Student實體對象 Model.Student student = new Model.Student() { SName="廣亮和尚",SAge=34,SMail="guangliang@163.com"}; //定義一個Clazz實體對象,這實體對象是要插入到數據庫中的 Model.Clazz clazz = new Model.Clazz() { CName="泡妞課程"}; //通過導航屬性把clazz對象附加到student實體對象上 student.Clazz = clazz; //把clazz對象加入到Clazz context.Student.Add(student); //更改數據庫 context.SaveChanges(); }
同樣的運行沒有任何的問題,但是當我們查看數據的時候卻發現問題來了。
各位發現了什么?學生能夠正常的插入,而班級則重復的插入了,按照我們正常的思維者這條記錄是不應該插入到數據庫中,其實這不該怪EF的,這是我們的業務邏輯有問題了,那么下面我們更改一下代碼:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); //定義一個Student實體對象 Model.Student student = new Model.Student() { SName="飛龍僧",SAge=56,SMail="feilong@163.com"}; //得到一個Clazz實體對象 Model.Clazz clazz = context.Clazz.Where<Model.Clazz>(c => c.CId == 1).Take<Model.Clazz>(1).FirstOrDefault<Model.Clazz>(); //通過導航屬性把clazz對象附加到student實體對象上 student.Clazz = clazz; //把clazz對象加入到Clazz context.Student.Add(student); //更改數據庫 context.SaveChanges(); }
運行一下,發現沒有問題,當我們再次查看數據的時候,發現數據時正常的,在Clazz表中並沒有插入新的紀錄(廢話,因為我們是查出來的嘛!)。
但是此時我們有生出了一個疑問,在想Student表中插入數據的時候,我們只需要一個CId,但這里我們卻是給student實體對象一個clazz對象,這樣不是浪費了內存了嗎?其實不然,微軟比我們聰明肯定想到了。其實在賦值的時候,只是把CId屬性的值賦值給了Student的CId了。下面是生成的SQL語句
INSERT [dbo].[Student]([SName], [SAge], [SMail], [CId]) VALUES (@0, @1, @2, @3) SELECT [SId] FROM [dbo].[Student] WHERE @@ROWCOUNT > 0 AND [SId] = scope_identity()',N'@0 varchar(50),@1 int,@2 varchar(50),@3 int',@0='飛龍僧',@1=56,@2='feilong@163.com',@3=1
本來嘛,使用EF只是為了能夠更簡單的操作數據庫,那么肯定是要犧牲一點效率的,這點就不用考慮了,現在的服務器的配置都那么高了,根本不在乎這點內存的浪費。
現在我們如果要更新一個學生的班級該如何做呢?看看代碼:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); //得到一個Student實體對象 Model.Student student = context.Student.Where<Model.Student>(s=>s.SId==3).Take<Model.Student>(1).FirstOrDefault<Model.Student>(); //定義一個Clazz實體對象,這實體對象是要插入到數據庫中的 Model.Clazz clazz = context.Clazz.Where<Model.Clazz>(c => c.CId == 10).Take<Model.Clazz>(1).FirstOrDefault<Model.Clazz>(); //通過導航屬性把clazz對象附加到student實體對象上 student.Clazz = clazz; //更改數據庫 context.SaveChanges(); }
運行一下,正常運行,在數據中同樣也到了爭取的結果了。但是我們現在又有問題誕生了,在更新的時候,我們是先查詢出來讓后再更新到數據庫,這樣的話就對數據庫操作了兩次,顯然不是我們想要的效果,那么該怎么來解決這問題呢,也就是不用先查詢然后在更新呢?答案是可以的!
在說這個問題的時候,老魏在這里得給大家說一下抱歉了,由於前兩章的內容是MySql EF5的版本的,所以和本節EF6的內容稍微的優點不同,所以這里呢,老魏使用EF6的方法把。
比如現在我們有如下的代碼:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); Model.Student student = new Model.Student(); //在使用這中方法的時候,必須保證主鍵在數據庫中必須存在 student.SId = 4; student.SName = "飛龍僧"; student.SAge = 34; student.CId = 1; student.SMail = "flydragon@ww.com"; //通過DbEntityEntry來跟蹤student的狀態,通過修改狀態值,來更新數據 context.Entry(student).State = EntityState.Modified; //更改數據庫 context.SaveChanges(); }
運行一下,在數據總的確放生改變了,那么在這里出現的Entry又是個什么東西呢?根據微軟的解釋是:“DbEntityEntry此類的實例提供對有關由 DbContext 跟蹤的實體的信息和控制的訪問權。 使用上下文的 Entity 或 Entities 方法來獲取此類型的對象。“什么意思呢?其實Entity Framworke再把實體更新到數據庫中(增刪改查),是根據實體的狀態來確定的,那么實體的狀態又是由DbEntityEntry這個類的對象來保存和設置,但同時還起到一個非常重要的作用,那就是把Entity(這里是student)加入到context中,如果Entity不在context中,那么context是無法更新數據的,所以context.Entry(Entity)就是把實體加入到context中進行管理,同時更改一下他的狀態,讓context知道如何去處理這個Entity.當調用SaveChanges方法時候,context就根據state來處理數據。
EntityState,實體對象的狀態。標志我們開發人員對實體的相應的操作,如下表格是實體的相關狀態以及說明。
成員名稱 | 說明 |
Detached | 對象存在,但沒有被跟蹤。 在創建實體之后、但將其添加到對象上下文之前,該實體處於此狀態。 An entity is also in this state after it has been removed from the context by calling the Detach method or if it is loaded by using a NoTrackingMergeOption. 沒有 ObjectStateEntry 實例與狀態為 Detached 的對象關聯。 |
Unchanged | 自對象附加到上下文中后,或自上次調用 SaveChanges 方法后,此對象尚未經過修改。 |
Added | 對象為新對象,並且已添加到對象上下文,但尚未調用 SaveChanges 方法。 在保存更改后,對象狀態將更改為 Unchanged。 狀態為 Added 的對象在 ObjectStateEntry 中沒有原始值。 |
Deleted | 對象已從對象上下文中刪除。 在保存更改后,對象狀態將更改為 Detached。 |
Modified | 對象上的一個標量屬性已更改,但尚未調用 SaveChanges 方法。 在不帶更改跟蹤代理的 POCO 實體中,調用 DetectChanges 方法時,已修改屬性的狀態將更改為 Modified。 在保存更改后,對象狀態將更改為 Unchanged。 |
對象上下文必須知道對象狀態才能將更改保存回數據源。雖然這樣我們可以少操作一次數據庫,但是由於必須保證的是主鍵在數據庫中必須存在,所以這點我們在寫程序的時候是很難保證的,所以老魏建議還是使用原來的方法,當然了這個得根據不同情況選用不同的方法,有的人推薦使用EntityState,有的人推薦使用查詢更新,具體情況及具體分析吧,但是在這里老魏的說一下,如果要更改實體的某些屬性而不是全部屬性的時候,我們這個時候得使用EntityState了。
好了,到此,關於Entity Framework的CRUD的操作說明老魏就說到這里了,只能起到一個拋磚引玉的作用,希望大家能夠從中得到點什么東西吧!