سلام؛ تشکر از توضیحات شما. اجازه بدید من طور دیگری سئوالم رو مطرح کنم. به طور مثال ما برای کار با تاریخ شمسی در SQl چندین روش پیش رو داریم که وارد جزئیات آن نمیشوم ولی یکی از این روشها که به خوبی جواب میدهد استفاده از CLR است که ما با توسط این قابلیت میتوانیم یک نوع دیتا تایپ جدید ، با ماهیت جدید در اس کیو ال اضافه کنیم. حالا منظور بنده این است که آیا برای تریگرها هم میشود این کار را انجام داد یا خیر؟ مثلا توسط CLR یا هر روش دیگری که وجود دارد ، ما بیاییم و یک نوع تریگر کاملا جدید و Customize شده برای خودمان درست کنیم. به طور مثال : زمانی که کاربر از دیتابیس بخواهد بکاپ تهیه کند یا آن را ریستور کند ، یکسری فعالیتها به آن فعالیت اضافه شود یا در راستای آن انجام شود. با تشکر
در مطلب قبل نحوهی ایجاد روابط Polymorphic را بررسی کردیم و همچنین چندین راهحل جایگزین را نیز ارائه دادیم. همانطور که عنوان شد این نوع روابط اساساً از لحاظ طراحی دیتابیس اصولی نیستند و تا حد امکان نباید استفاده شوند. این نوع روابط بیشتر ORM friendly هستند و اکثر فریمورکهای غیرداتنتی به عنوان یک گزینهی توکار، امکان ایجاد این روابط را فراهم میکنند. به عنوان مثال درLaravel Eloquent ORM به صورت توکار از این قابلیت پشتیبانی میشود:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Comment extends Model { /** * Get the owning commentable model. */ public function commentable() { return $this->morphTo(); } } class Post extends Model { /** * Get all of the post's comments. */ public function comments() { return $this->morphMany('App\Comment', 'commentable'); } } class Video extends Model { /** * Get all of the video's comments. */ public function comments() { return $this->morphMany('App\Comment', 'commentable'); } }
اما در Entity Framework Core هنوز این قابلیت پیادهسازی نشده است. در اینجا میتوانید وضعیت پیادهسازی Polymorphic Association را پیگیری کنید. پیادهسازی این قابلیت از این جهت مهم است که امکان کوئری نویسی را برای این نوع روابط سادهتر خواهد کرد به عنوان مثال در کدهای PHP فوق جهت واکشی کامنتهای یک مطلب میتوانیم اینگونه عمل کنیم:
$post = App\Post::find(1); foreach ($post->comments as $comment) { // }
همچنین امکان واکشی owner این رابطه را نیز حین کار با کامنتها را خواهیم داشت:
$comment = App\Comment::find(1); $commentable = $comment->commentable;
در ادامه میخواهیم معادل LINQ آن را پیادهسازی کنیم. در مطلب قبل مدل Comment این چنین ساختاری داشت:
public enum CommentType { Article, Video, Event } public class Comment { public int Id { get; set; } public string CommentText { get; set; } public string User { get; set; } public int? TypeId { get; set; } public CommentType CommentType { get; set; } }
در اینجا همانطور که مشاهده میکنید هیچگونه ارتباط معناداری بین Comment و همچنین owner رابطه (که ممکن است هر کدام از مقادیر Enum فوق باشد) وجود ندارد. اگر این مدل به تنها یک مدل مثلاً Article اشاره داشته باشد، نیاز به تعیین Navigation Property در دو طرف رابطه خواهد بود:
public class Comment { public int Id { get; set; } public string CommentText { get; set; } public string User { get; set; } public virtual Article Article { get; set; } public int ArticleId { get; set; } } public class Article { public int Id { get; set; } public string Title { get; set; } public string Slug { get; set; } public string Description { get; set; } public virtual ICollection<Article> Articles { get; set; } }
اما از آنجائیکه رابطه یک حالت پویا دارد، نمیتوانیم به صورت صریح نوع ارجاعات را در دو طرف رابطه تعیین کنیم. برای داشتن همچین قابلیتی میتوانیم Navigation Property را به صورت [NotMapped] تعیین کنیم که EF Core آنها را در نظر نگیرید. بنابراین به صورت دستی عملکرد آنها را پیادهسازی خواهیم کرد. برای اینکار میتوانیم یک اینترفیس با عنوان ICommentable را تعریف کنیم و برای هر مدلی که نیاز به قابلیت کامنت دارد، این اینترفیس را پیادهسازی کنیم. همچنین یک ارجاع به لیستی از کامنتها را به صورت Navigation Property به هر کدام از مدلها نیز اضافه خواهیم کرد:
interface ICommentable { int Id { get; set; } } public class Article : ICommentable { public int Id { get; set; } public string Title { get; set; } public string Slug { get; set; } public string Description { get; set; } [NotMapped] public ICollection<Comment> Comments { get; set; } } public class Video : ICommentable { public int Id { get; set; } public string Url { get; set; } public string Description { get; set; } [NotMapped] public ICollection<Comment> Comments { get; set; } } public class Event : ICommentable { public int Id { get; set; } public string Name { get; set; } public DateTimeOffset? Start { get; set; } public DateTimeOffset? End { get; set; } [NotMapped] public ICollection<Comment> Comments { get; set; } }
سپس درون مدل Comment ارجاع به Polymorphic relation را نیز به صورت [NotMapped] پیادهسازی خواهیم کرد:
public class Comment { public int Id { get; set; } public string CommentText { get; set; } public string User { get; set; } public int? TypeId { get; set; } public CommentType CommentType { get; set; } ICommentable _parent; [NotMapped] public ICommentable Parent { get => _parent; set { _parent = value; TypeId = value.Id; CommentType = (CommentType) Enum.Parse(typeof(CommentType), value.GetType().Name); } } }
کاری که در بالا انجام شده، تنظیم تایپ مدلی است که میخواهیم واکشی کنیم. یعنی به محض مقداردهی، پراپرتی Comments مدل مورد نظر به همراه Id و در نهایت نوع آن را تنظیم کردهایم. اکنون برای واکشی کامنتهای یک مطلب خواهیم داشت:
var article = dbContext.Articles.Find(1); article.Comments = dbContext.Comments .Where(c => c.TypeId == article.Id && c.CommentType == CommentType.Article) .ToList(); foreach (var comment in article.Comments) comment.Parent = article; foreach (var comment in article.Comments) { Console.WriteLine($"{comment.User} - ${comment.CommentText} - {((Article) comment.Parent).Title}"); }
همانطور که مشاهده میکنید اکنون میتوانیم از هر دو طرف رابطه به اطلاعات موردنیازمان دسترسی داشته باشیم.
در قسمت قبل، کار پیاده سازی سمت سرور نمایش اطلاعات یک گرید، به پایان رسید. در این قسمت میخواهیم از سمت کلاینت، اطلاعات صفحه بندی و مرتب سازی را به سمت سرور ارسال کرده و همچنین نتیجهی دریافتی از سرور را نمایش دهیم.
پیشنیازهای نمایش اطلاعات گرید به همراه صفحه بندی اطلاعات
در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانههای ثالث» نحوهی نصب و معرفی کتابخانهی ngx-bootstrap را بررسی کردیم. دقیقا همان مراحل، در اینجا نیز باید طی شوند و از این مجموعه تنها به کامپوننت Pagination آن نیاز داریم. همان قسمت ذیل گرید تصویر فوق که شماره صفحات را جهت انتخاب، نمایش دادهاست.
بنابراین ابتدا فرض بر این است که دو بستهی بوت استرپ و ngx-bootstrap را نصب کردهاید:
در فایل angular-cli.json. شیوهنامهی بوت استرپ را نیز افزودهاید:
پس از آن باید بهخاطر داشت که کامپوننت نمایش صفحه بندی این مجموعه PaginationModule نام دارد و باید در نزدیکترین ماژول مورد نیاز، ثبت و معرفی شود:
برای نمونه در این مثال، ماژولی به نام simple-grid.module.ts دربرگیرندهی گرید مطلب جاری است و به صورت ذیل به برنامه اضافه شدهاست:
بنابراین تعریف PaginationModule باید به قسمت imports این ماژول اضافه شود و تعریف آن در app.module.ts تاثیری بر روی این قسمت نخواهد داشت.
کامپوننتی هم که مثال جاری را نمایش میدهد به صورت ذیل به ماژول SimpleGrid فوق اضافه شدهاست:
تهیه معادلهای قراردادهای سمت سرور در سمت Angular
در قسمت قبل، تعدادی قرارداد مانند پارامترهای دریافتی از سمت کلاینت و ساختار اطلاعات ارسالی به سمت کلاینت را تعریف کردیم. اکنون جهت کار strongly typed با آنها در سمت یک برنامهی تایپ اسکریپتی Angular، کلاسهای معادل آنها را تهیه میکنیم.
ساختار شیء محصول دریافتی از سمت سرور
با این محتوا
که در اینجا هر کدام از خواص ذکر شده، معادل camel case نمونهی سمت سرور خود هستند (چون JSON.NET در ASP.NET Core، به صورت پیش فرض یک چنین خروجی را تولید میکند).
ساختار معادل پارامترهای صفحه بندی و مرتب سازی ارسالی به سمت سرور
با این محتوا
در اینجا همان ساختار IPagedQueryModel سمت سرور را مشاهده میکنید. از آن جهت مشخص سازی جزئیات صفحه بندی و نحوهی مرتب سازی اطلاعات، استفاده میشود.
ساختار معادل اطلاعات صفحه بندی شدهی دریافتی از سمت سرور
با این محتوا
این ساختار جنریک نیز دقیقا معادل همان PagedQueryResult سمت سرور است و حاوی تعداد کل ردیفهای یک کوئری و تنها قسمتی از اطلاعات صفحه بندی شدهی آن میباشد.
ساختار ستونهای گرید نمایشی
با این محتوا
هر ستون نمایش داده شده، دارای یک برچسب، خاصیتی مشخص در سمت سرور و بیانگر قابلیت مرتب سازی آن میباشد. اگر isSortable به true تنظیم شود، با کلیک بر روی سرستونها میتوان اطلاعات را بر اساس آن ستون، مرتب سازی کرد.
تهیه سرویس ارسال اطلاعات صفحه بندی به سرور و دریافت اطلاعات از آن
پس از تدارک این مقدمات، اکنون کار تعریف سرویسی که این اطلاعات را به سمت سرور ارسال میکند و نتیجه را باز میگرداند، به صورت ذیل خواهد بود:
این دستور سبب ایجاد کلاس ProductsListService شده و همچنین قسمت providers ماژول simple-grid را نیز بر این اساس به روز رسانی میکند.
پیش از تکمیل این سرویس، نیاز است متدی را جهت تبدیل یک شیء، به معادل کوئری استرینگ آن تهیه کنیم:
در قسمت قبل امضای متد GetPagedProducts دارای ویژگی HttpGet است. بنابراین، نیاز است اطلاعات را به صورت کوئری استرینگ از سمت کلاینت دریافت کند و متد toQueryString فوق به صورت خودکار بر روی تمام خواص یک شیء دلخواه حرکت کرده و آنها را تبدیل به یک رشتهی حاوی کوئری استرینگها میکند.
برای نمونه متد toQueryString فوق است که سبب ارسال یک چنین درخواستی به سمت سرور میشود:
پس از این تعریف، سرویس ProductsListService به صورت ذیل تکمیل خواهد شد:
در اینجا از متد toQueryString، جهت تکمیل متد get ارسالی به سمت سرور استفاده شدهاست تا پارامترها را به صورت کوئری استرینگها تبدیل کرده و ارسال کند.
سپس در متد map آن، res.json دقیقا همان ساختار PagedQueryResult سمت سرور را به همراه دارد. اینجا است که فرصت خواهیم داشت نمونهی سمت کلاینت آنرا که در ابتدای بحث تهیه کردیم، وهله سازی کرده و بازگشت دهیم (نگاشت فیلدهای دریافتی از سمت سرور به سمت کلاینت).
تکمیل کامپوننت نمایش گرید
قسمت آخر این مطلب، استفادهی از این ساختارها و سرویسها و نمایش اطلاعات دریافتی از آنها است. برای این منظور ابتدا نیاز است سرستونهای این گرید را تهیه کرد:
در اینجا ابتدا بررسی میشود که آیا یک ستون قابلیت مرتب سازی را دارد، یا خیر؟ اگر اینطور است، در کنار آن یک گلیف آیکن مرتب سازی درج میشود. اگر خیر، صرفا متن عنوان آن نمایش داده خواهد شد. میشد تمام این موارد را به ازای هر ستون به صورت مجزایی ارائه داد، اما در این حالت به کدهای تکراری زیادی میرسیدیم. به همین جهت از یک حلقه بر روی تعریف ستونهای این گرید استفاده شدهاست. آرایهی این ستونها نیز به صورت ذیل تعریف میشود:
همچنین در کدهای قالب این کامپوننت، مدیریت کلیک بر روی یک سر ستون را نیز مشاهده میکنید:
در اینحالت اگر ستونی که بر روی آن کلیک شده، پیشتر مرتب سازی شدهاست، صرفا خاصیت صعودی بودن آن برعکس خواهد شد. در غیراینصورت، نام خاصیت درخواستی مرتب سازی و جهت آن نیز مشخص میشود. سپس مجددا این گرید توسط متد getPagedProductsList رندر خواهد شد.
کار رندر بدنهی اصلی گرید توسط همین چند سطر در قالب آن مدیریت میشود:
اولین ستون آن، اندکی ابتکاری است. در اینجا شماره ردیفهای خودکاری در هر صفحه درج خواهند شد. این شماره ردیف نیز جزو ستونهای منبع دادهی فرضی برنامه نیست. به همین جهت برای درج آن، توسط let i = index در ngFor، به شماره ایندکس ردیف جاری دسترسی پیدا میکنیم. سپس توسط محاسباتی بر اساس تعداد ردیفهای هر صفحه و شمارهی صفحهی جاری، میتوان شماره ردیف فعلی را محاسبه کرد.
در اینجا حلقهای بر روی queryResult.items تشکیل شدهاست. این منبع داده به صورت ذیل در کامپوننت متناظر مقدار دهی میشود:
ابتدا سرویس ProductsListService را که در ابتدای بحث تکمیل شد، به سازندهی این کامپوننت تزریق میکنیم. به کمک آن میتوان در متد getPagedProductsList، ابتدا queryModel جاری را که شامل اطلاعات مرتب سازی و صفحه بندی است، به سرور ارسال کرده و سپس نتیجهی نهایی را به queryResult انتساب دهیم. به این ترتیب تعداد کل رکوردها و همچنین آیتمهای صفحهی جاری دریافت میشوند. اکنون حلقهی ngFor نمایش بدنهی گرید، کار تکمیل صفحهی جاری را انجام خواهد داد.
قسمت آخر کار، افزودن کامپوننت نمایش شماره صفحات است:
در اینجا از کامپوننت pagination مجموعهی ngx-bootstarp استفاده شدهاست و یک سری از خواص مستند شدهی آن، مقدار دهی شدهاند؛ مانند متنهای صفحهی بعد و قبل و امثال آن. مدیریت کلیک بر روی شمارههای آن، در کامپوننت جاری به صورت ذیل است:
علت تعریف دو خاصیت اضافهی currentPage و numberOfPages، استفادهی از آنها در قسمت ذیل این شمارهها (خارج از کامپوننت نمایش شماره صفحات) جهت نمایش page 1/x است.
هر زمانیکه کاربر بر روی شمارهای کلیک میکند، رخداد onPageChange فراخوانی شده و در اینحالت تنها کافی است شماره صفحهی درخواستی queryModel جاری را به روزرسانی کرده و سپس آنرا در اختیار متد getPagedProductsList جهت دریافت اطلاعات این صفحهی درخواستی قرار دهیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
پیشنیازهای نمایش اطلاعات گرید به همراه صفحه بندی اطلاعات
در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانههای ثالث» نحوهی نصب و معرفی کتابخانهی ngx-bootstrap را بررسی کردیم. دقیقا همان مراحل، در اینجا نیز باید طی شوند و از این مجموعه تنها به کامپوننت Pagination آن نیاز داریم. همان قسمت ذیل گرید تصویر فوق که شماره صفحات را جهت انتخاب، نمایش دادهاست.
بنابراین ابتدا فرض بر این است که دو بستهی بوت استرپ و ngx-bootstrap را نصب کردهاید:
> npm install bootstrap --save > npm install ngx-bootstrap --save
"apps": [ { "styles": [ "../node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.css" ],
import { PaginationModule } from "ngx-bootstrap"; @NgModule({ imports: [ PaginationModule.forRoot() ]
>ng g m SimpleGrid -m app.module --routing
کامپوننتی هم که مثال جاری را نمایش میدهد به صورت ذیل به ماژول SimpleGrid فوق اضافه شدهاست:
>ng g c SimpleGrid/products-list
تهیه معادلهای قراردادهای سمت سرور در سمت Angular
در قسمت قبل، تعدادی قرارداد مانند پارامترهای دریافتی از سمت کلاینت و ساختار اطلاعات ارسالی به سمت کلاینت را تعریف کردیم. اکنون جهت کار strongly typed با آنها در سمت یک برنامهی تایپ اسکریپتی Angular، کلاسهای معادل آنها را تهیه میکنیم.
ساختار شیء محصول دریافتی از سمت سرور
>ng g cl SimpleGrid/app-product
export class AppProduct { constructor( public productId: number, public productName: string, public price: number, public isAvailable: boolean ) {} }
ساختار معادل پارامترهای صفحه بندی و مرتب سازی ارسالی به سمت سرور
>ng g cl SimpleGrid/PagedQueryModel
export class PagedQueryModel { constructor( public sortBy: string, public isAscending: boolean, public page: number, public pageSize: number ) {} }
ساختار معادل اطلاعات صفحه بندی شدهی دریافتی از سمت سرور
>ng g cl SimpleGrid/PagedQueryResult
export class PagedQueryResult<T> { constructor(public totalItems: number, public items: T[]) {} }
ساختار ستونهای گرید نمایشی
>ng g cl SimpleGrid/GridColumn
export class GridColumn { constructor( public title: string, public propertyName: string, public isSortable: boolean ) {} }
تهیه سرویس ارسال اطلاعات صفحه بندی به سرور و دریافت اطلاعات از آن
پس از تدارک این مقدمات، اکنون کار تعریف سرویسی که این اطلاعات را به سمت سرور ارسال میکند و نتیجه را باز میگرداند، به صورت ذیل خواهد بود:
>ng g s SimpleGrid/products-list -m simple-grid.module
پیش از تکمیل این سرویس، نیاز است متدی را جهت تبدیل یک شیء، به معادل کوئری استرینگ آن تهیه کنیم:
toQueryString(obj: any): string { const parts = []; for (const key in obj) { if (obj.hasOwnProperty(key)) { const value = obj[key]; if (value !== null && value !== undefined) { parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); } } } return parts.join("&"); }
[HttpGet("[action]")] public PagedQueryResult<Product> GetPagedProducts(ProductQueryViewModel queryModel)
http://localhost:5000/api/Product/GetPagedProducts?sortBy=productId&isAscending=true&page=2&pageSize=7
پس از این تعریف، سرویس ProductsListService به صورت ذیل تکمیل خواهد شد:
@Injectable() export class ProductsListService { private baseUrl = "api/Product"; constructor(private http: Http) {} getPagedProductsList( queryModel: PagedQueryModel ): Observable<PagedQueryResult<AppProduct>> { return this.http .get(`${this.baseUrl}/GetPagedProducts?${this.toQueryString(queryModel)}`) .map(res => { const result = res.json(); return new PagedQueryResult<AppProduct>( result.totalItems, result.items ); }); }
سپس در متد map آن، res.json دقیقا همان ساختار PagedQueryResult سمت سرور را به همراه دارد. اینجا است که فرصت خواهیم داشت نمونهی سمت کلاینت آنرا که در ابتدای بحث تهیه کردیم، وهله سازی کرده و بازگشت دهیم (نگاشت فیلدهای دریافتی از سمت سرور به سمت کلاینت).
تکمیل کامپوننت نمایش گرید
قسمت آخر این مطلب، استفادهی از این ساختارها و سرویسها و نمایش اطلاعات دریافتی از آنها است. برای این منظور ابتدا نیاز است سرستونهای این گرید را تهیه کرد:
<table class="table table-striped table-hover table-bordered table-condensed"> <thead> <tr> <th class="text-center" style="width:3%">#</th> <th *ngFor="let column of columns" class="text-center"> <div *ngIf="column.isSortable" (click)="sortBy(column.propertyName)" style="cursor: pointer"> {{ column.title }} <i *ngIf="queryModel.sortBy === column.propertyName" class="glyphicon" [class.glyphicon-sort-by-order]="queryModel.isAscending" [class.glyphicon-sort-by-order-alt]="!queryModel.isAscending"></i> </div> <div *ngIf="!column.isSortable" style="cursor: pointer"> {{ column.title }} </div> </th> </tr> </thead>
export class ProductsListComponent implements OnInit { columns: GridColumn[] = [ new GridColumn("Id", "productId", true), new GridColumn("Name", "productName", true), new GridColumn("Price", "price", true), new GridColumn("Available", "isAvailable", true) ];
همچنین در کدهای قالب این کامپوننت، مدیریت کلیک بر روی یک سر ستون را نیز مشاهده میکنید:
export class ProductsListComponent implements OnInit { itemsPerPage = 7; queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage); sortBy(columnName) { if (this.queryModel.sortBy === columnName) { this.queryModel.isAscending = !this.queryModel.isAscending; } else { this.queryModel.sortBy = columnName; this.queryModel.isAscending = true; } this.getPagedProductsList(); } }
کار رندر بدنهی اصلی گرید توسط همین چند سطر در قالب آن مدیریت میشود:
<tbody> <tr *ngFor="let item of queryResult.items; let i = index"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center">{{ item.productName }}</td> <td class="text-center">{{ item.price | number:'.0' }}</td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" disabled="disabled" /> </td> </tr> </tbody> </table>
در اینجا حلقهای بر روی queryResult.items تشکیل شدهاست. این منبع داده به صورت ذیل در کامپوننت متناظر مقدار دهی میشود:
export class ProductsListComponent implements OnInit { itemsPerPage = 7; currentPage: number; numberOfPages: number; isLoading = false; queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage); queryResult = new PagedQueryResult<AppProduct>(0, []); constructor(private productsListService: ProductsListService) {} ngOnInit() { this.getPagedProductsList(); } private getPagedProductsList() { this.isLoading = true; this.productsListService .getPagedProductsList(this.queryModel) .subscribe(result => { this.queryResult = result; this.isLoading = false; }); } }
قسمت آخر کار، افزودن کامپوننت نمایش شماره صفحات است:
<div align="center"> <pagination [maxSize]="8" [boundaryLinks]="true" [totalItems]="queryResult.totalItems" [rotate]="false" previousText="‹" nextText="›" firstText="«" lastText="»" (numPages)="numberOfPages = $event" [(ngModel)]="currentPage" (pageChanged)="onPageChange($event)"></pagination> </div> <pre class="card card-block card-header">Page: {{currentPage}} / {{numberOfPages}}</pre>
export class ProductsListComponent implements OnInit { itemsPerPage = 7; currentPage: number; numberOfPages: number; onPageChange(event: any) { this.queryModel.page = event.page; this.getPagedProductsList(); } }
هر زمانیکه کاربر بر روی شمارهای کلیک میکند، رخداد onPageChange فراخوانی شده و در اینحالت تنها کافی است شماره صفحهی درخواستی queryModel جاری را به روزرسانی کرده و سپس آنرا در اختیار متد getPagedProductsList جهت دریافت اطلاعات این صفحهی درخواستی قرار دهیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
نظرات مطالب
EF Code First #9
برای حالت TPH فقط باید این یک تعریف را داشته باشید public DbSet<Person> People { set; get; } و دسترسی به سایر مشتقات Person، از طریق کوئریهای ()<db.People.OfType<Coach انجام میشود و نه ارجاع مستقیمی به DbSet ایی که نباید داشته باشند. وجود DbSet در Context، یعنی الزام به ساخت جدول معادل.
مقدمه
به عبارت دیگر در طراحی ساخت یافته، کلاسهای سطح بالا، به کلاسهای سطح پایین وابستهاند. این مسئله دو مشکل را ایجاد میکند:
یکی از اصول پنجگانهی طراحی برنامههای شیء گرا که با نام اصول SOLID شناخته میشوند، اصل «وارونگی وابستگیها» است که روشی برای مشکل جفت شدگی و وابستگی کلاسها به یکدیگر را به صورت تئوری ارائه میدهد.
در شکل زیر، حالت عادی جریان کنترل را میبینید .
شیء گرایی در واقع در مورد نحوهی جریان کنترل است. در اینجا اینترفیسها به ما کنترل کاملی را بر جریان کنترل (Flow of control) میدهند که با استفاده از این امکان میتوانیم از نوشتن کدهای جفت شده، شکننده و کلاسهایی یکبار مصرف، بپرهیزیم.
الگوی Dependency Injection
الگوی تزریق وابستگی 3 نوع کلاس را درگیر میکند:
شکل زیر وابستگی بین کلاسها را شرح میدهد:
همانطور که میبینید، کلاس Injector، نمونهای از کلاس سرویس را میسازد و آن را به نمونهای از کلاس Client تزریق میکند. با این کار، DI، وظیفهی ساخت یک نمونه از کلاس Service را از درون کلاس Client جدا میکند.
انواع تزریقات وابستگیها:
به صورت کلی به سه روش و در سه مکان، امکان تزریق وابستگی کلاس سرویس، درون کلاس کلاینت وجود دارد:
Inversion of Control Container
به صورت کلی IoC Container ها سه وظیفهی اساسی را برعهده دارند:
زمانیکه یک برنامه را بر پایهی شیء گرایی طراحی میکنید و مینویسید، به صورت معمول جریان وابستگیها در برنامهی شما به صورت زیر است:
در این حالت برای کامپایل شدن برنامه نیاز است که فرآیند کامپایل از دورترین کلاس و متد شروع شود. همانطور که میبینید در اینجا هر کلاس به تمام زیر کلاسهای خود وابسته است و هر تغییر در هر کدام از کلاسهای خدمتگزار میتواند تاثیرات مستقیمی بر روی سایر کلاسها داشته باشد. در واقع جریان کنترل برنامهی ما بجای اینکه در اختیار کلاسهای سیاست گذار ( کلاسهای بالایی در شکل) باشد، به دست کلاسهای خدمتگزار افتاده است. این قضیه باعث درهم تنیدگی و جفت شدگی کدها و کلاسها به یکدیگر میشود که مشکلزاست و امکان نگهداری، تغییرات و توسعهی برنامه را به شدت کاهش میدهد.
به عبارت دیگر در طراحی ساخت یافته، کلاسهای سطح بالا، به کلاسهای سطح پایین وابستهاند. این مسئله دو مشکل را ایجاد میکند:
- هر تغییری در کلاسهای سطح پایین ممکن است باعث ایجاد اشکالی در کلاسهای سطح بالا گردد.
- استفادهی مجدد از کلاسهای سطح بالا در جاهای دیگر مشکل است؛ زیرا وابستگی مستقیمی به کلاسهای سطح پایین دارند.
اصل معکوس سازی / وارونگی وابستگیها Dependency Inversion Principle
یکی از اصول پنجگانهی طراحی برنامههای شیء گرا که با نام اصول SOLID شناخته میشوند، اصل «وارونگی وابستگیها» است که روشی برای مشکل جفت شدگی و وابستگی کلاسها به یکدیگر را به صورت تئوری ارائه میدهد.
اصل وارونگی وابستگیها بیان میکند:
- ماژولهای (کلاسهای) سطح بالا نباید به ماژولهای (کلاسهای) سطح پایین وابسته باشند و هر دو باید به انتزاعات وابسته باشند (برای مثال interfaceها).
- انتزاعات نباید وابسته به جزئیات باشند؛ بلکه جزئیات (پیاده سازی) باید وابسته به انتزاعات باشند.
بر اساس این اصل، ما باید در سطوح بالا سیاست گذاریها و انتزاعات را در قالب interfaceها تعریف کرده و کلاسهای سطح بالای خود را بر همین اساس پیاده سازی کنیم و در سطوح پائینتر، پیاده سازیهایی را بر اساس انتزاعات و سیاست گذاریهای سطوح بالاتر، انجام دهیم.
همانطور که میبینید، کلاس M برای اجرا، وابسته به کلاس N و متد F در درون آن است. در اینجا ما با استفاده از اینترفیسها میتوانیم جریان کنترل را معکوس یا وارونه کنیم که به این عمل «وارونگی کنترل یا Inversion of Control» میگویند.
شیء گرایی در واقع در مورد نحوهی جریان کنترل است. در اینجا اینترفیسها به ما کنترل کاملی را بر جریان کنترل (Flow of control) میدهند که با استفاده از این امکان میتوانیم از نوشتن کدهای جفت شده، شکننده و کلاسهایی یکبار مصرف، بپرهیزیم.
الگوی Dependency Injection
تزریق وابستگی یا Dependency Injection، یک الگوی طراحی است که از آن برای طراحی و پیاده سازی IoC Containerها استفاده میشود. این الگو به ما اجازه میدهد که اشیاء وابسته را خارج از کلاس بسازیم و آنها را به طریقی دیگر به کلاس، جهت استفاده ارائه دهیم. بهوسیلهی DI ما ساخت و اتصال اشیاء وابسته به کلاس را از تعریف آن خارج میکنیم.
- کلاس کلاینت / Client Class : کلاس کلاینت (کلاس وابسته) کلاسی است که به کلاس سرویس وابسته است .
- کلاس سرویس / Service Class : کلاس سرویس (وابستگی) کلاسی است که یک سرویس را به کلاس کلاینت ارائه میدهد.
- کلاس تزریق کننده / Injector Class : کلاس تزریق کننده، نمونهای از کلاس سرویس را ساخته و به کلاس کلاینت، تزریق میکند.
شکل زیر وابستگی بین کلاسها را شرح میدهد:
همانطور که میبینید، کلاس Injector، نمونهای از کلاس سرویس را میسازد و آن را به نمونهای از کلاس Client تزریق میکند. با این کار، DI، وظیفهی ساخت یک نمونه از کلاس Service را از درون کلاس Client جدا میکند.
انواع تزریقات وابستگیها:
به صورت کلی به سه روش و در سه مکان، امکان تزریق وابستگی کلاس سرویس، درون کلاس کلاینت وجود دارد:
- تزریق درون سازنده / Constructor Injection : در تزریق درون سازنده، در سازندهی کلاس کلاینت، لیستی از سرویسهای مورد نیاز کلاس، که کلاس، برای عملکرد خود به آنها «وابسته» است، ثبت میشوند و کلاس Injector، سرویس (وابستگی) مورد نظر را درون سازندهی کلاس Client ارائه میدهد.
- تزریق درون Property کلاس / Property Injection : در این حالت که همچنین با نام (Setter Injection) هم شناخته میشود، تزریق کننده، وابستگی را به وسیلهی یک Property عمومی کلاس کلاینت ارائه میدهد.
- تزریق درون متد / Method Injection : در این حالت، خود کلاس کلاینت، یک پیاده سازی از یک interface را ارائه میکند که درون آن متدهایی برای ارائهی وابستگیها به کلاینت تعریف شدهاند. در این وضعیت، تزریق کننده از این اینترفیس برای ارائهی وابستگیها به کلاینت درون متدها، استفاده میکند.
هر کدام از روشهای فوق مزایا و معایب خود را دارند، ولی در NET Core. بیشتر از «تزریق درون سازنده» استفاده میشود. در صورت لزوم میتوانید از اینجا نمونههایی از تزریق وابستگی را به هر کدام از سه روش بالا، مشاهده کنید.
Inversion of Control Container
در واژگان فنی مهندسی نرم افزار، Container (محفظه) به جزیی از برنامه گفته میشود که میتواند اجزای دیگر برنامه را در بر بگیرد. IoC Container ها در واقع فریم ورکها/چارچوبها یا کتابخانههای برنامه نویسی هستند که ما در آنها میتوانیم اشیاء مختلف را به سبکهای خاصی تعریف و ثبت کنیم و در مواقع لزوم آنها را واکشی و به کلاسها تزریق کنیم. معمولا IoC Containerها لیستی از اشیاء هستند که در آن اینترفیسها و پیاده سازیهای مربوط به هر کدام از آنها ثبت میشوند. درون IoC Container برای پیاده سازی اصل وارونگی وابستگیها، معمولا از یکی از دو الگوی زیر استفاده میکنند (گاها هم دو الگو را با هم پیاده سازی میکنند) :
- Dependency Injection
- Service Locator
تمرکز اصلی ما در این نوشتار بر روی DI Container هاست. فرق Dependency Injection و Service Locator در این است که در DI، وابستگیها توسط IoC Container از درون محفظه واکشی میشوند و به درون کد تزریق میشوند ولی در Service Locator در هر جایی از برنامه میتوان با استفادهی مستقیم از Container و با استفاده از متدهایی که به ما ارائه میدهد، پرس و جو کرد (کوئری زد) و وابستگی مورد نظر را واکشی کرد.
در تزریق وابستگی، کلاس استفاده کننده از سرویسها، درگیر نحوهی واکشی و نمونه سازی از سرویس مورد نظر خود نمیشود و همهی کار توسط DI Container انجام میگیرد. ولی در Service Locator باید سرویس مورد نظر، درون خود کلاس، مستقیما از Container دریافت و ساخته شود.
برای استفاده از Service Locator، تنها پیش نیاز، دسترسی به شیء Service Locator است.
به صورت کلی IoC Container ها سه وظیفهی اساسی را برعهده دارند:
- ثبت سرویس درون خود
- ساخت نمونههای مورد نظر از سرویسها و ارائه دادن آنها به کلاسهایی که نیاز دارند.
- از بین بردن نمونه سرویسهای ساخته شده (Dispose) کردن آنها .
در ادامه با ساخت پروژهای، اولین سرویس خودمان را درون Microsoft Dependency Injection Container یا به اختصار DI Container، ثبت کرده و آن را واکشی میکنیم.
فرض کنید لایه سرویس هم به این شکل باشد :
public IQueryable<Branch> GetAlls() { return _branchRepository.GetAllwithCorporate(); } public IQueryable<Branch> GetAllWithAll() { var CorporateID = _serverApplicationSettingRepository.GetAll().FirstOrDefault().CorporateID; return _branchRepository.GetAllWithAll().Where(x => x.CorporateID == CorporateID); }
مساله ای که هست اینه که ما نمیخواهیم برای بیرون کشیدن یک رکورد در Controller، در لایه سرویس یا مخزن کل رکوردها رو بیرون بکشیم.
اگر قرار باشد در لایه سرویس از نوع IList یا IEnumberable برای خروجی استفاده کنیم، یعنی در واقع کل رکوردها رو واکشی کردیم که شاید فقط در Controller نیاز به یک رکورد باشد. یعنی بستن کوئری در لایه سرویس یا مخزن، ممکنه باعث اجبار در واکشی همه رکوردها در لایههای پایین، حتی برای حالتی که ما فقط به یک رکورد نیاز داریم بشه.
پس راه حل چیه؟
اشتراکها
نقشههای ذهنی LINQ
اشتراکها
lookup در LINQ
اشتراکها
یک پروژه Email-Client-LINQ-to-IMAP
نظرات مطالب
SQL Injection چیست؟
یعنی در صورت استفاده از linq هم باز این مشکل پا برجاست؟!