使用LINQ to Entities來寫查詢語句
Entity Framework查詢是使用的.NET Framework功能Language Integrated Query,AKA LINQ。LINQ與.NET的編程體驗是緊密集成在一起的,它提供了強類型的查詢,何謂強類型,各位自行補腦。與弱類型的查詢相比,它提供了編譯時檢查來保證你的查詢通過驗證以及IntelliSense。
LINQ是一個通用的查詢框架,並不僅僅是針對Entity Framework或者數據庫的,LINQ提供程序負責把LINQ查詢轉換成對數據的查詢並返回結果,對Entity Framework來說這個提供程序就是LINQ to Entities,它負責把LINQ查詢轉換成對數據庫的SQL查詢,你提供給EF關於你的模型的信息以及模型如何映射到數據的的信息都會用來執行這個轉換,一旦查詢返回,EF就負責把結果數據返回給你的模型(拷貝數據給你的模型實例)。
下面為了演示DbContext查詢方面的功能我們建立下面幾個Model以及Context
1 [Table("Locations", Schema = "baga")] publicclass Destination
2 {
3 public Destination()
4 {
5 this.Lodgings = new List<Lodging>();
6 }
7
8 [Column("LocationID")]
9 public int DestinationId { get; set; }
10 [Required, Column("LocationName")]
11 [MaxLength(200)]
12 public string Name { get; set; }
13 public string Country { get; set; }
14 [MaxLength(500)]
15 public string Description { get; set; }
16 [Column(TypeName = "image")]
17 public byte[] Photo { get; set; }
18 public string TravelWarnings { get; set; }
19 public string ClimateInfo { get; set; }
20
21 public virtual List<Lodging> Lodgings { get; set; }
22 }
1 public class Lodging
16 [InverseProperty("PrimaryContactFor")]
17 [ForeignKey("PrimaryContactId")]
18 public Person PrimaryContact { get; set; }
19 public Nullable<int> SecondaryContactId { get; set; }
20 [InverseProperty("SecondaryContactFor")]
21 [ForeignKey("SecondaryContactId")]
22 public Person SecondaryContact { get; set; }
23 }
1 public class BreakAwayContext : DbContext
10 }
查詢所有數據
最簡單的查詢就是對一個給定的實體類型返回所有數據。同等於SELECT * FROM MYTABLE 查詢語句。EF會為你轉換LINQ查詢成SQL查詢。
獲取所有數據並不需要整的去寫一個查詢,你只需要簡單的迭代DbSet提供的內容,EF會發送一個查詢到數據庫來找尋DbSet其中所有的數據。
Example 2-4 查詢所有的destionations
private static void PrintAllDestinations()
{執行之後明了的顯示了表內所有的地點的名稱。
當代碼開始迭代Destionations中的內容的時候,EF發布了對數據庫的SQL查詢來返回所需數據:
SELECT
[Extent1].[LocationID] AS [LocationID], [Extent1].[LocationName] AS [LocationName], [Extent1].[Country] AS [Country], [Extent1].[Description] AS [Description], [Extent1].[Photo] AS [Photo] FROM [baga].[Locations] AS [Extent1]
細心的朋友可以看到Destionation Model特性跟語句表名及架構的對應關系。 這個SQL語句與我們平常寫的不太像,這是因為EF內置了通用的查詢語句生成算法,其不僅僅要考慮簡單查詢還需要考慮的非常復雜的場景。
重點:每次你迭代DbSet的時候EF都會執行一次對數據庫的查詢,如果你不停的查詢相同的數據會對性能造成影響。為了避免這樣,你可以使用ToList方法拷貝結果到一個List中,然後再迭代這個List的內容,而不會造成每次都查詢數據庫。
迭代DbSet只會返回存在於數據庫中的數據,任何存在內存中的對象處在等待被保存到數據庫中的狀態時候都不會返回,如果需要這些對象也被返回到查詢結果中,你可以使用"Query Local Data"技術,後續會講到。
使用LINQ排序、篩選...
先看個例子
Example 2-6 對destinations的查詢結果排序 :
1 private static void PrintDestinationNameOnly()上面的語句有點類似於SQL,這種寫法我們有個名字叫LINQ query sytenx,很好理解:查詢Destionatinos結果按Name排序,記住一點,EF不會立即執行數據庫查詢直到程序中首次使用返回的結果。
還有一種相同功能的寫法:
var query = context.Destinations .Where(d => d.Country == "Australia") .OrderBy(d => d.Name) .Select(d => d.Name);這個方法使用了lambda表達式來定義了查詢。
LINQ是很強大的查詢語句,上面的示例只是觸碰到了表皮而已,我這篇文章主要側重於DbContext,所以關於LINQ更多的功能及用法可以查看MSDN:
http://msdn.microsoft.com/zh-cn/library/bb399367.aspx
查詢單個對象
通過給予的key來查詢單個對象是最常用的使用場景,DbContext API 通過提供DbSet.Find方法來使它非常簡單,這裡有個注意點:如果Find方法找不到指定key的實體,它將返回null。
Find方法的一個優點,前一篇博客已有介紹,在這裡它還有一個功能:它能夠查詢最近的是添加狀態的數據(內存中),這個數據還沒有被保存到數據庫中。
Find使用下面幾個簡單的規則來定位到對象:
1.在內存中查詢已經存在的從數據庫中加載到內存中的實體,或者附加到上下文中的實體(通過Attach方法)。
2.查詢內存中是添加狀態還沒有保存到數據庫中的實體。
3.查詢數據庫中還沒有被加載到內存中的實體。
Find方法的使用在這就不舉例了。
Find方法的使用在這邊有個特殊的情況要說明下:也即復合主鍵的實體的查詢如何使用Find方法。
舉例:context.DemoEntity.Find("first key","second key");
這裡參數中多個key值的順序要按照數據庫列的順序(DB First)或者模型的定義的主鍵字段的順序(Modal First、Code First)來傳入。
也有很多場景下查詢單個實體不能使用Find方法的情況,比如:不是通過key來查詢的、想要在查詢中包含關聯數據的。要實現這些功能,你就必須要構建標准的LINQ查詢語句,然後使用Single方法來返回單個結果。示例如下:
var query = from d in context.Destinations
where d.Name="demoName"
select d;
var result = query.Single();
如果查詢返回無返回結果,調用Single方法會報錯,我們可以替代使用名如其意的SingleOrDefault方法:如果無返回結果就返回空。
SingleOrDefault方法使用與Find方法相同的數據庫查詢來查詢數據庫中的數據。這個SQL 選擇TOP 2 結果來保證只有一個匹配:
SELECT TOP (2)
[Extent1].[LocationID] AS [LocationID],
[Extent1].[LocationName] AS [LocationName],
[Extent1].[Country] AS [Conuntry],
...
FROM [baga].[Locations] AS [Extent1]
WHERE [Extent1].[LocationName]='demoName'
如果兩行數據被查找到,Single跟SingleOrDefault方法會發生異常,如果你只是想要第一個結果,而且並不關心是否會超過一個結果,你可以使用First或者FirstOrDefault.
查詢本地數據(Querying Local Data)
使用Querying Local Data的其中一個原因是:為了避免多次查詢數據庫,並且你已經知道數據已經被加載到內存中了,在上一篇文章中我們已經知道了其中的一個方法就是使用ToList方法拷貝結果到一個List。這個方法在我們在同一個代碼段內使用這個數據是可以的。但如果我們在應用程序內傳遞這個List時,事情就變的有點麻煩了。比如:我們在應用程序初始化的時候就從數據庫庫中查詢並加載了所有的Destinations,之後應用程序的不同區域對數據進行不同的查詢。在一些地方我們想要呈現所有的Destinations,在另外一些地方我們想要對名字進行排序,在另外一些地方我們想要對字段Country進行篩選,相較於在應用內傳遞Destinations列表數據,我們更願意利用數據上下文(context)在跟蹤所有實例的事實,並查詢本地數據。
另一個原因:如果你想返回的查詢結果包含最近是添加狀態的數據(還沒有被保存到數據庫中)。對LINQ查詢執行ToList方法總是會發送查詢請求到數據庫。這就意味著前一句的需求得不到滿足,而使用本地查詢,就可以包含狀態是添加狀態並且沒有保存到數庫的數據。
通過DbSet的Local屬性我們可以操作內存中的數據。Local會返回所有從數據庫中加載到內存中的數據,並包含是添加、修改、刪除狀態單並沒有保存到數據庫中的數據。
我們通過一個例子來演示下它的用法:查詢有多少個Destinations在內存中並且可被查詢。
Example 2-17
1 public static void GetLocalDestinationCount()
2 {8 }
運行程序我們可以看到如下輸出:
Destinations in memory:0
我們得到0的結果是因為我們還沒有運行任何查詢來從數據庫中獲取Destinations,並且我們也沒有增加任何新的Destination對象,讓我們更新下上的方法,在讀取Local.Count之前查詢下數據庫。
Example 2-18
1 public static void GetLocalDestinationCount()
2 {12 }
執行更新後的方法我們可以獲得如下輸出:
Grand Canyon
Hawaii
Destinations in memory:2
使用Load方法來加載數據到內存
用foreach來遍歷DbSet的內容是其中一種方法來把數據加載到內存中,但用這種方法來加載數據效率不高,並且代碼的意圖也有點表達不明確,特別的是當循環代碼沒有明確的處理本地查詢。
幸運的是DbContext API提供了Load方法,可以用來把DbSet從數據庫加載到內存中,我們繼續修改下示例2-18方法:
Example 2-19:
1 public static void GetLocalDestinationCountWithLoad()
2 {比較兩種方法,示例2-19代碼明確意圖明顯。
ps:Load方法實際上是IQueryable<T>的擴展方法,定義在System.Data.Entity命名空間中,如果你要使用它記得引用這個命名空間。
因為Load是IQueryable<T>的擴展方法,所以我們同樣可以對LINQ查詢的結果調用Load方法,把結果加載到內存中。
Example 2-20
1 public static void LoadAustralianDestination()
2 {12 }
這次我們只是把國家是澳大利亞的Destinations加載到內存中了,我們查看程序輸出,個數明顯減少了。
ps:我們已經知道了如果對LINQ查詢使用Load方法會把結果加載到內存中,但它並沒有把以前的查詢刪除掉,怎麼理解呢?比如說:你先對國家是澳大利亞的Destinations查詢結果調用了Load方法,然後你又對國家是美國的Destinations的查詢結果調用Load方法,兩個查詢結果都會存在內存中,訪問Local屬性都會返回它們。
----這篇博客准備分幾部分來講,下篇博客會接著這一篇,下一篇會講解一些查詢的高級主題:Lazy Loading、Eager Loading、Explicit Loading等。另外排版樣式真是頭疼,比較丑,各位將就著看看哈。