تذکر:
جهت درک صحیح مطالب این مقاله، آشنایی با مفاهیم Entity Framework CodeFirst و
WCF الزامی می باشد.
همانطور که حتما مستحضر هستید، شرکت مایکروسافت در نسخه Entity Framework 4.1
برای اولین بار کلاس DbContext را معرفی نمود که شیوه نوینی جهت برنامه نویسی برای
پایگاه داده بوده و با نام CodeFirst شناخته می شود. این شیوه برنامه
نویسی بسیار مورد استقبال برنامه نویسان گرفت و در نسخه های بعدی امکانات جالب
فراوانی به آن اضافه شد و هم اکنون نیز در حال توسعه و بهینه سازی می باشد.
یکی از مشکلات کار با ابزار های ORM این می باشد که کلاس های تولید شده معمولا
از کلاس های پایه ای به ارث رفته اند و این موضوع باعث می شود که این کلاس ها به
سادگی قابل انتقال از سمت سرویس به کلاینت نباشند و هزینه سریالایز (Serialize) و
دی سریالایز (Deserialize) نمودن این کلاس ها نیز بسیار بالا می باشد.
بنابراین این موضوع همواره مورد علاقه برنامه نویسان بوده است که بتوانند از
کلاس های پایه خود (POCO) و بدون هیچ سربار اضافی به جای کلاس های پیشفرض تولید شده
توسط Entity Framework استفاده نمایند.
با استفاده از روش CodeFirst براحتی می توان این قابلیت را ایجاد نمود. برای درک
بهتر مسئله، به یک مثال توجه کنید.
فرض کنید که دو کلاس به نام های Customer و Address داریم و به ازای این دو
کلاس، دو جدول در پایگاه داده با همین نام ها وجود دارد. رابطه بین جدول Customer با
جدول Address از نوع "یک به چند" می باشد یعنی هر مشتری می تواند چندین آدرس داشته
باشد.
کلاس های مربوط به این جداول در روش CodeFirst مشابه قطعه کد زیر می باشند.
public partial class
CustomerDBEntities : DbContext
{
public DbSet<Address> Addresses { get; set; }
public DbSet<Customer> Customers { get; set; }
}
public class Customer
{
public Customer()
{
this.Addresses = new HashSet<Address>();
}
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public
ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int CustomerId { get; set; }
public string Street { get; set; }
public string Alley { get; set; }
public int No { get; set; }
public
Customer Customer { get; set; }
}
همانطور که در بالا ملاحظه می کنید، کلاس های Customer و Address از هیچ کلاس
دیگری به ارث نرفته اند و دارای ویژگی های اضافه دیگری نیز نیستند و کاملا مناسب
برای منظور ما یعنی استفاده در سرویس های WCF می باشند.
اما روش بالا چند محدودیت برای ما ایجاد کرده است. از جمله اینکه چون موجودیت ها
از کلاس
های پایه Entity Framework به ارث نرفته اند، پس قابلیت های Lazy Loading و Change
Traking از کار افتاده اند و دیگر قابل استفاده نمی باشند.
برای اینکه قابلیت Lazy Loading را برای کلاس ها فراهم کنیم کافیست که خصوصیاتی
ناوبری (Navigation Properties) را به شکل virtual در آوریم. لازم به ذکر است که
خصوصیاتی ناوبری، همان خصوصیاتی می باشند که نمایانگر ارتباط بین کلاس ها (و
احتمالا جداول پایگاه داده) می باشند. در مثال ما هر مشتری می تواند چندین آدرس
داشته باشد و هر آدرس حتما مربوط به یک مشتری می باشد. بنابراین خصوصیت Addresses
در کلاس Customer و خصوصیت Customer در کلاس Address نمایانگر این رابطه بوده و
خصوصیاتی ناوبری می باشند.
تغییرات ما برای کلاس های Customer و Address به شکل زیر اعمال شده است.
public class Customer
{
public Customer()
{
this.Addresses = new HashSet<Address>();
}
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public
virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int CustomerId { get; set; }
public string Street { get; set; }
public string Alley { get; set; }
public int No { get; set; }
public
virtual Customer Customer { get; set; }
}
خوب تا اینجای کار همه چیز خوب پیش رفته و قابلیت Lazy Loading نیز به کلاس های
ما اضافه شده است.
شاید این سوال در ذهنتان بوجود آمده باشد که چگونه فقط با اضافه نموده کلمه
کلیدی virtual این قابلیت به کلاس های ما اضافه می شود؟!
با virtual نمودن
خصوصیات ناوبری، Entity Framework هنگام اجرای برنامه (RunTime) کلاس هایی با عنوان
"پروکسی های پویا" یا همان Dynamic Proxies به کلاس های Address و Customer اضافه
می کند و بنابراین قابلیت Lazy Loading برای این کلاس ها در زمان اجرای برنامه
فراهم می گردد.
تا اینجای کار توانستیم با تغییر کوچکی در کلاس ها قابلیت Lazy Loading را فراهم
کنیم ولی با اضافه شدن پروکسی های پویا به کلاس های ما، مجددا مشکل ابتدای مقاله
پدیدار می شوند و این کلاس ها قابلیت انتقال خود از طریق سرویس های WCF را از دست
می دهند زیرا پروکسی های پویا به طور پیشفرض قابلیت سریالایز و دیسریالایز شدن را
ندارند!
تذکر:
قبل از این که به سراغ راه حل برویم بد نیست به این موضوع نیز اشاره کنیم که در
صورتی که تمامی خصوصیات کلاس های خود را virtual کنیم، قابلیت Change Tracking نیز
به کلاس های ما اضافه می شود ولی اضافه نمودن این قابلیت بر روی راندمان و کارایی
برنامه تاثیر می گذارد و بنابراین در صورتی که قصد انجام این کار را دارید، قبل از
انجام این کار، تحقیقات و آزمایشات کافی را انجام دهید.
برگردیم به سراغ مشکل اصلی این مقاله.
خوشبختانه با استفاده کلاس DbContext می توانیم تولید کلاس های پروکسی را در
زمان اجرای برنامه را فعال یا غیر فعال کنیم. بنابراین تنها کافیست که هر زمان نیاز
به استفاده از آن ها در سرویس های WCF داشتیم، این قابلیت را به شکل زیر از کار
بیندازیم. در اینصورت قابلیت Lazy Loading و برخی ویژگی های دیگر از کار افتاده و
کلاس ها قابلیت استفاده در سرویس های WCF را خواهند داشت.
public Customer GetFirstCustomer()
{
using(var context = new
CustomerDBEntities())
{
context.Configuration.ProxyCreationEnabled =
false;
var firstCustomer = context.Customers.FirstOrDefault();
return firstCustomer;
}
}
در متد بالا ویژگی ProxyCreationEnabled مربوط به کلاس DbContext را غیر فعال
نموده ایم و بنابراین پروکسی های داینامیک تولید نخواهند شد و نتیجه کوئری قابل
استفاده در سرویس های WCF خواهد بود.