- اول، ابزار Microsoft ASP.NET Scaffolding را از منوی Tools گزینه Extensions and Updates دریافت و نصب نمایید.
- دوم پروژه جدیدی از نوع Visual C# ASP.NET Web Forms Application با فریم ورک 4.5 ایجاد نمایید.
- از پنجره NuGet Package manager با دستور install کتابخانه ASP.NET Web Forms Scaffold Generator را دریافت نمایید
install-package Microsoft.AspNet.Scaffolding.WebForms -pre
- کلاس Person را مانند زیر در فولدر Models ایحاد نماییدویژگی ScaffoldColumn را برای ID، برابر false قرار دهید تا از ایجاد این ستون جلوگیری نمائید.
public class Person { [ScaffoldColumn(false)] public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
- پروژه را Build نمایید.
- بر روی پروژه راست کلیک و از گزینه Add، گزینه ...Scaffold را انتخاب نمایید.
- از پنجره Add Scaffold باز شده بر روی گزینه Add، کلیک کنید.
- پنجره
Add Web Forms Pages مانند زیر باز میشود که امکان انتخاب کلاس،Data Context و MasterPage فراهم میباشد.
- از گزینه Data Context class گزینه New Data Context را انتخاب نمایید. صفحات مورد نیاز را در فولدر Views/Person ایجاد مینمایید.
- کدهای تولید شده را میتوانید بازبینی نمایید پروژه را اجرا تا خروجی کار را مشاهده نمایید.
namespace EntitySample1.DomainClasses { public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public virtual PersonInfo PersonInfo { get; set; } public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; } public virtual ICollection<Address> Addresses { get; set; } } }
namespace EntitySample1.DomainClasses { public class PersonInfo { public int Id { get; set; } public string Note { get; set; } public string Major { get; set; } } }
namespace EntitySample1.DomainClasses { public enum PhoneType { Home, Mobile, Work } public class PhoneNumber { public int Id { get; set; } public string Number { get; set; } public PhoneType PhoneType { get; set; } public virtual Person Person { get; set; } } }
namespace EntitySample1.DomainClasses { public class Address { public int Id { get; set; } public string City { get; set; } public string Street { get; set; } public virtual ICollection<Person> Persons { get; set; } } }
namespace EntitySample1.DataLayer { public class PhoneBookDbContext : DbContext { public DbSet<Person> Persons { get; set; } public DbSet<PhoneNumber> PhoneNumbers { get; set; } public DbSet<Address> Addresses { get; set; } } }
استفاده از ردیابی تغییر عکس فوری
ردیابی تغییر عکس فوری، وابسته به این است که EF بفهمد، چه زمانی تغییرات رخ داده است. رفتار پیش فرض DbContext API ، این هست که به صورت خودکار بازرسی لازم را در نتیجهی رخدادهای DbContext انجام دهد. DetectChanges تنها اطلاعات مدیریت حالت context، که وظیفهی انعکاس تغییرات صورت گرفته به پایگاه داده را دارد، به روز نمیکند، بلکه اصلاح رابطه(ralationship) ترکیبی از خواص راهبری مرجع ، مجموعه ای و کلیدهای خارجی را انجام میدهد. این خیلی مهم خواهد بود که درک روشنی داشته باشیم از این که چگونه و چه زمانی تغییرات تشخیص داده میشوند،چه چیزی باید از آن انتظار داشته باشیم و چگونه کنترلش کنیم.
چه زمانی تشخیص خودکار تغییرات اجرا میشود؟
متد DetectChanges کلاس ObjectContext، از EF نسخهی 4 به عنوان بخشی از الگوی ردیابی تغییر عکس فوری اشیای POCO ،در دسترس بوده است. تفاوتی که در مورد DataContext.ChangeTracker.DetectChanges( در حقیقت ObjectContext.DetectChanges فراخوانی میشود) وجود دارد این است که، رویدادهای خیلی بیشتری وجود دارند که به صورت خودکار DetectChanges را فراخوانی میکنند.
لیستی از متدهایی که باعث انجام عمل تشخیص تغییرات (DetectChanges)، میشوند را در ادامه مشاهده میکنید:
• DbSet.Add
• DbSet.Find
• DbSet.Remove
• DbSet.Local
• DbSet.SaveChanges
• فراخوانی Linq Query از DbSet
• DbSet.Attach
• DbContext.GetValidationErrors
• DbContext.Entry
• DbChangeTracker.Entries
کنترل زمان فراخوانی DetectChanges
بیشترین زمانی که EF احتیاج به فهمیدن تغییرات دارد، در زمان SaveChanges است، اما حالتهای زیاد دیگری نیز هست. برای مثال، اگر ما از ردیاب تغییرات، درخواست وضعیت فعلی یک شی را بکنیم،EF احتیاج به اسکن کردن و بررسی تغییرات رخ داده را دارد. همچنین وضعیتی را در نظر بگیرید که شما از پایگاه داده یک شماره تلفن را واکشی میکنید و سپس آن را به مجموعه شماره تلفنهای یک شخص جدید اضافه میکنید.آن شماره تلفن اکنون تغییر کرده است، چرا که انتساب آن به یک شخص جدید،خاصیت PersonId آن را تغییر داده است. ولی EF برای اینکه بفهمد تغییر رخ داده است(یا حتی نداده است) ، احتیاج به اسکن کردن همهی اشیا Person دارد.
بیشتر عملیاتی که بر روی DbContext API انجام میدهید، موجب فراخوانی DetectChanges میشود. در بیشتر موارد DetectChanges به اندازه کافی سریع هست تا باعث ایجاد مشکل کارایی نشود. با این حال ممکن است ، شما تعداد خیلی زیادی اشیا در حافظه داشته باشید، و یا تعداد زیادی عملیات در DbContext ، در مدت خیلی کوتاهی انجام دهید، رفتار تشخیص خودکار تغییرات ممکن است، باعث نگرانیهای کارایی شود. خوشبختانه گزینه ای برای خاموش کردن رفتار تشخیص خودکار تغییرات وجود دارد و هر زمانی که میدانید لازم است، میتوانید آن را به صورت دستی فراخوانی کنید.
EF بر مبنای این فرض ساخته شده است که شما ، در صورتی که در فراخوانی آخرین API، موجودیتی تغییر پیدا کرده است، قبل از فراخوانی API جدید، باید DetectChanges صدا زده شود. این شامل فراخوانی DetectChanges، قبل از اجرای هر query نیز میشود.اگر این عمل ناموفق یا نابجا انجام شود،ممکن است عواقب غیر منتظره ای در بر داشته باشد. DbContext انجام این وظیفه را بر عهده گرفته است و به همین دلیل به طور پیش فرض تشخیص تغییرات خودکار آن فعال است.
نکته: تشخیص اینکه چه زمانی احتیاج به فراخوانی DetectChanges است،آن طور که ساده و بدیهی به نظر میآید نیست. تیم EF شدیدا توصیه کرده اند که فقط، وقتی با مشکلات عدم کارایی روبرو شدید، تشخیص تغییرات را به حالت دستی در بیاورید.همچنین توصیه شده که در چنین مواقعی، تشخیص خودکار تغییرات را فقط برای قسمتی از کد که با کارایی پایین مواجه شدید خاموش کنید و پس از اینکه اجرای آن قسمت از کد تمام شد،دوباره آن را روشن کنید.
برای خاموش یا روشن کردن تشخیص خودکار تغییرات، باید متغیر بولین DbContext.Configuration.AutoDetectChangesEnabled را تنظیم کنید.
در مثال زیر، ما در متد ManualDetectChanges، تشخیص خودکار تغییرات را خاموش کرده ایم و تاثیرات آن را بررسی کرده ایم.
private static void ManualDetectChanges() { using (var context = new PhoneBookDbContext()) { context.Configuration.AutoDetectChangesEnabled = false; // turn off Auto Detect Changes var p1 = context.Persons.Single(p => p.FirstName == "joe"); p1.LastName = "Brown"; Console.WriteLine("Before DetectChanges: {0}", context.Entry(p1).State); context.ChangeTracker.DetectChanges(); // call detect changes manually Console.WriteLine("After DetectChanges: {0}", context.Entry(p1).State); } }
در کدهای بالا ابتدا تشخیص خودکار تغییرات را خاموش کرده ایم و سپس یک شخص با نام joe را از دیتابیس فراخواندیم و سپس نام خانوادگی آن را به Brown تغییر دادیم. سپس در خط بعد، وضعیت فعلی موجودیت p1 را از context جاری پرسیدیم. در خط بعدی، DetectChanges را به صورت دستی صدا زده ایم و دوباره همان پروسه را برای به دست آوردن وضیعت شی p1، انجام داده ایم. همان طور که میبینید ، برای به دست آوردن وضعیت فعلی شی مورد نظر از متد Entry متعلق به ChangeTracker API استفاده میکنیم، که در آینده مفصل در مورد آن بحث خواهد شد. اگر شما متد Main را با صدا زدن ManualDetectChanges ویرایش کنید ، خروجی زیر را مشاهده خواهید کرد:
Before DetectChanges: Unchanged After DetectChanges: Modified
همان طور که انتظار میرفت، به دلیل خاموش کردن تشخیص خودکار تغییرات، context قادر به تشخیص تغییرات صورت گرفته در شی p1 نیست، تا زمانی که متد DetectChanges را به صورت دستی صدا بزنیم. دلیل این که در دفعه اول، ما نتیجهی غلطی مشاهده میکنیم، این است که ما قانون را نقض کرده ایم و قبل از صدا زدن هر API ، متد DetectChanges را صدا نزده ایم. خوشبختانه چون ما در اینجا وضعیت یک شی را بررسی کردیم، با عوارض جانبی آن روبرو نشدیم.
نکته: به این نکته توجه داشته باشید که متد Entry به صورت خودکار، DetectChanges را فراخوانی میکند. برای اینکه دانسته بخواهیم این رفتار را غیر فعال کنیم، باید AutoDetectChangesEnabled را غیر فعال کنیم.
در مثال فوق ،خاموش کردن تشخیص خودکار تغییرات، برای ما مزیتی به همراه نداشت و حتی ممکن بود برای ما دردسر ساز شود. ولی حالتی را در نظر بگیرید که ما یک سری API را فراخوانی میکنیم ،بدون این که در این بین ،در حالت اشیا تغییری ایجاد کنیم.در نتیجه میتوانیم از فراخوانیهای بی جهت DetectChanges جلوگیری کنیم.
در متد AddMultiplePersons مثال بعدی، این کار را نشان داده ام:
private static void AddMultiplePerson() { using (var context = new PhoneBookDbContext()) { context.Configuration.AutoDetectChangesEnabled = false; context.Persons.Add(new Person { FirstName = "brad", LastName = "watson", BirthDate = new DateTime(1990, 6, 8) }); context.Persons.Add(new Person { FirstName = "david", LastName = "brown", BirthDate = new DateTime(1990, 6, 8) }); context.Persons.Add(new Person { FirstName = "will", LastName = "smith", BirthDate = new DateTime(1990, 6, 8) }); context.SaveChanges(); } }
استفاده از DetectChanges برای فراخوانی اصلاح رابطه
DetectChanges همچنین مسئولیت انجام اصلاح رابطه ، برای هر رابطه ای که تشخیص دهد تغییر کرده است را دارد.اگر شما بعضی از روابط را تغییر دادید و مایل بودید تا همهی خواص راهبری و خواص کلید خارجی را منطبق کنید، DetectChanges این کار را برای شما انجام میدهد. این قابلیت میتواند برای سناریوهای data-binding که در آن ممکن است در رابط کاربری(UI) یکی از خواص راهبری (یا حتی یک کلید خارجی) تغییر کند، و شما بخواهید که خواص دیگری این رابطه به روز شوند و تغییرات را نشان دهند، مفید واقع شود.
متد DetectRelationshipChanges در مثال زیر از DetectChanges برای انجام اصلاح رابطه استفاده میکند.
private static void DetectRelationshipChanges() { using (var context = new PhoneBookDbContext()) { var phone1 = context.PhoneNumbers.Single(x => x.Number == "09351234567"); var person1 = context.Persons.Single(x => x.FirstName == "will"); person1.PhoneNumbers.Add(phone1); Console.WriteLine("Before DetectChanges: {0}", phone1.Person.FirstName); context.ChangeTracker.DetectChanges(); // ralationships fix-up Console.WriteLine("After DetectChanges: {0}", phone1.Person.FirstName); } }
در اینجا ابتدا ما شماره تلفنی را از دیتابیس لود میکنیم. سپس شخص دیگری را نیز با نام will از دیتابیس میخوانیم. قصد داریم شماره تلفن خوانده شده را به این شخص نسبت دهیم و مجموعه شماره تلفنهای وی اضافه کنیم و ما این کار را با افزودن phone1 به مجموعه شماره تلفنهای person1 انجام داده ایم. چون ما از اشیای POCO استفاده کرده ایم،EF نمیفهمد که ما این تغییر را ایجاد کرده ایم و در نتیجه کلید خارجی PersonId شی phone1 را اصلاح نمیکند. ما میتوانیم تا زمانی صبر کنیم تا متدی مثل SaveChanges، متد DetectChanges را فراخوانی کند،ولی اگر بخواهیم این عمل در همان لحظه انجام شود، میتوانیم DetectChanges را دستی صدا بزنیم.
اگر ما متد Main را با اضافه کردن فرخوانی DetectRealtionShipsChanges تغییر بدهیم و آن را اجرا کنیم، نتیجه زیر را مشاهده میکنید:
Before DetectChanges: david After DetectChanges: will
تا قبل از فراخوانی تشخیص تغییرات(DetectChanegs)، هنوز phone1 منتسب به شخص قدیمی(david) بوده، ولی پس از فراخوانی DetectChanges ، اصلاح رابطه رخ داده و همه چیز با یکدیگر منطبق میشوند.
فعال سازی و کار با پروکسیهای ردیابی تغییر
اگر پروفایلر کارایی شما، فراخوانیهای بیش از اندازه DetectChnages را به عنوان یک مشکل شناسایی کند، و یا شما ترجیح میدهید که اصلاح رابطه به صورت بلادرنگ صورت گیرد ، ردیابی تغییر پروکسیهای پویا، به عنوان گزینه ای دیگر مطرح میشود.فقط با چند تغییر کوچک در کلاسهای POCO، EF قادر به ساخت پروکسیهای پویا خواهد بود.پروکسیهای ردیابی تغییر به EF اجازه ردیابی تغییرات در همان لحظه ای که ما تغییری در اشیای خود میدهیم را میدهند و همچنین امکان انجام اصلاح رابطه را در هر زمانی که تغییرات روابط را تشخیص دهد، دارد.
برای اینکه پروکسی ردیابی تغییر بتواند ساخته شود، باید قوانین زیر رعایت شود:
• کلاس باید public باشد و seald نباشد.
• همهی خواص(properties) باید virtual تعریف شوند.
• همهی خواص باید getter و setter با سطح دسترسی public داشته باشند.
• همهی خواص راهبری مجموعه ای باید نوعشان، از نوع ICollection<T> تعریف شوند.
کلاس Person مثال خود را به گونه ای بازنویسی کرده ایم که تمام قوانین فوق را پیاده سازی کرده باشد.
نکته: توجه داشته باشید که ما دیگر در داخل سازنده کلاس ،کدی نمینویسیم و منطقی که باعث نمونه سازی اولیه خواص راهبری میشدند، را پیاده سازی نمیکنیم. این پروکسی ردیاب تغییر، همهی خواص راهبری مجموعه ای را تحریف کرده و ار نوع مجموعه ای مخصوص خود(EntityCollection<T>) استفاده میکند. این نوع مجموعه ای، هر تغییری که در این مجموعه صورت میگیرد را زیر نظر گرفته و به ردیاب تغییر گزارش میدهد. اگر تلاش کنید تا نوع دیگری مانند List<T> که معمولا در سازنده کلاس از آن استفاده میکردیم را به آن انتساب دهیم، پروکسی، استثنایی را پرتاب میکند.
namespace EntitySample1.DomainClasses { public class Person { public virtual int Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual DateTime BirthDate { get; set; } public virtual PersonInfo PersonInfo { get; set; } public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; } public virtual ICollection<Address> Addresses { get; set; } } }
با این که احتیاجات رسیدن به پروکسیهای ردیابی تغییر خیلی ساده هستند، اما سادهتر از آن ها، فراموش کردن یکی از آن هاست.حتی از این هم سادهتر میشود که در آینده تغییری در آن کلاسها ایجاد کنید و ناخواسته یکی از آن قوانین را نقض کنید.به این خاطر، فکر خوبیست که یک آزمون واحد نیز اضافه کنیم تا مطمئن شویم که EF توانسته، پروکسی ردیابی تغییر را ایجاد کند یا نه.
در مثال زیر یک متد نوشته شده که این مورد را مورد آزمایش قرار میدهد. همچنین فراموش نکنید که فضای نام System.Data.Object.DataClasses را به usingهای خود اضافه کنید.
private static void TestForChangeTrackingProxy() { using (var context = new PhoneBookDbContext()) { var person = context.Persons.First(); var isProxy = person is IEntityWithChangeTracker; Console.WriteLine("person is a proxy: {0}", isProxy); } }
اکنون متد ManualDetectChanges را که کمی بالاتر بررسی کرده ایم را در نظر بگیرید و کد context.ChangeTracker.DetectChanges آن را حذف کنید و بار دیگر آن را فرا بخوانید و نتیجه را مشاهده کنید:
Before DetectChanges: Modified After DetectChanges: Modified
اکنون متد DetectRelationshipChanges را ویرایش کرده و برنامه را اجرا کنید:
Before DetectChanges: will After DetectChanges: will
نکته: زمانی که شما از پروکسیهای ردیابی تغییر استفاده میکنید،احتیاجی به غیرفعال کردن تشخیص خودکار تغییرات نیست. DetectChanges برای همه اشیایی که تغییرات را به صورت بلادرنگ گزارش میدهند،فرآیند تشخیص تغییرات را انجام نمیدهد. بنابراین فعال سازی پروکسیهای ردیابی تغییر،برای رسیدن به مزایای کارایی بالا در هنگام عدم استفاده از DetectChanges کافی است. در حقیقت زمانی که EF، یک پروکسی ردیابی پیدا میکند، از مقادیر خاصیت ها، عکس فوری نمیگیرد. همچنین DetectChanges این را نیز میداند که نباید تغییرات موجودیت هایی که عکسی از مقادیر اصلی آنها ندارد را اسکن کند.
تذکر: اگر شما موجودیت هایی داشته باشید که شامل انواع پیچیده(Complex Types) میشوند،EF هنوز هم از ردیابی تغییر عکس فوری، برای خواص موجود در نوع پیچیده استفاده میکند، و از این جهت لازم است کهEF، برای نمونهی نوع پیچیده، پروکسی ایجاد نمیکند.شما هنوز هم، تشخیص خودکار تغییرات خواصی که مستقیما درون آن موجودیت(Entity) تعریف شده اند را دارید، ولی تغییرات رخ داده درون نوع پیچیده، فقط از طریق DetectChanges قابل تشخیص است.
چگونگی اطمینان از اینکه نمونههای جدید ، پروکسیها را دریافت خواهند کرد
EF به صورت خودکار برای نتایج حاصل از کوئری هایی که شما اجرا میکنید، پروکسیها را ایجاد میکند. با این حال اگر شما فقط از سازندهی کلاس POCO خود برای ایجاد نمونهی جدید استفاده کنید،دیگر پروکسیها ایجاد نخواهند شد.بدین منظور برای دریافت پروکسی ها، شما باید از متد DbSet.Create برای دریافت نمونههای جدید آن موجودیت استفاده کنید.
نکته: اگر شما، پروکسیهای ردیابی تغییر را برای موجودیتی از مدلتان فعال کرده باشید،هنوز هم میتوانید،نمونههای فاقد پروکسی آن موجودیت را ایجاد و بیافزایید.خوشبختانه EF با موجودیتهای پروکسی و غیر پروکسی در همان مجموعه(set) کار میکند.شما باید آگاه باشید که ردیابی خودکار تغییرات و یا اصلاح رابطه، برای نمونه هایی که پروکسی هایی ردیابی تغییر نیستند، قابل استفاده نیستند.داشتن مخلوطی از نمونههای پروکسی و غیر پروکسی در همان مجموعه، میتواند گیج کننده باشد.بنابر این عموما توصیه میشود که برای ایجاد نمونههای جدید از DbSet.Create استفاده کنید، تا همهی موجودیتهای موجود در مجموعه، پروکسیهای ردیابی تغییر باشند.
متد CreateNewProxies را به برنامهی خود اضافه کرده و آن را اجرا کنید.
private static void CreateNewProxies() { using (var context = new PhoneBookDbContext()) { var phoneNumber = new PhoneNumber { Number = "987" }; var davidPersonProxy = context.Persons.Create(); davidPersonProxy.FirstName = "david"; davidPersonProxy.PhoneNumbers.Add(phoneNumber); Console.WriteLine(phoneNumber.Person.FirstName); } }
خروجی مثال فوق david خواهد بود.همان طور که میبینید با استفاده از context.Persons.Create، نمونهی ساخته شده، دیگر شی POCO نیست، بلکه davidPersonProxy، از جنس پروکسی ردیابی تغییر است و تغییرات آن به طور خودکار ردیابی شده و رابطه آن نیز به صورت خودکار اصلاح میشود.در اینجا نیز با افزودن phoneNumber به شماره تلفنهای davidPersonProxy، به طور خودکار رابطهی بین phoneNumber و davidPersonPeroxy بر قرار شده است. همان طور که میدانید این عملیات بدون استفاده از پروکسیهای ردیابی تغییرات امکان پذیر نیست و موجب بروز خطا میشود.
ایجاد نمونههای پروکسی برای انواع مشتق شده
اورلود جنریک دیگری برای DbSet.Create وجود دارد که برای نمونه سازی کلاسهای مشتق شده در مجموعه ما استفاده میشود .برای مثال، فراخوانی Create بر روی مجموعهی Persons ،نمونه ای از کلاس Person را بر میگرداند.ولی ممکن است کلاس هایی در مجموعهی Persons وجود داشته باشند، که از آن مشتق شده باشند، مانند Student. برای دریافت نمونهی پروکسی Student، از اورلود جنریک Create استفاده میکنیم.
var newStudent = context.Persons.Create<Student>();
تا به این جای کار باید متوجه شده باشید که ردیابی تغییرات، فرآیندی ساده و بدیهی نیست و مقداری سربار در کار است. در بعضی از بخشهای برنامه تان، احتمالا دادهها را به صورت فقط خواندنی در اختیار کاربران قرار میدهید و چون اطلاعات هیچ وقت تغییر نمیکنند، شما میخواهید که سربار ناشی از ردیابی تغییرات را حذف کنید.
خوشبختانه EF شامل متد AsNoTracking است که میتوان از آن برای اجرای کوئریهای بدون ردیابی استفاده کرد.یک کوئری بدون ردیابی، یک کوئری ساده هست که نتایج آن توسط context برای تشخیص تغییرات ردیابی نخواهد شد.
متد PrintPersonsWithoutChangeTracking را به برنامه اضافه کنید و آن را اجرا کنید:
private static void PrintPersonsWithoutChangeTracking() { using (var context = new PhoneBookDbContext()) { var persons = context.Persons.AsNoTracking().ToList(); foreach (var person in persons) { Console.WriteLine(person.FirstName); } } }
نکته: واکشی دادهها بدون ردیابی تغییرات،معمولا وقتی باعث افزایش قابل توجه کارایی میشود که بخواهیم تعداد خیلی زیادی داده را به صورت فقط خواندنی نمایش دهیم. اگر برنامهی شما داده ای را تغییر میدهد و میخواهد آن را ذخیره کند، باید از AsNoTracking استفاده نکنید.
AsNoTracking یک متد الحاقی است، که در <IQueryable<T تعریف شده است، در نتیجه شما میتوانید از آن، در کوئریهای LINQ نیز استفاده کنید. شما میتوانید از AsNoTracking، در انتهای DbSet ،در خط from کوئری استفاده کنید.
var query = from p in context.Persons.AsNoTracking() where p.FirstName == "joe" select p;
شما همچنین از AsNoTracking میتوانید برای تبدیل یک کوئری LINQ موجود، به یک کوئری فاقد ردیابی استفاده کنید. این نکته را به یاد داشته باشید که فقط AsNoTracking بر روی کوئری، فرانخوانده شده است، بلکه متغیر query را با نتیجهی حاصل از فراخوانی AsNoTracking بازنویسی(override) کرده است و این، از این جهت لازم است که AsNoTracking ،تغییری در کوئری ای که بر روی آن فراخوانده شده نمیدهد، بلکه یک کوئری جدید بر میگرداند.
var query = from p in context.Persons where p.FirstName == "joe" select p; query = query.AsNoTracking();
منبع: ترجمه ای آزاد از کتاب Programming Entity Framework: DbContext
فعال سازی عملیات CRUD در Kendo UI Grid
using System.Linq; using System.Net; using System.Net.Http; using System.Web.Mvc; using Kendo.DynamicLinq; using KendoUI06Mvc.Models; using Newtonsoft.Json; namespace KendoUI06Mvc.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); // shows the page. } [HttpDelete] public ActionResult DeleteProduct(int id) { var item = ProductDataSource.LatestProducts.FirstOrDefault(x => x.Id == id); if (item == null) return new HttpNotFoundResult(); ProductDataSource.LatestProducts.Remove(item); return Json(item); } [HttpGet] public ActionResult GetProducts() { var request = JsonConvert.DeserializeObject<DataSourceRequest>( this.Request.Url.ParseQueryString().GetKey(0) ); var list = ProductDataSource.LatestProducts; return Json(list.AsQueryable() .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter), JsonRequestBehavior.AllowGet); } [HttpPost] public ActionResult PostProduct(Product product) { if (!ModelState.IsValid) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var id = 1; var lastItem = ProductDataSource.LatestProducts.LastOrDefault(); if (lastItem != null) { id = lastItem.Id + 1; } product.Id = id; ProductDataSource.LatestProducts.Add(product); // گرید آی دی جدید را به این صورت دریافت میکند return Json(new DataSourceResult { Data = new[] { product } }); } [HttpPut] // Add it to fix this error: The requested resource does not support http method 'PUT' public ActionResult UpdateProduct(int id, Product product) { var item = ProductDataSource.LatestProducts .Select( (prod, index) => new { Item = prod, Index = index }) .FirstOrDefault(x => x.Item.Id == id); if (item == null) return new HttpNotFoundResult(); if (!ModelState.IsValid || id != product.Id) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); ProductDataSource.LatestProducts[item.Index] = product; //Return HttpStatusCode.OK return new HttpStatusCodeResult(HttpStatusCode.OK); } } }
var productsDataSource = new kendo.data.DataSource({ transport: { read: { url: "@Url.Action("GetProducts","Home")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' }, create: { url: "@Url.Action("PostProduct","Home")", contentType: 'application/json; charset=utf-8', type: "POST" }, update: { url: function (product) { return "@Url.Action("UpdateProduct","Home")/" + product.Id; }, contentType: 'application/json; charset=utf-8', type: "PUT" }, destroy: { url: function (product) { return "@Url.Action("DeleteProduct","Home")/" + product.Id; }, contentType: 'application/json; charset=utf-8', type: "DELETE" }, parameterMap: function (options) { return kendo.stringify(options); } },
مدلها و تنظیمات برنامه
مدلها و تنظیمات مورد استفادهی در مثال جاری، با مدلهای مطلب «لغو Lazy Loading در حین کار با AutoMapper و Entity Framework» یکی است. فقط ViewModel مورد استفاده اینبار یکچنین ساختاری را دارد:
public class UserViewModel { public int Id { set; get; } public string CustomName { set; get; } public int PostsCount { set; get; } }
- خاصیت CustomName از جمع نام و سن شخص تشکیل شود.
- خاصیت PostsCount بیانگر جمع مطالب ارسالی آن شخص باشد.
نگاشتهای AutoMapper میتوانند حاوی توابع تجمعی نیز باشند
برای حل مسالهی فوق تنها کافی است نگاشت ذیل را تهیه کنیم:
public class TestProfile : Profile { protected override void Configure() { this.CreateMap<User, UserViewModel>() .ForMember(dest => dest.CustomName, opt => opt.MapFrom(src => src.Name + "[" + src.Age + "]")) .ForMember(dest => dest.PostsCount, opt => opt.MapFrom(src => src.BlogPosts.Count())); } public override string ProfileName { get { return this.GetType().Name; } } }
کوئری نهایی استفاده کننده از تنظیمات نگاشت تهیه شده
در ادامه متدهای Project To را جهت استفادهی از تنظیمات نگاشت فوق بکار میگیریم:
using (var context = new MyContext()) { var user1 = context.Users .Project() .To<UserViewModel>() .FirstOrDefault(); if (user1 != null) { Console.Write(user1.CustomName); Console.Write(user1.PostsCount); } }
SELECT [Limit1].[Id] AS [Id], [Limit1].[C1] AS [C1], [Limit1].[C2] AS [C2] FROM ( SELECT TOP (1) [Project1].[Id] AS [Id], CASE WHEN ([Project1].[Name] IS NULL) THEN N'' ELSE [Project1].[Name] END + N'[' + CAST( [Project1].[Age] AS nvarchar(max)) + N']' AS [C1], [Project1].[C1] AS [C2] FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Age] AS [Age], (SELECT COUNT(1) AS [A1] FROM [dbo].[BlogPosts] AS [Extent2] WHERE [Extent1].[Id] = [Extent2].[UserId]) AS [C1] FROM [dbo].[Users] AS [Extent1] ) AS [Project1] ) AS [Limit1]
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
public static readonly Func<object, DateTimeOffset?> EFPropertyCreatedDateTime = entity => EF.Property<DateTimeOffset?>(entity, CreatedDateTime);
var persons = context.Persons .Where(x => AuditableShadowProperties.EFPropertyCreatedDateTime(x) == DateTimeOffset.UtcNow) .ToList();
در حالت متصل مانند برنامههای متداول دسکتاپ، Context مورد استفاده در طول عمر صفحهی جاری زنده نگه داشته میشود. در این حالت اگر شیءایی اضافه شود، حذف شود یا تغییر کند، توسط EF ردیابی شده و تنها با فراخوانی متد SaveChanges، تمام این تغییرات به صورت یکجا به بانک اطلاعاتی اعمال میشوند.
در حالت غیرمتصل مانند برنامههای وب، طول عمر Context در حد طول عمر یک درخواست است. پس از آن از بین خواهد رفت و دیگر فرصت ردیابی تغییرات سمت کاربر را نخواهد یافت. در این حالت به روز رسانی کلیه تغییرات انجام شده در خواص و همچنین ارتباطات اشیاء موجود، کاری مشکل و زمانبر خواهد بود.
برای حل این مشکل، کتابخانهای به نام GraphDiff طراحی شدهاست که صرفا با فراخوانی متد UpdateGraph آن، به صورت خودکار، محاسبات تغییرات صورت گرفته در اشیاء منقطع و اعمال آنها به بانک اطلاعاتی صورت خواهد گرفت. البته ذکر متد SaveChanges پس از آن نباید فراموش شود.
اصطلاحات بکار رفته در GraphDiff
برای کار با GraphDiff نیاز است با یک سری اصطلاح آشنا بود:
Aggregate root
گرافی است از اشیاء به هم وابسته که مرجع تغییرات دادهها به شمار میرود. برای مثال یک سفارش و آیتمهای آنرا درنظر بگیرید. بارگذاری آیتمهای سفارش، بدون سفارش معنایی ندارند. بنابراین در اینجا سفارش aggregate root است.
AssociatedCollection/AssociatedEntity
حالتهای Associated به GraphDiff اعلام میکنند که اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root نباید به روز رسانی شوند. در این حالت تنها ارجاعات به روز رسانی خواهند شد.
اگر خاصیت راهبری از نوع ICollection است، حالت AssociatedCollection و اگر صرفا یک شیء ساده است، از AssociatedEntity استفاده خواهد شد.
OwnedCollection/OwnedEntity
حالتهای Owned به GraphDiff اعلام میکنند که جزئیات و همچنین ارجاعات اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root باید به روز رسانی شوند.
دریافت و نصب GraphDiff
برای نصب خودکار کتابخانهی GraphDiff میتوان از دستور نیوگت ذیل استفاده کرد:
PM> Install-Package RefactorThis.GraphDiff
بررسی GraphDiff در طی یک مثال
مدلهای برنامه آزمایشی، از سه کلاس ذیل که روابط many-to-many و one-to-many با یکدیگر دارند، تشکیل شدهاست:
using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; namespace GraphDiffTests.Models { public class BlogPost { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } public virtual ICollection<Tag> Tags { set; get; } // many-to-many [ForeignKey("UserId")] public virtual User User { get; set; } public int UserId { get; set; } public BlogPost() { Tags = new List<Tag>(); } } public class Tag { public int Id { set; get; } [StringLength(maximumLength: 450), Required] public string Name { set; get; } public virtual ICollection<BlogPost> BlogPosts { set; get; } // many-to-many public Tag() { BlogPosts = new List<BlogPost>(); } } public class User { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<BlogPost> BlogPosts { get; set; } // one-to-many } }
- هر کاربر میتواند چندین مطلب ارسال کند.
در این حالت، Context برنامه چنین شکلی را خواهد یافت:
using System; using System.Data.Entity; using GraphDiffTests.Models; namespace GraphDiffTests.Config { public class MyContext : DbContext { public DbSet<User> Users { get; set; } public DbSet<BlogPost> BlogPosts { get; set; } public DbSet<Tag> Tags { get; set; } public MyContext() : base("Connection1") { this.Database.Log = sql => Console.Write(sql); } } }
using System.Data.Entity.Migrations; using System.Linq; using GraphDiffTests.Models; namespace GraphDiffTests.Config { public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { if(context.Users.Any()) return; var user1 = new User {Name = "User 1"}; context.Users.Add(user1); var tag1 = new Tag { Name = "Tag1" }; context.Tags.Add(tag1); var post1 = new BlogPost { Title = "Title...1", Content = "Content...1", User = user1}; context.BlogPosts.Add(post1); post1.Tags.Add(tag1); base.Seed(context); } } }
در این تصاویر به Id هر کدام از رکوردها دقت کنید. از آنها در ادامه استفاده خواهیم کرد.
در اینجا نمونهای از نحوهی استفاده از GraphDiff را جهت به روز رسانی یک Aggregate root ملاحظه میکنید:
using (var context = new MyContext()) { var user1 = new User { Id = 1, Name = "User 1_1_1" }; var post1 = new BlogPost { Id = 1, Title = "Title...1_1", Content = "Body...1_1", User = user1, UserId = user1.Id }; var tags = new List<Tag> { new Tag {Id = 1, Name = "Tag1_1"}, new Tag {Id=12, Name = "Tag2_1"}, new Tag {Name = "Tag3"}, new Tag {Name = "Tag4"}, }; tags.ForEach(tag => post1.Tags.Add(tag)); context.UpdateGraph(post1, map => map .OwnedEntity(p => p.User) .OwnedCollection(p => p.Tags) ); context.SaveChanges(); }
پارامتر دوم آن، همان مباحث Owned و Associated بحث شده در ابتدای مطلب را مشخص میکنند. در اینجا چون میخواهیم هم برچسبها و هم اطلاعات کاربر مطلب اول به روز شوند، نوع رابطه را Owned تعریف کردهایم.
در حین کار با متد UpdateGraph، ذکر Idهای اشیاء منقطع از Context بسیار مهم هستند. اگر دستورات فوق را اجرا کنیم به خروجی ذیل خواهیم رسید:
- همانطور که مشخص است، چون id کاربر ذکر شده و همچنین این Id در post1 نیز درج گردیده است، صرفا نام او ویرایش گردیده است. اگر یکی از موارد ذکر شده رعایت نشوند، ابتدا کاربر جدیدی ثبت شده و سپس رابطهی مطلب و کاربر به روز رسانی خواهد شد (userId آن به userId آخرین کاربر ثبت شده تنظیم میشود).
- در حین ثبت برچسبها، چون Id=1 از پیش در بانک اطلاعاتی موجود بوده، تنها نام آن ویرایش شدهاست. در سایر موارد، برچسبهای تعریف شده صرفا اضافه شدهاند (چون Id مشخصی ندارند یا Id=12 در بانک اطلاعاتی وجود خارجی ندارد).
- چون Id مطلب مشخص شدهاست، فیلدهای عنوان و محتوای آن نیز به صورت خودکار ویرایش شدهاند.
و ... تمام این کارها صرفا با فراخوانی متدهای UpdateGraph و سپس SaveChanges رخ دادهاست.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
GraphDiffTests.zip
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت سوم - تکمیل مستندات یک API با کامنتها
استفاده از XML Comments برای بهبود کیفیت مستندات API
نوشتن توضیحات XML ای برای متدها و پارامترها در پروژههای داتنتی، روشی استاندارد و شناخته شدهاست. برای نمونه در AuthorsController، میخواهیم توضیحاتی را به اکشن متد GetAuthor آن اضافه کنیم:
/// <summary> /// Get an author by his/her id /// </summary> /// <param name="authorId">The id of the author you want to get</param> /// <returns>An ActionResult of type Author</returns> [HttpGet("{authorId}")] public async Task<ActionResult<Author>> GetAuthor(Guid authorId)
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.2</TargetFramework> <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup>
اکنون نیاز است وجود این فایل را به تنظیمات SwaggerDoc در کلاس Startup برنامه، اعلام کنیم:
namespace OpenAPISwaggerDoc.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSwaggerGen(setupAction => { setupAction.SwaggerDoc( // ... ); var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentsFile); setupAction.IncludeXmlComments(xmlCommentsFullPath); }); }
پس از این تنظیمات اگر برنامه را اجرا کنیم، در Swagger-UI حاصل، این تغییرات قابل مشاهده هستند:
افزودن توضیحات به Response
تا اینجا توضیحات پارامترها و متدها را افزودیم؛ اما response از نوع 200 آن هنوز فاقد توضیحات است:
علت را نیز در تصویر فوق مشاهده میکنید. قسمت responses در OpenAPI specification، اطلاعات خودش را از اسکیمای مدلهای مرتبط دریافت میکند. بنابراین نیاز است کلاس DTO متناظر با Author را به نحو ذیل تکمیل کنیم:
using System; namespace OpenAPISwaggerDoc.Models { /// <summary> /// An author with Id, FirstName and LastName fields /// </summary> public class Author { /// <summary> /// The id of the author /// </summary> public Guid Id { get; set; } /// <summary> /// The first name of the author /// </summary> public string FirstName { get; set; } /// <summary> /// The last name of the author /// </summary> public string LastName { get; set; } } }
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup> </Project>
namespace OpenAPISwaggerDoc.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSwaggerGen(setupAction => { setupAction.SwaggerDoc( // ... ); var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.TopDirectoryOnly).ToList(); xmlFiles.ForEach(xmlFile => setupAction.IncludeXmlComments(xmlFile)); }); }
در این حالت اگر مجددا برنامه را اجرا کنیم، خروجی ذیل را در قسمت schemas مشاهده خواهیم کرد:
بهبود مستندات به کمک Data Annotations
اگر به اکشن متد UpdateAuthor در کنترلر نویسندگان دقت کنیم، چنین امضایی را دارد:
[HttpPut("{authorId}")] public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
using System.ComponentModel.DataAnnotations; namespace OpenAPISwaggerDoc.Models { /// <summary> /// An author for update with FirstName and LastName fields /// </summary> public class AuthorForUpdate { /// <summary> /// The first name of the author /// </summary> [Required] [MaxLength(150)] public string FirstName { get; set; } /// <summary> /// The last name of the author /// </summary> [Required] [MaxLength(150)] public string LastName { get; set; } } }
بهبود مستندات متد HttpPatch با ارائهی یک مثال
دو نگارش از اکشن متد UpdateAuthor در این مثال موجود هستند:
یکی HttpPut است
[HttpPut("{authorId}")] public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
[HttpPatch("{authorId}")] public async Task<ActionResult<Author>> UpdateAuthor( Guid authorId, JsonPatchDocument<AuthorForUpdate> patchDocument)
بهتر است در این حالت مثالی را به استفاده کنندگان از آن ارائه دهیم تا در حین کار با آن، به مشکل برنخورند:
/// <summary> /// Partially update an author /// </summary> /// <param name="authorId">The id of the author you want to get</param> /// <param name="patchDocument">The set of operations to apply to the author</param> /// <returns>An ActionResult of type Author</returns> /// <remarks> /// Sample request (this request updates the author's first name) \ /// PATCH /authors/id \ /// [ \ /// { \ /// "op": "replace", \ /// "path": "/firstname", \ /// "value": "new first name" \ /// } \ /// ] \ /// </remarks> [HttpPatch("{authorId}")] public async Task<ActionResult<Author>> UpdateAuthor( Guid authorId, JsonPatchDocument<AuthorForUpdate> patchDocument)
روش کنترل warningهای کامنتهای تکمیل نشده
با فعالسازی GenerateDocumentationFile در فایل csproj برنامه، کامپایلر، بلافاصله برای تمام متدها و خواص عمومی که دارای کامنت نیستند، یک warning را صادر میکند. یک روش برطرف کردن این مشکل، افزودن کامنت به تمام قسمتهای برنامه است. روش دیگر آن، تکمیل خواص کامپایلر، جهت مواجه شدن با عدم وجود کامنتها در فایل csproj برنامه است:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.2</TargetFramework> <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> <GenerateDocumentationFile>true</GenerateDocumentationFile> <TreatWarningsAsErrors>false</TreatWarningsAsErrors> <WarningsAsErrors>NU1605;</WarningsAsErrors> <NoWarn>1701;1702;1591</NoWarn> </PropertyGroup>
- اگر میخواهید خودتان را مجبور به کامنت نویسی کنید، میتوانید نبود کامنتها را تبدیل به error کنید. برای این منظور خاصیت TreatWarningsAsErrors را به true تنظیم کنید. در این حالت هر کامنت نوشته نشده، به صورت یک error توسط کامپایلر گوشزد شده و برنامه کامپایل نخواهد شد.
- اگر TreatWarningsAsErrors را خاموش کردید، هنوز هم میتوانید یکسری از warningهای انتخابی را تبدیل به error کنید. برای مثال NU1605 ذکر شدهی در خاصیت WarningsAsErrors، مربوط به package downgrade detection warning است.
- اگر به warning نبود کامنتها دقت کنیم به صورت عبارات warning CS1591: Missing XML comment for publicly visible type or member شروع میشود. یعنی CS1591 مربوط به کامنتهای نوشته نشدهاست. میتوان برای صرفنظر کردن از آن، شمارهی این خطا را بدون CS، توسط خاصیت NoWarn ذکر کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: OpenAPISwaggerDoc-03.zip
در قسمت بعد، مشکل خروجی تولید response از نوع 200 را که در قسمت دوم به آن اشاره کردیم، بررسی خواهیم کرد.
اگر در یک پروژه EF Code first چندین Context وجود داشته باشد و دستور enable-migrations را بدون پارامتری فراخوانی کنیم، پیغام خطای More than one context type was found in the assmbly xyz را دریافت خواهیم کرد.
الف) اما در EF 6 میتوان با بکار بردن سوئیچ جدید ContextTypeName، به ازای هر Context، مهاجرت مرتبط با آنرا تنظیم نمود:
enable-migrations -ContextTypeName dbContextName1 -MigrationDirectory DataContexts\Name1Migrations
ب) در مرحله بعد، نیاز به فراخوانی دستور add-migration است:
add-migration -ConfigurationTypeName FullNameSpaceCtx1.Configuration "InitialCreate"
ذکر کامل فضای نام، از این جهت مهم است که کلاس Configuration به ازای Contextهای مختلف ایجاد شده، یک نام را خواهد داشت؛ اما در فضاهای نام متفاوتی قرار میگیرد.
با اجرای دستور add-migration، کدهای سی شارپ مورد نیاز جهت اعمال تغییرات بر روی ساختار بانک اطلاعاتی تولید میشوند. در مرحله بعد، این کدها تبدیل به دستورات SQL متناظری شده و بر روی بانک اطلاعاتی اجرا خواهند شد.
بدیهی است اگر دو Context در برنامه تعریف کرده باشید، دوبار باید دستور enable-migrations و دوبار دستور add-migration را با پارامترهای اشاره کننده به Conetxtهای مدنظر اجرا کرد.
ج) سپس برای اعمال این تغییرات، باید دستور update-database را اجرا کرد.
update-database -ConfigurationTypeName FullNameSpaceCtx1.Configuration
نهایتا اگر به بانک اطلاعاتی مراجعه کنید، تمام جداول و تعاریف را یکجا در همان بانک اطلاعاتی میتوانید مشاهده نمائید.
داشتن چندین Context در برنامه و مدیریت تراکنشها
در EF، هر DbContext معرف یک واحد کار است. یعنی تراکنشها و چندین عمل متوالی مرتبط انجام شده، درون یک DbContext معنا پیدا میکنند. متد SaveChanges نیز بر همین اساس است که کلیه اعمال ردیابی شده در طی یک واحد کار را در طی یک تراکنش به بانک اطلاعاتی اعمال میکند. همچنین مباحثی مانند lazy loading نیز در طی یک Context مفهوم دارند. به علاوه دیگر امکان join نویسی بین دو Context وجود نخواهد داشت. باید اطلاعات را از یکی واکشی و سپس این اطلاعات درون حافظهای را به دیگری ارسال کنید.
یک نکته
میتوان یک DbSet را در چندین Context تعریف کرد. یعنی اگر بحث join نویسی مطرح است، با تکرار تعریف DbSetها اینکار قابل انجام است اما این مساله اساس جداسازی Contextها را نیز زیر سؤال میبرد.
داشتن چندین Context در برنامه و مدیریت رشتههای اتصالی
در EF Code first روشهای مختلفی برای تعریف رشته اتصالی به بانک اطلاعاتی وجود دارند. اگر تغییر خاصی در کلاس مشتق شده از DbContext ایجاد نکنیم، نام کلید رشته اتصالی تعریف شده در فایل کانفیگ باید به نام کامل کلاس Context برنامه اشاره کند. اما با داشتن چندین Context به ازای یک دیتابیس میتوان از روش ذیل استفاده کرد:
public class Ctx1 : DbContext { public Ctx1() : base("DefaultConnection") { //Database.Log = sql => Debug.Write(sql); } } public class Ctx2 : DbContext { public Ctx2() : base("DefaultConnection") { //Database.Log = sql => Debug.Write(sql); } }
<connectionStrings> <add name="DefaultConnection" connectionString="…." providerName="System.Data.SqlClient" /> </connectionStrings>
چه زمانی بهتر است از چندین Context در برنامه استفاده کرد؟
عموما در طراحیهای سازمانی SQL Server، تمام جداول از schema مدیریتی به نام dbo استفاده نمیکنند. جداول فروش از schema خاص خود و جداول کاربران از schema دیگری استفاده خواهند کرد. با استفاده از چندین Context میتوان به ازای هر کدام از schemaهای متفاوت موجود، «یک ناحیه ایزوله» را ایجاد و مدیریت کرد.
public class Ctx2 : DbContext { public Ctx2() : base("DefaultConnection") { //Database.Log = sql => Debug.Write(sql); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema("sales"); base.OnModelCreating(modelBuilder); } }