import { Directive, TemplateRef, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[myIf]', inputs:["myIf"] }) export class UnlessDirective { constructor(private _templateRef:TemplateRef<any>,private _viewContainerRef:ViewContainerRef) { } set myIf(condition:true) { if(!condition) { this._viewContainerRef.clear(); } else{ this._viewContainerRef.createEmbeddedView(this._templateRef); } } }
سپس ورودی را به شکل متد نوشته و عبارت set را جلوی آن مینویسیم. عبارت set به این معناست که وقتی مقداری به این ورودی set شد، متد myIf را اجرا کن. اگر نتیجه شرط ورودی false شده باشد، محتوای ViewContainer پاک خواهد شد و اگر محتوای true داشته باشد templateRef به آن اضافه خواهد شد.
برای استفادهی از دایرکتیو نوشته شده، از تکه کد زیر استفاده میکنیم:
<input type="text" #x (keyup)="0"/> <div *myIf="x.value=='yes'"> it means we are on condition </div>
نرمال سازی (قسمت سوم: Third Normal Form)
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; public class Student { public int Id { set; get; } public string Name { set; get; } //هر دانشجو چند ترم در دانشگاه خواهد بود public virtual ICollection<Semester> Semesters { set; get; } //هر دانشجو چندین واحد دارد public virtual ICollection<Unit> Units { set; get; } } public class Semester { public int Id { set; get; } public string Name { set; get; } public int Average { set; get; } [ForeignKey("StudentId")] public virtual Student Student { set; get; } public int StudentId { set; get; } } public class Unit { public int Id { set; get; } public string Name { set; get; } public string UnitType { set; get; } public int NumberOfUnits { set; get; } [ForeignKey("StudentId")] public virtual Student Student { set; get; } public int StudentId { set; get; } }
و یا هر ترم یک سری واحد داره. اینطوری چطور؟ چون الان مشخص نیست در هر ترم چه واحدهایی برداشته.
فعال سازی استفادهی از Tag Helpers برای تمام Viewهای برنامه
برای اینکه تمام Viewهای سایت بتوانند به امکانات Tag Helpers دسترسی پیدا کنند، باید یک سطر ذیل را به فایل ViewImports.cshtml_ اضافه کرد:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Microsoft.AspNetCore.Mvc.TagHelpers به همراه افزودن وابستگی Microsoft.AspNetCore.Mvc در حین فعال سازی ASP.NET MVC، به پروژه اضافه میشود:
فعال سازی Intellisense مربوط به Tag Helpers در ویژوال استودیو
هرچند فعال سازی ASP.NET MVC، تنها وابستگی است که برای کار با Tag Helpers نیاز است، اما برای فعال سازی Intellisense آنها باید بستهی Microsoft.AspNetCore.Razor.Tools را نیز به فایل prject.json برنامه، جهت نصب معرفی کرد:
{ "dependencies": { //same as before "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0", "Microsoft.AspNetCore.Razor.Runtime": "1.0.0", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" } }, "tools": { //same as before "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final" } }
یک مثال: ایجاد لینکی به یک اکشن متد
<a asp-controller="Home" asp-action="Index" asp-route-id="123">Home</a>
اگر نیاز به اشارهی به مسیریابی خاصی از طریق نام آن وجود دارد (همان نامهایی که در حین تعریف یک مسیریابی ذکر میشوند) میتوان به صورت ذیل عمل کرد:
<a asp-route="login">Login</a>
<a asp-controller="Account" asp-action="Register" asp-protocol="https" asp-host="asepecificdomain.com" asp-fragment="fragment">Register</a>
راهنمای تبدیل HTML Helpers به Tag Helpers
در جدول ذیل، مثالهایی را از HTML Helpers متداول و معادلهای Tag Helper آنها مشاهده میکنید:
Tag Helper | HTML Helper |
<label asp-for="Email" class="col-md-2 control-label"></label> | @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) |
<a asp-controller="MyController" asp-action="MyAction" class="my-css-classname" my-attr="my-attribute">Click me</a> | @Html.ActionLink("Click me", "MyController", "MyAction", { @class="my-css-classname", data_my_attr="my-attribute"}) |
<input asp-for="FirstName" style="width:100px;"/> | @Html.TextBox("FirstName", Model.FirstName, new { style = "width: 100px;" }) |
<input asp-for="Email" class="form-control" /> | @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) |
<input asp-for="Password" class="form-control" /> | @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) |
<input asp-for="UserName" class="form-control" /> | @Html.EditorFor(l => l.UserName, new { htmlAttributes = new { @class = "form-control" } }) |
<form asp-controller="Account" asp-action="Register" method="post" class="form-horizontal" role="form"> | @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { @Html.AntiForgeryToken() |
<span asp-validation-for="UserName" class="text-danger"></span> | @Html.ValidationMessageFor(m => m.UserName, "", new { @class = "text-danger" }) |
<div asp-validation-summary="ValidationSummary.All" class="text-danger"></div> | @Html.ValidationSummary("", new { @class = "text-danger" }) |
نکات تکمیلی کار با فرمها توسط Tag Helpers
نمونهای از مثال Tag helper کار با فرمها را در جدول فوق ملاحظه میکنید. چند نکتهی تکمیلی ذیل را میتوان به آن اضافه کرد:
- در حین کار با Tag Helpers، درج anti forgery token به صورت خودکار صورت میگیرد. اگر میخواهید که این توکن ذکر نشود، آنرا توسط ویژگی "asp-anti-forgery="false خاموش کنید.
- برای درج پارامترهای مسیریابی خاص، از asp-route به همراه نام پارامتر مدنظر استفاده کنید:
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewBag.ReturnUrl" method="post" > </form>
<form action="/Account/Login?returnurl=%2FHome%2FAbout" method="post">
<form asp-route="login" asp-route-returnurl="@ViewBag.ReturnUrl" method="post" > </form>
Tag helpers مخصوص تعریف اسکریپتها و CSSها
در اینجا Tag Helpers صرفا به عنوان جایگزینهای HTML Helpers مطرح نیستند. توسط آنها قابلیتهای جدیدی نیز ارائه شدهاست. برای مثال اگر تگ اسکریپت را به صورت ذیل تعریف کنیم:
<script asp-src-include="~/app/**/*.js"></script>
<script src="/app/app.js"></script> <script src="/app/controllers/controller1.js"></script> <script src="/app/controllers/controller2.js"></script> <script src="/app/controllers/controller3.js"></script> <script src="/app/controllers/controller4.js"></script> <script src="/app/services/service1.js"></script> <script src="/app/services/service2.js"></script>
در این بین اگر میخواهید از پوشهی خاصی صرفنظر کنید، از asp-src-exclude استفاده کنید:
<script asp-src-include="~/app/**/*.js" asp-src-exclude="~/app/services/**/*.js"> </script>
<link rel="stylesheet" href="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/css/bootstrap.min.css" asp-fallback-href="~/lib/bootstrap/css/bootstrap.min.css" asp-fallback-test-class="hidden" asp-fallback-test-property="visibility" asp-fallback-test-value="hidden" /> <script src="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/bootstrap.min.js" asp-fallback-src="~/lib/bootstrap/js/bootstrap.min.js" asp-fallback-test="window.jQuery"> </script>
به علاوه اگر ویژگی asp-file-version را نیز ذکر کنید:
<link rel="stylesheet" href="~/css/site.min.css" asp-file-version="true"/>
<link rel="stylesheet" href="/css/site.min.css?v=UdxKHVNJA5vb1EsG9O9uURFDfEE3j1E3DgwL6NiDGMc" />
یک نکته: ویژگی asp-file-version را برای تصاویر هم میتوان بکار برد:
<img src="~/images/logo.png" alt="company logo" asp-file-version="true" />
<img src="/images/logo.png?v=W2F5D366_nQ2fQqUk3URdgWy2ZekXjHzHJaY5yaiOOk" alt="company logo"/>
بررسی Environment Tag Helper
با متغیرهای محیطی و نحوهی تعریف آنها در قسمتهای قبل آشنا شدیم. در اینجا tag helper سفارشی خاصی برای کار با آنها ارائه شدهاست که شیبه به if/else عمل میکنند:
<environment names="Development"> <link rel="stylesheet" href="~/css/site1.css" /> <link rel="stylesheet" href="~/css/site2.css" /> </environment> <environment names="Staging,Production"> <link rel="stylesheet" href="~/css/site.min.css" asp-file-version="true"/> </environment>
کار با دراپ داونها توسط Tag helpers
فرض کنید ViewModel یک view جهت نمایش یک دراپ داون به این صورت تنظیم شدهاست:
public class CustomerViewModel { public string Vehicle { get; set; } public List<SelectListItem> Vehicles { get; set; }
<select asp-for="Vehicle" asp-items="Model.Vehicles"> </select>
قبل ازاین مقاله، درباره راه اندازی و استفاده از کتابخانه Automapper بحث شده ولی موردی که شاید کمتر به آن توجه شده سرعت این نگاشت میباشد. در این مقاله با استفاده از نوشتن تست، این موضوع بررسی میشود.
کلاس ساده زیر را در نظر بگیرید که
برای مثال از سمت لایه دسترسی به داده گرفته شده است:
public enum PersonType { Real =0, Legal=1 } public class Person { public long PersonId { get; set; } public string Name { get; set; } public string Family { get; set; } public PersonType PersonType { get; set; } public Person(long personId, string name, string family, PersonType personType) { PersonId = personId; Name = name; Family = family; PersonType = personType; } }
از سازنده آن برای دریافت مقادیر مربوط به خصوصیات شیء استفاده شد.
در طرف دیگر نیز کلاسی برای نگاشت از آبجکت رسیده از سمت لایه داده ساخته میشود که برای نمایش در ویوها ایجاد شده است:
public class PersonDto { public long PersonId { get; set; } public string Name { get; set; } public string Family { get; set; } public PersonType PersonType { get; set; } public PersonDto(long personId, string name, string family, PersonType personType) { PersonId = personId; Name = name; Family = family; PersonType = personType; } }
همانطور که مشاهده میکنید در سازنده این کلاس نیز مقادیر خصوصیات، دریافت شدهاست.
برای ایجاد لیستی که در تست مورد استفاده قرار میگیرد نیز کلاس زیر را فراهم میکنیم:
public class PersonList { readonly List<Person> _list = new List<Person>(); public ReadOnlyCollection<Person> GetPersons() { if (!_list.Any()) { for (int i = 0; i < 100*1000; i++) { _list.Add(new Person(i + 1, "Person Name" + i, "Person Family" + i, (PersonType)(i % 2))); } } return _list.AsReadOnly(); } }
در اینجا برای
محسوس بودن نتیجه تست میتوان تعداد آبجکتهای لازم برای تست را تعیین کرد، فعلا 100
هزار آبجکت در نظر گرفته شده است:
for (int i = 0; i < 100*1000; i++) { _list.Add(new Person(i + 1, "Person Name" + i, "Person Family" + i, (PersonType)(i % 2))); }
برای
ارجاع به AutoMapper، با
استفاده از نیوگت، پکیج را به پروژه تست
ارجاع میدهیم: (در حال حاضر نسخه 5.1.1 استفاده شده است)
<package id="AutoMapper" version="5.1.1" targetFramework="net452" />
در سمت
تست نگاشت نیز از دو متد برای مقایسه استفاده میکنیم؛ یکی با استفاده از AutoMapper و دیگری بدون استفاده از آن:
[TestMethod] public void FillPersonDtoList_AutoMapperShouldMapPersonListToPersonDtoList_WhenLargeAmountOfPerson() { // arrange var personDtoList = new List<PersonDto>(); persons = new PersonList().GetPersons(); // act personDtoList = Mapper.Map<List<PersonDto>>(persons); //assert Assert.AreEqual(persons.Count, personDtoList.Count); } [TestMethod] public void FillPersonDtoList_UsingHandlyAssignment_WhenLargeAmountOfPerson() { // arrange var personDtoList = new List<PersonDto>(); persons = new PersonList().GetPersons(); // act foreach (var person in persons) { personDtoList.Add(new PersonDto(person.PersonId, person.Name, person.Family, person.PersonType)); } //assert Assert.AreEqual(persons.Count, personDtoList.Count); }
سرعت
نگاشت AutoMapper در نسخه حال حاضر تقریبا سه بار کندتر از استفاده معمول برای تهیه نگاشت
جدید از یک آبجکت است:
نکته: این تست با نسخه قدیمی تر(4.0.4.0) نیز انجام شده که این اختلاف سرعت نزدیک به 13 بار کندتر هم رسیده است.
پ.ن: سورس پروژه تست
تبدیلگر تاریخ شمسی برای AutoMapper
public class User { public int Id { set; get; } public string Name { set; get; } public DateTime RegistrationDate { set; get; } }
public class UserViewModel { public int Id { set; get; } public string Name { set; get; } public string RegistrationDate { set; get; } }
تبدیلگر سفارشی تاریخ میلادی به شمسی مخصوص AutoMapper
در ذیل یک تبدیلگر سفارشی مخصوص AutoMapper را با پیاده سازی اینترفیس ITypeConverter آن ملاحظه میکنید:
public class DateTimeToPersianDateTimeConverter : ITypeConverter<DateTime, string> { private readonly string _separator; private readonly bool _includeHourMinute; public DateTimeToPersianDateTimeConverter(string separator = "/", bool includeHourMinute = true) { _separator = separator; _includeHourMinute = includeHourMinute; } public string Convert(ResolutionContext context) { var objDateTime = context.SourceValue; return objDateTime == null ? string.Empty : toShamsiDateTime((DateTime)context.SourceValue); } private string toShamsiDateTime(DateTime info) { var year = info.Year; var month = info.Month; var day = info.Day; var persianCalendar = new PersianCalendar(); var pYear = persianCalendar.GetYear(new DateTime(year, month, day, new GregorianCalendar())); var pMonth = persianCalendar.GetMonth(new DateTime(year, month, day, new GregorianCalendar())); var pDay = persianCalendar.GetDayOfMonth(new DateTime(year, month, day, new GregorianCalendar())); return _includeHourMinute ? string.Format("{0}{1}{2}{1}{3} {4}:{5}", pYear, _separator, pMonth.ToString("00", CultureInfo.InvariantCulture), pDay.ToString("00", CultureInfo.InvariantCulture), info.Hour.ToString("00"), info.Minute.ToString("00")) : string.Format("{0}{1}{2}{1}{3}", pYear, _separator, pMonth.ToString("00", CultureInfo.InvariantCulture), pDay.ToString("00", CultureInfo.InvariantCulture)); } }
ثبت و معرفی تبدیلگرهای سفارشی AutoMapper
پس از تعریف یک تبدیلگر سفارشی AutoMapper، اکنون نیاز است آنرا به AutoMapper معرفی کنیم:
public class TestProfile1 : Profile { protected override void Configure() { // این تنظیم سراسری هست و به تمام خواص زمانی اعمال میشود this.CreateMap<DateTime, string>().ConvertUsing(new DateTimeToPersianDateTimeConverter()); this.CreateMap<User, UserViewModel>(); } public override string ProfileName { get { return this.GetType().Name; } } }
همانطور که مشاهده میکنید در اینجا دو نگاشت تعریف شدهاند. یکی برای تبدیل User به UserViewModel و دیگری، معرفی نحوهی نگاشت DateTime به string، توسط تبدیلگر سفارشی DateTimeToPersianDateTimeConverter است که به کمک متد الحاقی ConvertUsing صورت گرفتهاست.
باید دقت داشت که تنظیمات تبدیلگرهای سفارشی سراسری هستند و در کل برنامه و به تمام پروفایلها اعمال میشوند.
بررسی خروجی تبدیلگر سفارشی تاریخ
اکنون کار استفاده از تنظیمات AutoMapper با ثبت پروفایل تعریف شده آغاز میشود:
Mapper.Initialize(cfg => // In Application_Start() { cfg.AddProfile<TestProfile1>(); });
var dbUser1 = new User { Id = 1, Name = "Test", RegistrationDate = DateTime.Now.AddDays(-10) }; var uiUser = new UserViewModel(); Mapper.Map(source: dbUser1, destination: uiUser);
نوشتن تبدیلگرهای غیر سراسری
همانطور که عنوان شد، معرفی تبدیلگرها به AutoMapper سراسری است و در کل برنامه اعمال میشود. اگر نیاز است فقط برای یک مدل خاص و یک خاصیت خاص آن تبدیلگر نوشته شود، باید نگاشت مورد نظر را به صورت ذیل تعریف کرد:
this.CreateMap<User, UserViewModel>() .ForMember(userViewModel => userViewModel.RegistrationDate, opt => opt.ResolveUsing(src => { var dt = src.RegistrationDate; return dt.ToShortDateString(); }));
خصوصی سازی تبدیلگرها با تدارک موتورهای نگاشت اختصاصی
اگر میخواهید تنظیمات TestProfile1 به کل برنامه اعمال نشود، نیاز است یک MappingEngine جدید و مجزای از MappingEngine سراسری AutoMapper را ایجاد کرد:
var configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers); configurationStore.AddProfile<TestProfile1>(); var mapper = new MappingEngine(configurationStore); mapper.Map(source: dbUser1, destination: uiUser);
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
AM_Sample02.zip
ASP.NET MVC #12
- اگر لیستی قرار است در تمام صفحات نمایش داده شود و محل آن هم باید در layout باشد، یعنی باید این لیست در هر بار نمایش و یا تولید هر View (تمام Viewهای سایت)، تولید شود. بنابراین در BaseViewModelایی که عنوان شد، تعریف خاصیت این لیست را قرار دهید و در layout از آن استفاده کنید.
// در پایه مدلها public abstract class BaseViewModel { public IList<Post> Posts { get; set; } // خاصیت عمومی که قرار است در فایل مستر قابل دسترسی باشد } // در اکشن متد return View(model: new HomeViewModel { Posts = .... }); // در اینجا ویوومدل ارسالی از پایه مدلها مشتق میشود public class HomeViewModel : BaseViewModel
EF Code First #1
<connectionStrings> <clear/> <add name="ProductContext" connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true" providerName="System.Data.SqlClient" /> </connectionStrings>
public class ProductContext : DbContext
با این تعاریف باید برنامه کار کند (البته بر اساس نام کلاسهای برنامه شما).
ضمنا login failed به این معنا است که رشته اتصالی اشتباه تعریف شده است. رشته فوق به یک بانک اطلاعاتی sql server و به وهله پیش فرض آن اشاره میکند و از نوع windows authentication است. این موارد را باید بر اساس تنظیمات سیستم خودتون تغییر بدید.
EF Code First #5
DropColumn("dbo.Judges", "Photo"); Sql("alter table [dbo].[Judges] add [PhotoTemp] varbinary(max) FILESTREAM not null"); RenameColumn("dbo.Judges", "PhotoTemp", "Photo"); Sql("alter table [dbo].[Judges] add constraint [DF_Judges_Photo] default(0x) for [Photo]");
ترجمه و تالیف: بهروز راد
وضعیت: در حال نگارش
پیشتر، آقای نصیری در بخشی از مباحث مربوط به Code First در مورد روشهای مختلف ارث بری در EF و در روش Code First صحبت کرده اند. در این مقالهی دو قسمتی، در مورد دو تا از این روشها در حالت Database First میخوانید.
چرا باید از ارث بری استفاده کنیم؟
یکی از اهداف اصلی ORMها این است که با ایجاد یک مدل مفهومی از پایگاه داده، آن را هر چه بیشتر به طرز تفکر ما از مدل شی گرای برنامه مان نزدیکتر کنند. از آنجا که ما توسعه گران از مفاهیم شی گرایی مانند "ارث بری" در کدهای خود استفاده میکنیم، نیاز داریم تا این مفهوم را در سطح پایگاه داده نیز داشته باشیم. آیا این کار امکان پذیر است؟ EF چه امکاناتی برای رسیدن به این هدف برای ما فراهم کرده است؟ در این قسمت به این سوال پاسخ خواهیم داد.
ارث بری جداول مفهومی است که در EF به راحتی قابل پیاده سازی است. سه روش برای پیاده سازی این مفهوم در مدل وجود دارد.
- Table Per Type یا TPT: خصیصههای مشترک در جدول پایه قرار دارند و به ازای هر زیر مجموعه نیز یک جدول جدا ایجاد میشود.
- Table Per Hierarchy یا TPH: تمامی خصیصهها در یک جدول وجود دارند.
- Table Per Concrete Type یا TPC: جدول پایه ای وجود ندارد و به ازای هر موجودیت دقیقاً یک جدول همراه با خصیصههای موجودیت در آن ایجاد میشود.
روش TPT
در این روش، خصیصههای مشترک در یک جدول پایه قرار دارند و به ازای هر زیر مجموعه از جدول پایه، یک جدول با خصیصههای منحصر به آن نوع ایجاد میشود. ابتدا جداول و ارتباطات بین آنها که در توضیح مثال برای این روش با آنها کار میکنیم را ببینیم.
فرض کنید قصد داریم تا در هنگام ثبت مشخصات یک دانش آموز، مقطع تحصیلی او نیز حتماً ذخیره شود. در این حالت، فیلدی با نام Degree ایجاد و تیک گزینهی Allow Nulls را از روبروی آن بر میداریم. با این حال اگر مشخصات دانش آموزان را در جدولی عمومی مثلاً با نام People ذخیره کنیم و این جدول را مکانی برای ذخیرهی مشخصات افراد دیگری مانند مدیران و معلمان نیز در نظر بگیریم، از آنجا که قصد ثبت مقطع تحصیلی برای مدیران و معلمان را نداریم، وجود فیلد Degree در کار ما اختلال ایجاد میکند. اما با ذخیرهی اطلاعات مدیران و معلمان در جداول مختص به خود، میتوان قانون غیر قابل Null بودن فیلد Degree برای دانش آموزان را به راحتی پیاده سازی کرد.
همان طور که در شکل قبل نیز مشخص است، ما یک جدول پایه با نام Persons ایجاد کرده ایم و خصیصههای مشترک بین زیر مجموعهها (FirstName و LastName) را در آن قرار داده ایم. سه موجودیت (Student، Admin و Instructor) از Persons ارث میبرند و موجودیت BusinessStudent نیز از Student ارث میبَرَد.
جداول ایجاد شده، پس از ایجاد مدل به روش Database First، به شکل زیر تبدیل میشوند.
از آنجا که قصد داریم ارتباطات ارث بری شده ایجاد کنیم، باید ارتباطات پیش فرض شکل گرفته بین موجودیتها را حذف کنیم. بدین منظور، بر روی هر خط ارتباطی در EDM Designer کلیک راست و گزینهی Delete from Model را انتخاب کنید. سپس بر روی موجودیت Person، کلیک راست کرده و از منوی Add New، گزینهی Inheritance را انتخاب کنید (شکل زیر).
شکل زیر ظاهر میشود.
قسمت بالا، موجودیت پایه، و قسمت پایین، موجودیت مشتق شده را مشخص میکند. این کار را سه مرتبه برای ایجاد ارتباط ارث بری شده بین موجودیت Person به عنوان موجودیت پایه و موجودیتهای Student، Instructor و Admin به عنوان موجودیتهای مشتق شده ایجاد کنید. همچنین یک ارتباط نیز بین موجودیت Student به عنوان موجودیت پایه و موجودیت BusinessStudent به عنوان موجودیت مشتق شده ایجاد کنید. نتیجهی کار را در شکل زیر ملاحظه میکنید.
اگر بر روی دکمهی Save در نوار ابزار Visual Studio کلیک کنید، چهار خطا در پنجرهی Error List نمایش داده میشود
این خطاها بیانگر این هستند که خصیصهی PersonId به دلیل اینکه در موجودیت پایهی Person تعریف شده است، نباید در موجودیتهای مشتق شده از آن نیز وجود داشته باشد چون موجودیتهای مشتق شده، خصیصهی PersonId را به ارث برده اند. وجود این خصیصه در زمان طراحی جدول در مدل فیزیکی الزامی بوده است اما اکنون ما با مدل مفهومی و قوانین شی گرایی سر و کار داریم. بنابراین خصیصهی PersonId را از موجودیتهای Student، Instructor، Admin و BusinessStudent حذف کنید. شکل زیر، نتیجهی کار را نشان میدهد.
اکنون اگر بر روی دکمهی Save کلیک کنید، خطاها از بین میروند.
ما خصیصهی PersonId را از موجودیتهای مشتق شده به این دلیل که آن را از موجودیت پایه ارث میبرند حذف کردیم. حال این خصیصه برای موجودیتهای مشتق شده وجود دارد اما باید مشخص کنیم که به کدام خصیصه از کلاس پایه تناظر دارد. شاید انتظار این باشد که EF، خود تشخیص بدهد که PersonId در موجودیتهای مشتق شده باید به PersonId کلاس پایهی خود تناظر داشته باشد اما در حال حاضر این کاری است که خود باید انجام دهیم. بدین منظور، بر روی هر یک از موجودیتهای مشتق شده کلیک راست کرده و گزینهی Table Mapping را انتخاب کنید. سپس همان طور که در شکل زیر مشاهده میکنید، تناظر را ایجاد کنید.
مدل ما آماده است. آن را امتحان میکنیم. در زیر، یک کوئری LINQ ساده بر روی مدل ایجاد شده را ملاحظه میکنید.
using (PersonDbEntities context = new PersonDbEntities()) { var people = from p in context.Persons select p; foreach (Person person in people) { Console.WriteLine("{0}, {1}", person.LastName, person.FirstName); } Console.ReadLine(); }
قضیه به همین جا ختم نمیشود! ما الان یک مدل ارث بری شده داریم. بهتر است مزایای آن را در عمل ببینیم. شاید دوست داشته باشیم تا فقط اطلاعات زیر مجموعهی BusinessStudent را بازیابی کنیم.
using (PersonDbEntities context = new PersonDbEntities()) { var students = from p in context.Persons.OfType<BusinessStudent>() select p; foreach (BusinessStudent student in students) { Console.WriteLine("{0}, {1}: Degree {2}, Discipline {3}", student.LastName, student.FirstName, student.Degree, student.Discipline); } Console.ReadLine(); }
همان طور که در کدهای قبل نیز مشخص است، خصیصههای LastName و FirstName از موجودیت پایه یعنی Person، خصیصهی Degree از موجودیت مشتق شدهی Student (که البته در نقش موجودیت پایه برای BusinessStudent است) و Discipline از موجودیت مشتق شده یعنی BusinessStudent خوانده میشوند.
یک روش دیگر نیز برای کار با این سلسه مراتب ارث بری وجود دارد. کوئری اول را دست نزنیم (اطلاعات موجودیت پایه را بازیابی کنیم) و پیش از انجام عملیاتی خاص، نوع موجودیت مشتق شده را بررسی کنیم. مثالی در این زمینه:
using (PersonDbEntities context = new PersonDbEntities()) { var people = from p in context.Persons select p; foreach (Person person in people) { Console.WriteLine("{0}, {1}", person.LastName, person.FirstName); if (person is Student) Console.WriteLine(" Degree: {0}", ((Student)person).Degree); if (person is BusinessStudent) Console.WriteLine(" Discipline: {0}", ((BusinessStudent)person).Discipline); } Console.ReadLine(); }
مزایای روش TPT
- امکان نرمال سازی سطح 3 در این روش به خوبی وجود دارد
- افزونگی در جداول وجود ندارد.
- اصلاح مدل آسان است (برای اضافه یا حذف کردن یک موجودیت به/از مدل فقط کافی است تا جدول متناظر با آن را از پایگاه داده حذف کنید)
- سرعت عملیات CRUD (ایجاد، بازیابی، آپدیت، حذف) دادهها با افزایش تعداد موجودیتهای شرکت کننده در سلسله مراتب ارث بری کاهش مییابد. به عنوان مثال، کوئریهای SELECT، حاوی عبارتهای JOIN خواهند بود و عدم توجه صحیح به کوئری نوشته شده میتواند منجر به حضور چندین عبارت JOIN که برای ارتباط بین جداول به کار میرود در اسکریپت تولیدی و کاهش زمان اجرای بازیابی دادهها شود.
- تعداد جداول در پایگاه داده زیاد میشود
در قسمت بعد با روش TPH آشنا میشوید.