本文目錄
<a href="#1">EF對單個實體的增查改删</a>
<a href="#1_1">增加單個實體</a>
<a href="#1_2">查詢單個實體</a>
<a href="#1_3">修改單個實體</a>
<a href="#1_4">删除單個實體</a>
<a href="#2">EF裡主從表關聯資料的各種增删改查</a>
<a href="#2_1">增加(增加從表資料、增加主從表資料)</a>
<a href="#2_2">查詢(查詢導航屬性為集合、查詢導航屬性為單個對象)</a>
<a href="#2_3">修改(修改從表的外鍵)</a>
<a href="#2_4">删除(删除主從表關系、删除主表資料、删除主從表資料、修改從表資料外鍵)</a>
<a href="#3">補充内容</a>
<a href="#3_1">SaveChanges方法送出多次操作</a>
<a href="#3_2">DbSet.Add方法傳回目前實體</a>
<a href="#4">源碼和系列文章導航</a>
注:本章節多次示範了各種删除,若要重複檢視效果,需解開注釋初始化資料的方法。
<a></a>
增加單個實體:
産生的insert sql:
查詢單個實體:
生成的select sql(find方法生成的查詢sql略複雜點,普通的linq查詢或者Lambda表達式寫法就簡單許多了):
修改單個實體:
産生的update sql:
删除單個實體:
産生的delete sql:
删除方法這樣寫可能有點效率問題:要删除一個實體,隻要知道它的id就可以了,但是上面的方法卻先加載了這個實體到記憶體中,這個是多餘的步驟。使用attach方法改進:
自然就沒有了先加載實體到記憶體中的sql,隻有一個簡單的删除sql。attach方法是讓EF知道DestinationId為2的實體是一個存在的實體。當然不使用attach,直接調用Remove方法删除會報一個InvalidOperationException錯:無法删除此對象,因為未在 ObjectStateManager中找到它。
attach中文意為“連接配接、附上、貼上”等意思。
注:Attach的實體事先不能已經在記憶體中,否則上下文會追蹤到兩個相同鍵名的實體,并且會報一個InvalidOperationException錯:An object with the same key already exists in the ObjectStateManager.
還有一種不加載實體到記憶體就可以删除實體的簡單方法,用EF直接執行sql:
可見,都不需要調用上下文的SaveChanges方法了,因為是直接執行sql,是以并不需要被資料庫上下文跟蹤到任何狀态。
ok,對于單個的增删改查就是這麼簡單,有Linq的寫法,也有Lambda表達式的寫法,都很簡單,下面看複雜點的。
1.增加
主從表資料的添加分為:僅添加從表資料、添加主表同時增加相關聯的從表資料
僅添加從表資料:
Lodging是住宿類,有兩個類繼承本類,分别Resort度假村類和Hostel宿舍類。上面的方法添加了一個Grand Canyon景點的度假村,Name是Pete's Luxury Resort。這裡的Grand Canyon是主表資料,Pete's Luxury Resort是從表資料。跟蹤到的sql:
添加主表資料同時添加相關聯的從表資料:
監控到三段sql,分别是添加主表資料,和兩條添加相關聯的從表資料,它們是通過外鍵destination_id相關聯的:
注意看第一段sql,使用了scope_identity(),這個和ado.net裡在每條insert的sql後加上;SELECT @@IDENTITY是一個意思,它會傳回自增長的主鍵id。這裡當然是需要傳回主鍵id的,因為後面從表的資料需要用這個當外鍵。可以複制第一條sql到資料庫環境裡執行看看效果。
2.查找
根據主表找從表資料(顯示加載:先Entry,然後Collection):
2013.09.04修改:之前描述的“根據主表找從表資料”有錯誤,Collection不是主表找從表,而是找導航屬性是一個集合的。
根據從表找主表資料(顯示加載:先Entry,然後Reference):
2013.09.04修改:之前描述的“根據從表找主表資料”有錯誤,Reference不僅僅是根據從表找主表,而是找導航屬性是一個實體對象的,常用于一對多關系裡從表對象找主表對象,也可以用于一對一關系的查找。
這是EF标準的查詢關聯表的資料。如果不看官方的API,大家會怎麼查呢?我想是這樣:先拿到主表主鍵id,然後根據id使用find方法(甚至使用ExcuteSqlCommad發送sql)去從表裡查,最後得到結果集。從表查主表也一樣。這樣寫有什麼不好呢?語句多了不少,其次不是EF建議的寫法,我個人還是建議使用Entry配合Collection和Reference方法。
3.修改
修改從表的外鍵:
Grand Hotel本來的外鍵是LocationId為1的Grand Canyon,代碼把它修改成到了LocationId為4的Great Barrier Reef下。生成的sql簡單明了:
4.删除
删除分為:删除主從表關系、删除主表資料不删除相關聯的從表資料、同時删除主從表資料(級聯和不級聯删除)、删除主表資料同時修改相關聯的從表資料指向另一個主表實體
删除主從表關系:主從表的關系是通過從表的外鍵列确定的,隻需要指派從表外鍵列為null即可
另一種方式:
住宿類Lodging跟人類Person有一個多對一的關系,這個很好了解,一個人可以有多個酒店。Dave's Dump這個住宿的地方本來本來是PrimaryContactId為1,也就是PersionId為1的這個人的記錄,上面的兩個方法都是修改這個1為空,即這個Dave's Dump這個住宿的地方不屬于任何人了。看看生成的sql:
删除主表資料不删除相關聯的從表資料:
ok,先介紹兩個新的實體:
沒有配置任何Data Annotation和Fluent API。兩個實體的關系是通過Reservation類的Trip導航屬性确立的。很明顯,這是一個一對一的關系,且預約類Reservation的外鍵Trip_Identifier是可空的(為何生成的外鍵名是Trip_Identifier?EF預設映射是取主表實體類名字加主鍵列),意思很明确,就是預約表Reservations的資料可以對應到旅行表Trip,也可以不對應:

