در این بخش از آموزش تصویری WEB Api به توضیح جزئیات بخشهای EF در ارتباط با انتیتیها میپردازیم و اینکه چگونه میتوان ویژگیهای Navigation را در کلاسهای مدل مدیریت کرد. (این بخش به دانش قبلی نیاز دارد و برای تکمیل این آموزش ضروری نیست. اگر میخواهید میتوانید این بخش را رد شده و به بخش بعد بروید.)
سایر پست های آموزش تصویری WEB Api
- آموزش APS.NET Web Api 2 با انتیتی فریم ورک ۶ (Entity Framework)
- افزودن مدل و کنترلرها – آموزش ASP.net Web API
- Seed کردن ساختمان داده – آموزش API
- روابط موجود در انتیتی – آموزش تصویری WEB API
- ساخت اشیاء انتقال داده یا DTOها
- ساخت اپلیکیشن سمت کاربر با فرمت Javascript
- ساخت رابط کاربری (UI)
آموزش تصویری WEB API
بارگیری دیرهنگام (lazy loading) در مقابل بارگیری مشتاق (eager Loading)
در زمان استفاده از EF با ساختمان داده مرتبط، درک اینکه چگونه EF دادههای مرتبط را بارگیری میکند اهمیت دارد.
همچنین مشاهده عبارتهای SQL که EF میسازد نیز بسیار کاربردی است. به منظور پیگیری 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 }, ...
همانطور که مشاهده میکنید بخش ویژگی نویسنده خالی است. با این که کتاب دارای ID نویسنده معتبری است. علت این امر این است که EF انتیتیهای مرتبط با نویسنده را بارگیری نمیکند. لاگ ردیابی 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]
عبارت SELECT تنها از جدول Books گرفته می گیرد و به جدول نویسنده ها رفرنس داده نمیشود.
در اینجا متدی در کلاس BookController ارائه شده است که لیست کتابها را در رفرنس ها نمایش می دهد.
public IQueryable<Book> GetBooks() { return db.Books; }
بیایید ببینم چگونه میتوانیم نویسنده را به عنوان بخشی از داده JSON بازگردانیم. سه راه برای بارگیری داده های مرتبط در انتیتی فریم ورک وجود دارد: بارگیری مشتاق(eage loading)، بارگیری دیرهنگام(lazy loading) و بارگیری صریح (explicit loading). هر کدام از این تکنیک ها خوبی و بدی هایی دارد. بنابراین فهم عملکرد هرکدام از آنها اهمیت زیادی دارد.
بارگیری مشتاق
با بارگیری eage loading (مشتاق) ، EF انتیتی های مرتبط را به عنوان بخشی از ساختمان داده لود میکند. به منظور اجرای eage loading ، از متد افزونه System.Data.Entity.Include استفاده کنید.
public IQueryable<Book> GetBooks() { return db.Books // new code: .Include(b => b.Author); }
این کد به EF میگوید که داده های نویسنده را نیز به پرس و جو اضافه کند. اگر این تغییرات را اعمال کرده و سپس اپلیکیشن را اجرا کنید، داده 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 ترکیبی از کتاب و نویسنده را اجرا کرده است.
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)
در بارگیری lazy loading انتیتی فریم ورک به صورت خودکار در زمانی که ویژگی navigation خوانده نشده باشد، انتیتی مرتبطی را لود میکند. به منظور فعال کردن بارگیری دیرهنگام، بخش Navigation را در حالت مجازی قرار دهید. برای مثال، در کلاس Book:
public class Book { // (Other properties) // Virtual navigation property public virtual Author Author { get; set; } }
حالا کد زیر را در نظر بگیرید:
var books = db.Books.ToList(); // Does not load authors var author = books[0].Author; // Loads the author for books[0]
زمانی که lazy loading فعال شده باشد، دسترسی به ویژگی Author در [Books[0 باعث میشود EF نویسنده را از ساختمان داده بخواند.
بارگیری دیرهنگام (lazy loading) به چند درخواست به ساختمان داده نیاز دارد. چرا که EF هر زمان که انتیتی مرتبطی را بازیابی میکند، یک کوئری میفرستند. به طور کلی، بهتر است بارگیری دیرهنگام را برای مواردی که به صورتی serialize هستند غیر فعال کنید. Serializerباید تمامی ویژگیهای یک مدل را بخواند که همین امر نیز موجب بارگیری انتیتیهای مرتبط میشود.
برای مثال، در این کد کوئریهای SQL را در زمانی که EF لیست کتاب ها را با بارگیری دیرهنگام serialize میکند مشاهده میکنید. میتوانید ببینید که 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] = @EntityKeyValue1 SELECT [Extent1].[AuthorId] AS [AuthorId], [Extent1].[Name] AS [Name] FROM [dbo].[Authors] AS [Extent1] WHERE [Extent1].[AuthorId] = @EntityKeyValue1 SELECT [Extent1].[AuthorId] AS [AuthorId], [Extent1].[Name] AS [Name] FROM [dbo].[Authors] AS [Extent1] WHERE [Extent1].[AuthorId] = @EntityKeyValue1
باز هم امکان دارد بخواهید در موقعیت هایی از lazy loading استفاده کنید. eager loading موجب میشود EF، پیوندهای بسیار پیچیده ای بسازد. شاید هم بخواهید برای زیرمجموعههای کوچک دادهها از انتیتیهای مرتبط استفاده کنید و به همین منظور نیز lazy loading برای شما کافی باشد.
یک راه برای جلوگیری از مشکلات Serialize شدن این است که انتقال اشیاء را (DTOs) به جای اشیاء انتیتی serialize کنیم. کمی بعد نحوه انجام این کار به به شما آموزش میدهم.
بارگیری صریح (Explicit loading)
بارگیری صریح هم شبیه به بارگیری دیرهنگام است. تنها تفاوت آن در این است که شما دادههای مرتبط را به صراحت از کد میگیرید. این امر زمانی که شما به ویژگی(Propert) Navigation دسترسی دارید، به صورت خودکار صورت نمیپذیرد. با بارگیری صریح زمانی که میخواهید داده مرتبط را بارگیری کنید، کنترل بیشتری خواهید داشت، اما به کد اضافی نیز نیاز دارید. برای کسب اطلاعات بیشتر در زمینه بارگیری صریح، مقاله Loading Related Entities را ببینید.
ویژگیهای Navigation و Circular References
زمانی که من مدلهای Book و Author را تعریف کردم، ویژگی Navigation را روی کلاس Book، برای رابطه نویسنده و کتاب معرفی کردم. اما ویژگی Navigation را در آدرس معرفی نکردم.
اگر ویژگی Navigation متناظر را برای کلاس Author اضافه کنیم چه اتفاقی میافتد؟
public class Author { public int AuthorId { get; set; } [Required] public string Name { get; set; } public ICollection<Book> Books { get; set; } }
متاسفانه، این کار زمانی که میخواهید مدلهای را serialize کنید مشکلی را به وجود میآورد. اگر دادههای مرتبط را بارگیری کنید، یک نمودار دایرهای برای شیء میسازد.
وقتی فرمتر JSON یا XML سعی در serialize کردن نمودار دارد، یک استثنا قائل میشود. این دو فرمتر exception messages های متفاوتی میفرستند. اینجا مثالی برای فرمتر 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ها استفاده کنیم. DTO را در بخش بعدی توضیح خواهم داد. از سوی دیگر، میتوانید فرمترهای JSON و XML را بررسی کنید تا چرخههای نمودار را متوجه شوید.برای کسب اطلاعات بیشتر، مطلب Handling Circular Object References را ببینید.
برای این آموزش تصویری WEB Api ، نیازی به ویژگی Navigation با عنوان Author.Book ندارید، بنابراین میتوانید از این بخش خارج شوید.