標籤:self content 異常 string asm 過程 名稱 產生 rac
前言
本部分描寫敘述了EF怎樣載入相關實體的細節,而且怎樣在你的模型類中處理環形導覽屬性。(本部分預備了背景知識,而這不是完畢這個教程所必須的。你也能夠跳到第五節)
預載入和延遲載入
預載入和延遲載入的英文名稱各自是Eager Loading和Lazy Loading。
當EF與關聯式資料庫一同使用時。瞭解EF是怎樣載入相關資料是很重要的。
去查看EF產生的SQL查詢也是很有協助的。
為了追蹤SQL,加入下列代碼到BookServiceContext構造器中:
public BookServiceContext() : base("name=BookServiceContext"){ // New code: this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);}
假設發送一個GET請求到/api/books,它返回像以下這種JSON:
[ { "BookId": 1, "Title": "Pride and Prejudice", "Year": 1813, "Price": 9.99, "Genre": "Comedy of manners", "AuthorId": 1, "Author": null }, ...
你能看到Author屬性是空的,即便book包括有效AuthorId。
那是由於EF沒有在載入相關的Author實體。關於SQL查詢的追蹤記錄檔例如以下:
SELECT [Extent1].[BookId] AS [BookId], [Extent1].[Title] AS [Title], [Extent1].[Year] AS [Year], [Extent1].[Price] AS [Price], [Extent1].[Genre] AS [Genre], [Extent1].[AuthorId] AS [AuthorId] FROM [dbo].[Books] AS [Extent1]
該SQL跟蹤在Visual Studio的Output表單中顯示。——譯者注
SELECT語句從Books表中擷取資料,但並沒有引用Author表。
作為參考,這裡是在BooksController類中的方法。它返回books的列表。
public IQueryable<Book> GetBooks(){ return db.Books;}
來看看我們怎樣才幹讓Author作為返回的JSON資料的一部分。在Entity Framework中有三種方式載入相關資料:預載入(eager loading)、延遲載入(lazy loading)和顯式載入(explicit loading)。我們應該在這三種技術中有所取捨,所以瞭解它們是怎樣工作的就很重要了。
Eager Loading(預載入)
在預載入中,EF載入相關資料作為初始化資料庫查詢的一部分。
為了運行預載入,使用System.Data.Entity.Include擴充方法。
public IQueryable<Book> GetBooks(){ return db.Books // new code: .Include(b => b.Author);}
這會告訴EF將Author資料包括在查詢中。假設你做了這個改變並運行了app。如今JSON資料會是例如以下所看到的:
[ { "BookId": 1, "Title": "Pride and Prejudice", "Year": 1813, "Price": 9.99, "Genre": "Comedy of manners", "AuthorId": 1, "Author": { "AuthorId": 1, "Name": "Jane Austen" } }, ...
其追蹤記錄檔顯示EF在Book和Author表中運行了一個join操作。
SELECT [Extent1].[BookId] AS [BookId], [Extent1].[Title] AS [Title], [Extent1].[Year] AS [Year], [Extent1].[Price] AS [Price], [Extent1].[Genre] AS [Genre], [Extent1].[AuthorId] AS [AuthorId], [Extent2].[AuthorId] AS [AuthorId1], [Extent2].[Name] AS [Name] FROM [dbo].[Books] AS [Extent1] INNER JOIN [dbo].[Authors] AS [Extent2] ON [Extent1].[AuthorId] = [Extent2].[AuthorId]
Lazy Loading(延遲載入)
在延遲載入中,當實體的導覽屬性是非關聯時,EF會自己主動載入一個相關的實體。為了使用延遲載入。使導覽屬性變成虛擬。
比如。在Book類中:
public class Book{ // (Other properties) // Virtual navigation property public virtual Author Author { get; set; }}
如今考慮例如以下代碼:
var books = db.Books.ToList(); // Does not load authorsvar author = books[0].Author; // Loads the author for books[0]
當延遲載入開啟時。在books[0]上訪問Author屬性會使EF為author查詢資料庫。
延遲載入須要多段資料庫操作過程。由於每次EF發送一個查詢它都會取出一次相關實體。
通常。你希望為序列化的對象禁用延遲載入。序列化已經在模型上讀取了全部可能觸發載入相關實體的屬性。比如,以下是當延遲載入開啟後EF序列化books列表時的SQL查詢。
你能夠看到EF對於三個作者做了三次不同的查詢。
SELECT [Extent1].[BookId] AS [BookId], [Extent1].[Title] AS [Title], [Extent1].[Year] AS [Year], [Extent1].[Price] AS [Price], [Extent1].[Genre] AS [Genre], [Extent1].[AuthorId] AS [AuthorId] FROM [dbo].[Books] AS [Extent1]SELECT [Extent1].[AuthorId] AS [AuthorId], [Extent1].[Name] AS [Name] FROM [dbo].[Authors] AS [Extent1] WHERE [Extent1].[AuthorId] = @EntityKeyValue1SELECT [Extent1].[AuthorId] AS [AuthorId], [Extent1].[Name] AS [Name] FROM [dbo].[Authors] AS [Extent1] WHERE [Extent1].[AuthorId] = @EntityKeyValue1SELECT [Extent1].[AuthorId] AS [AuthorId], [Extent1].[Name] AS [Name] FROM [dbo].[Authors] AS [Extent1] WHERE [Extent1].[AuthorId] = @EntityKeyValue1
但還有很多時候你可能想要使用延遲載入。
預載入會造成EF產生很複雜的聯結。或者你可能須要對於小的資料集合的相關實體。延遲載入會更加有效。
避免序列化問題的一種方式是序列化傳輸資料對象(DTOs)而不是實體物件。我將會在後面的文章中展示這種實現。
顯式載入(Explicit Loading)
顯式載入和延遲載入很相似,除了你在代碼中顯式地擷取相關資料;當你訪問導覽屬性時它不會自己主動發生。
顯示載入會在載入相關資料時給你很多其它的控制權,但也須要額外的代碼。關於顯示載入的很多其它資訊,請查看Loading Related Entities。
http://msdn.microsoft.com/en-us/data/jj574232#explicit
導覽屬性和環形引用(Navigation Properties and Circular References)
當我定義Book和Author模型時。我在Book類中為Book-Author關係定義了導覽屬性。但我沒有在其它方向定義導覽屬性。
假設你在Author類中也定義對應的導覽屬性會怎樣呢?
public class Author{ public int AuthorId { get; set; } [Required] public string Name { get; set; } public ICollection<Book> Books { get; set; }}
不幸的是。當你在序列化模型時這會產生一個問題。
假設你載入相關資料,它會產生環形對象圖。
當JSON或XML格式試圖序列化圖時。它將會拋出一個異常。
這兩個格式拋出不同異常資訊。這裡是JSON格式的示範範例:
{ "Message": "An error has occurred.", "ExceptionMessage": "The ‘ObjectContent`1‘ type failed to serialize the response body for content type ‘application/json; charset=utf-8‘.", "ExceptionType": "System.InvalidOperationException", "StackTrace": null, "InnerException": { "Message": "An error has occurred.", "ExceptionMessage": "Self referencing loop detected with type ‘BookService.Models.Book‘. Path ‘[0].Author.Books‘.", "ExceptionType": "Newtonsoft.Json.JsonSerializationException", "StackTrace": "...” }}
這裡是XML格式的示範範例:
<Error> <Message>An error has occurred.</Message> <ExceptionMessage>The ‘ObjectContent`1‘ type failed to serialize the response body for content type ‘application/xml; charset=utf-8‘.</ExceptionMessage> <ExceptionType>System.InvalidOperationException</ExceptionType> <StackTrace /> <InnerException> <Message>An error has occurred.</Message> <ExceptionMessage>Object graph for type ‘BookService.Models.Author‘ contains cycles and cannot be serialized if reference tracking is disabled.</ExceptionMessage> <ExceptionType>System.Runtime.Serialization.SerializationException</ExceptionType> <StackTrace> ... </StackTrace> </InnerException></Error>
一個解決方式是使用DTO,我將會在下一節中描寫敘述它。
你能夠配置JSON或XML格式化程式來處理圖迴圈。關於很多其它資訊,請查看Handling Circular Object References. (http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization#handling_circular_object_references)
對於本教程。你不須要Author.Book導航熟悉,所以你能夠去掉它。
【Web API系列教程】3.4 — 實戰:處理資料(處理實體關聯)