看看這兩張表在資料庫裡有的資料:
很明顯,Reservations預約表的Trip_Identifier列(guid類型)指向了Trips表的主鍵列Identifier。試着删除:
根據Description列的内容從資料庫取出主表Trips的某條資料,然後直接調用上下文的Remove方法删除。程式跑起來會報一個DbUpdateException錯:
違反了主外鍵的限制。這個很好了解:從表的某條資料指向主表的這條資料,主表的這條資料自然不能随便删除。修改下方法,删除主表某條資料,同時加載其關聯的從表資料:
看看這幾行代碼生成了多少sql:
1.查出主表資料:
2.查出從表資料:
3.更新從表的外鍵為null:
4.删除主表資料:
看完了你肯定會想EF删除主表資料真麻煩:同時加載主表和從表的資料,然後設定從表外鍵為null讓它不指向主表任何資料,然後再删除主表資料。
正常的思維删除主表資料是這樣的:取出主表的主鍵字段,然後根據主鍵去從表裡找,看看有沒有相關聯的資料,有就指派外鍵為null,最後删除主表資料。寫出來無非就是各種find,然後update,最後delete。這是正常的思維和寫法,但是缺點很明顯:比上面的方法多寫了很多代碼。
是以,還是按照EF的思路來:删除主表資料,就同時加載主表和從表資料到記憶體中再執行删除主表資料的操作。隻需要直接調用Remove方法就好,EF自動把從表的相關資料外鍵列設定為null。
删除主表資料同時删除相關聯的從表資料(級聯删除)
因為Destination類和Lodging類已經設定好了級聯删除,是以直接找到主鍵删除即可,相關聯的從表資料由資料庫自動删除:
删除主表資料同時删除相關聯的從表資料(非級聯删除)
标注每個從表的資料為删除狀态,然後調用資料庫上下文的SaveChanges方法:
删除主表資料同時修改相關聯的從表資料指向另一個主表實體:
補充内容:以上所有的示範調用SaveChanges都是送出一個更改,試着送出多個操作:
增加一個Destinations表對象,又修改了一個對象,跟蹤下sql發現很明确的是一條insert,一條update的sql。SaveChanges也是一個事務,如果一個不成功,那麼所有都送出不成功。
仔細看上面的DbSet.Add方法可知,DbSet.Add方法傳回的對象就是添加的實體對象,上面的Add方法傳回的就是DbContexts.Model.Destination。這個給編碼提供了很好的便利性,來看一個方法: