نظرات مطالب
EF Code First #14
- بحث Self tracking entities در حالت database first است و در Code first پشتیبانی نمی‌شود و احتمالا هم نخواهد شد.
در اینجا رسما پایین صفحه قید شده که دیگر از STE با EF 5.0 برای حالت‌های N-Tier استفاده نکنید.
- در EF Code first توصیه می‌شود که برای کار با WCF از WCF Data Services و یا RIA Services استفاده کنید. هر دوی این‌ها برای کار با EF Code first به روز شدن اخیرا. هر دوی این‌ها change tracking سمت کاربر رو هم پشتیبانی می‌کنند.
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 2
برای استفاده از کلاس‏های Entity که در نوشتار پیشین ایجاد کردیم در WCF باید آن کلاس‎ها را دست‎کاری کنیم. متن کلاس tblNews را در نظر بگیرید:
namespace MyNewsWCFLibrary
{
    using System;
    using System.Collections.Generic;
    
    public partial class tblNews
    {
        public int tblNewsId { get; set; }
        public int tblCategoryId { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public System.DateTime RegDate { get; set; }
        public Nullable<bool> IsDeleted { get; set; }
    
        public virtual tblCategory tblCategory { get; set; }
    }
}
 مشاهده می‌کنید که برای تعریف کلاس‌ها از کلمه کلیدی partial استفاده شده است.  استفاده از کلمه کلیدی partial به شما اجازه می‌دهد که یک کلاس را در چندین فایل جداگانه تعریف کنید. به عنوان مثال می‌توانید فیلدها، ویژگی ها و سازنده‌ها را در یک فایل و متدها را در فایل دیگر قرار دهید. 
به صورت خودکار کلیه‌ی ویژگی‌ها به توجه به پایگاه داده ساخته شده اند. برای نمونه ما برای فیلد IsDeleted در SQL Server ستون Allow Nulls را کلیک کرده بودیم که در نتیجه در اینجا عبارت Nullable پیش از نوع فیلد نشان داده شده است. برای استفاده از این کلاس در WCF باید صفت  DataContract را به کلاس داد. این قرارداد به ما اجازه استفاده از ویژگی‌هایی که صفت DataMember را می‌گیرند را می‌دهد.
کلاس بالا را به شکل زیر بازنویسی کنید:
using System.Runtime.Serialization;

namespace MyNewsWCFLibrary
{
    using System;
    using System.Collections.Generic;
    
    [DataContract]
    public partial class tblNews
    {
        [DataMember]
        public int tblNewsId { get; set; }
        [DataMember]
        public int tblCategoryId { get; set; }
        [DataMember]
        public string Title { get; set; }
        [DataMember]
        public string Description { get; set; }
        [DataMember]
        public System.DateTime RegDate { get; set; }
        [DataMember]
        public Nullable<bool> IsDeleted { get; set; }

        public virtual tblCategory tblCategory { get; set; }
    }
}
هم‌چنین کلاس tblCategory را به صورت زیر تغییر دهید:
namespace MyNewsWCFLibrary
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;

    [DataContract]
    public partial class tblCategory
    {
        public tblCategory()
        {
            this.tblNews = new HashSet<tblNews>();
        }

        [DataMember]
        public int tblCategoryId { get; set; }
        [DataMember]
        public string CatName { get; set; }
        [DataMember]
        public bool IsDeleted { get; set; }
    
        public virtual ICollection<tblNews> tblNews { get; set; }
    }
}
با انجام کد بالا از بابت مدل کارمان تمام شده است. ولی فرض کنید در اینجا تصمیم به تغییری در پایگاه داده می‌گیرید. برای نمونه می‌خواهید ویژگی Allow Nulls فیلد IsDeleted را نیز False کنیم و مقدار پیش‌گزیده به این فیلد بدهید. برای این کار باید دستور زیر را در SQL Server اجرا کنیم:
BEGIN TRANSACTION
GO
ALTER TABLE dbo.tblNews
DROP CONSTRAINT FK_tblNews_tblCategory
GO
ALTER TABLE dbo.tblCategory SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Tmp_tblNews
(
tblNewsId int NOT NULL IDENTITY (1, 1),
tblCategoryId int NOT NULL,
Title nvarchar(50) NOT NULL,
Description nvarchar(MAX) NOT NULL,
RegDate datetime NOT NULL,
IsDeleted bit NOT NULL
)  ON [PRIMARY]
 TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE dbo.Tmp_tblNews SET (LOCK_ESCALATION = TABLE)
GO
ALTER TABLE dbo.Tmp_tblNews ADD CONSTRAINT
DF_tblNews_IsDeleted DEFAULT 0 FOR IsDeleted
GO
SET IDENTITY_INSERT dbo.Tmp_tblNews ON
GO
IF EXISTS(SELECT * FROM dbo.tblNews)
 EXEC('INSERT INTO dbo.Tmp_tblNews (tblNewsId, tblCategoryId, Title, Description, RegDate, IsDeleted)
SELECT tblNewsId, tblCategoryId, Title, Description, RegDate, IsDeleted FROM dbo.tblNews WITH (HOLDLOCK TABLOCKX)')
GO
SET IDENTITY_INSERT dbo.Tmp_tblNews OFF
GO
DROP TABLE dbo.tblNews
GO
EXECUTE sp_rename N'dbo.Tmp_tblNews', N'tblNews', 'OBJECT' 
GO
ALTER TABLE dbo.tblNews ADD CONSTRAINT
PK_tblNews PRIMARY KEY CLUSTERED 
(
tblNewsId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO
ALTER TABLE dbo.tblNews ADD CONSTRAINT
FK_tblNews_tblCategory FOREIGN KEY
(
tblCategoryId
) REFERENCES dbo.tblCategory
(
tblCategoryId
) ON UPDATE  NO ACTION 
 ON DELETE  NO ACTION 

GO
COMMIT
پس از آن مدل Entity Framework را باز کنید و در جایی از صفحه راست‌کلیک کرده و از منوی بازشده گزینه Update Model from Database را انتخاب کنید. سپس در پنجره بازشده، چون هیچ جدول، نما یا روالی به پایگاه داده‌ها نیفزوده ایم؛ دگمه‌ی Finish را کلیک کنید. دوباره کلاس tblNews را بازکنید. متوجه خواهید شد که همه‌ی DataContractها و DataMemberها را حذف شده است. ممکن است بگویید می‌توانستیم کلاس یا مدل را تغییر دهیم و به وسیله‌ی Generate Database from Model به‌هنگام کنیم. ولی در نظر بگیرید که نیاز به ایجاد چندین جدول دیگر داریم و مدلی با ده‌ها Entity دارید. در این صورت همه‌ی تغییراتی که در کلاس داده ایم زدوده خواهد شد. 
در بخش پسین، درباره‌ی این‌که چه کنیم که عبارت‌هایی که به کلاس‌ها می‌افزاییم حذف نشود؛ خواهم نوشت.
مطالب
آشنایی با معماری فیزیکی یک بانک اطلاعاتی
یکی از مهمترین مسائلی که به مدیر پایگاه داده، در پیاده سازی صحیح و نگهداری و برطرف سازی مشکلات می‌تواند کمک کند، شناخت و درک مفاهیم صحیحی از معماری فیزیکی یک بانک اطلاعاتی است. در این مقاله قصد دارم به معرفی برخی از این موارد بپردازم.

1:data pages اساسی‌ترین واحد نگهداری داده در اس کیوال سرور، صفحه نام دارد. فضای دیسک اختصاص یافته به فایل داده بانک، برای یک بانک اطلاعاتی به صورت منطقی به صفحات پیوسته از صفر تا n تقسیم بندی می‌شود. همچنین لازم به ذکر است عملیات خواندن و یا نوشتن در دیسک، در سطح این صفحه‌ها صورت می‌گیرد که در تصویر زیر قابل مشاهده است:


 لازم به ذکر است در sql server هر صفحه، 8 کیلوبایت است. این مورد به این معنی است که هر بانک اطلاعاتی، دارای 128 صفحه به ازای هر یک مگابایت است. هر صفحه دارای 96 بایت با عنوان header یا سرصفحه است که شامل اطلاعات سیستمی در مورد صفحه است. این اطلاعات سیستمی شامل مواردی چون page number یا شماره صفحه و نوع صفحه یا page type و مقدار فضای خالی آن صفحه و شماره شناسایی یک واحد اختصاص یافته یا به اختصار allocation unit id و.... هستند می‌باشد. نکته جالب و قابل توجه این است که فایل‌های ثبت وقایع یا Log files از صفحه استفاده نمی‌کنند؛ بلکه شامل یکسری رکورد log هستند.
 برای بدست آوردن اطلاعات در مورد فایلهای دیتابیس می‌توانید از کد زیر استفاده نمایید SELECT * FROM sys.database_files که خروجی زیر را به شما نشان می‌دهد:


 extents: به ابتدایی‌ترین قسمتی که sql server امکان مدیریت بر آن را دارد extent گویند. هر extent شامل 8 صفحه‌ی به هم پیوسته است. لازم به ذکر است که sql server هر 1 مگابایت را به شانزده extent اختصاص می‌دهد. sql server شامل دونوع extent است که عبارتند از : uniform,mixed uniform extent متعلق به یک شیء است و هر هشت صفحه‌ی آن فقط توسط یک شیء قابل استفاده‌است. mixed extent می‌تواند حداکثر بین هشت شیء به اشتراک گذاشته شود؛ به نحوی که هر یک از هشت صفحه می‌توانند متعلق به یک شیء باشند. همانطور که در شکل زیر می‌بینید به طور پیش فرض با ایجاد یک جدول، یک mixed extent به آن اختصاص داده می‌شود. در صورتیکه این شیء به اندازه‌ی هشت صفحه رشد کند، به آن یک uniform extent اختصاص داده میشود.


فایلهای بانک اطلاعاتی
هر بانک اطلاعاتی در sql server دارای سه نوع فایل است
فایلهای داده اولیه یا به اختصار primary data files
فایلهای داده‌های ثانویه یا به اختصار secondary data files
فایلهای ثبت وقایع یا به اختصار log file
فایل ثبت وقایع برای نگهداری و ثبت وقایع که برای عملیات recovery مورد نیاز است. معمولا یک بانک اطلاعاتی یک log file دارد؛ ولی می‌تواند بیشتر هم داشته باشد. پسوند این نوع فایلها ldf است .

مطالب
#Defensive Code in C - قسمت اول

Defensive Coding به معنی است که شما با انجام یکسری کار‌ها و در نظر گرفتن یکسری زیر ساخت‌ها در توسعه‌ی نرم افزار خود، به اهداف ذیل دست پیدا کنید:

1. Quality (کیفیت)

2. Comprehensible (جامعیت)

3. Predictable  (قابلیت پیش بینی)

دستیابی به هر کدام از این اهداف و روش‌های اعمال آنها بر روی یک پروژه‌ی نرم افزاری، در ادامه بحث خواهند شد. 

1. Clean Code

یکی از اهداف Defensive Coding که در ابتدای مقاله بحث شد جامعیت یا Comprehension بود. برای رسید به این هدف از مفهومی به نام Clean Code  استفاده می‌شود. Clean Code علاوه بر این مسئله، در پی ساده کردن ساختار بندی پشتیبانی و کاهش باگ‌های نرم افزار نیز هست. ویژگی‌های Clean Code در بالا با  توجه به شکل ذیل تشریح می‌شوند: 

· Easy to read

یک کد Clean  قابلیت خوانایی بالایی دارد. بسیاری از برنامه نویسان در سطوح مختلف با اهمیت این مسئله در توسعه نرم افزار آشنایی دارند. ولی بسیاری از همین برنامه نویسان این اصول را رعایت نمی‌کنند و سعی نمی‌کنند با اصول پیاده سازی آن در نرم افزارآشنا شوند.

اگر قابلیت خوانایی یک کد بالا باشد:

§ شما می‌توانید Pattern ‌های موجود در کد خود را که می‌توانید به عنوان نامزدهایی جهت Refactoring  هستند، تشخیص دهید.

§ برنامه نویسان دیگر به راحتی قصد و اهداف ( intent ) شما را از نوشتن یک کد خاص درک خواهند کرد و در طول زمان با خطا‌های زیادی روبرو نمی‌شوند.

§ توسعه‌ی راحت‌تر و در شرایط وجود فشار، ایجاد سریع یک قابلیت جدید در نرم افزار.

· Clear intent

یک کد Clear دارای اهداف روشن و قابل فهمی می‌باشد.

· Simple

پیچیدگی با کم هزینه بودن توسعه‌ی و پشتیبانی تضاد مستقیم دارد. بنابراین سادگی در کدها باید جزو اهداف اصلی قرار بگیرد.

· Minimal

کد باید به گونه‌ای باشد که تنها یک چیز را انجام داده و آن را به درستی انجام دهد. همچنین وابستگی بین اجزای کد باید در کمترین حد ممکن باشند.

· Thoughtful

یک کد Clean  کدی است که ساختار آن متفکرانه طراحی شده باشد. از نحوه‌ی طراحی یک کلاس گرفته تا layering و Tiering پروژه باید کاملا هوشمندانه و با توجه به پارامتر‌های موجود باشند. همچنین خطا‌های خطرناک و استثناء‌ها باید کاملا هندل شوند. 

همه‌ی ما با دیدن کد بالا سریعا مفهوم اسپاگتی کد به ذهنمان خطور می‌کند. تغییر، توسعه و پشتیبانی نرم افزارهایی که کد آنها به این صورت نوشته شده است، بسیار سخت و پر هزینه می‌باشد. در این حالت تغییر هر یک از اجزاء ممکن است بر سایر قسمت‌های دیگر تاثیرات مختلفی داشته باشد. راه کاری که در این حالت ارائه می‌شود، Refactoring می‌باشد. در این روش کد را به کلاس‌ها و متدهایی بر حسب عملکرد تقسیم خواهیم کرد. در نهایت کد تولید شده دارای کمترین تاثیر بر سایر قسمت‌ها خواهد بود. توجه داشته باشید که با انجام این کار، قدمی به سوی SOC یا Separation Of Concern برداشته‌اید.

1. Testable Code & Unit Test

یکی دیگر از اهداف Defensive Coding افزایش کیفیت یا Quality می‌باشد که برای رسیدن به این هدف از مفهوم Testable Code & Unit Test استفاده می‌شود. بسیاری از ویژگی‌های Testable Code و Clean Code با هم مشابه می‌باشند. برای مثال Refactor کردن هر متد به متد‌های کوچکتر، تست آن را ساده‌تر خواهند کرد. در نتیجه نوشتن کد‌های Testable ، با نوشتن کد‌های clean شروع می‌شود.

در این قسمت اشاره‌ای به Unit Test شده است؛ اما این مفهوم می‌تواند به یک مفهوم گسترده‌تر به نام  Automated Code testing، تعمیم داده شود. به این دلیل که تست فقط به Unit Testing محدود نمی‌شود و می‌تواند شامل سایر انواع تست‌ها مانند  integration test نیز باشد.

برای مثال شکل ذیل را در نظر بگیرید. در انتهای این سناریو یک Page جدید اضافه شده است. خوب؛ برای تست کد اضافه شده، مجبورید برنامه را اجرا کنید، login کنید، داده‌های مورد نظر را در فرم وارد کرده و در نهایت شرایط لازم را جهت تست، فراهم کنید تا بتوانید کد جدید را تست کنید. در این بین با خطایی مواجه می‌شوید. پس برنامه را متوقف می‌کنید و تغییرات لازم را اعمال می‌کنید. حال فرض کنید این خطا به این زودی‌ها رفع نشود. در این حالت باید فرآیند بالا را چندین و چند بار انجام دهید. نتیجه اینکه این روش بسیار زمان بر و پر هزینه خواهد بود. البته میزان هزینه و زمان رابطه‌ی نزدیکی با وسعت تغییرات دارند. برای رفع مسائلی از این دست مایکروسافت زیرساختی به نام MS Test ارائه داده است که می‌توان با آن سناریوهای تست متفاوتی را پیاده سازی و اجرا نمود. متاسفانه این مسئله در بسیار از جوامع توسعه نرم افزار رعایت نمی‌شود و در بسیاری از این جوامع، نیروی انسانی، این فرآیند و فرآیندهایی از این دست را انجام می‌دهند. درحالیکه چنین فرآیندهایی به راحتی توسط ابزارهای ارائه شده‌ی توسط شرکت‌های مختلف قابل مدیریت است.

 


1. Predictability

یکی دیگر از اهداف Defensive Coding، قابلیت پیش بینی یا Predictability می‌باشد. فرآیند تشخیص و پیش بینی خطا‌ها را Predictability می‌گویند. با درنظر گرفتن امکان وقوع خطاهای مختلف و تصمیم گرفتن در مورد اینکه در هنگام رخ دادن این خطا باید چه کاری صورت بگیرد، می‌توان در رسیدن به این هدف قدم بزرگی برداشت. 

برای رسیدن به این هدف باید اصل Trust but Verify را دنبال کنیم. برای مثال این اصل به ما می‌گوید که در هنگام تعریف متد‌های public باید یکسری موارد را در نظر بگیریم. یک متد باید از یکسری قرارداد‌ها پیروی کند. یک متد قرارداد می‌کند که یکسری پارامتر‌ها را با یک data type خاص به عنوان ورودی دریافت کند. قرارداد می‌کند که یک مقدار خاص با یک data type خاص را به عنوان نوع بازگشتی بازگرداند یا اینکه هیچ مقداری را باز نگرداند و در نهایت یک متد متعهد می‌شود که یکسری Exception ‌تعریف شده و پیش بینی شده را صادر کند. اما برای اینکه مطمئن شویم یک application واقعا قابل پیش بینی است و این اصل را به درستی پیاده سازی کرده است، اعتماد می‌کنیم اما Verify را هم انجام می‌دهیم. برای verify کردن باید پارامترها، دیتا‌های متغیر، مقادیر بازگشتی و استثناء‌ها به گونه‌ای بررسی شوند که مطمئن شویم انتظارت ما را برآورده کرده‌اند. 

زیاده روی بیش از حد خوب نیست و آدم باید همیشه حد اعتدال را رعایت کند. این مسئله اینجا هم صادق است؛ به گونه‌ای که زیاده روی بیش از حد در پیاده سازی و اعمال هر کدام یک از این مواردی که در بالا ذکر گردید، ممکن است باعث پیچیدگی ساختار کد و به طبع آن Application شود. بنابراین رعایت حد اعتدال می‌تواند در رسیدن به این هدف بسیار مهم باشد.


مطالب
مسیریابی در Angular - قسمت هشتم - مسیرهای ثانویه
به چندین مسیر که در یک زمان و در یک سطح، نمایش داده می‌شوند، مسیرهای ثانویه (secondary routes) گفته می‌شوند و برای ساخت رابط‌های کاربری پیچیده مفید هستند. از آن‌ها می‌توان برای نمایش چندین پنل در یک صفحه استفاده کرد که هر کدام دارای محتوایی متفاوت، به همراه مسیریابی مستقل و خاص خودشان هستند؛ مانند ساخت یک صفحه‌ی مدیریتی. هرچند می‌توان این صفحه‌ی مدیریتی را با درج مستقیم کامپوننت‌های آن‌ها در یک صفحه نیز نمایش داد، اما اگر هر کدام نیاز به مسیریابی خاصی نیز جهت نمایش جزئیات آن‌ها داشته باشند، دیگر روش درج مستقیم کامپوننت‌ها توسط selector آ‌ن‌ها در صفحه پاسخگو نخواهد بود.


 مروری بر نحوه‌ی کارکرد مسیریابی اصلی برنامه

 به router-outlet ایی که در فایل قالب src\app\app.component.html قرار گرفته‌است، primary outlet می‌گویند. زمانیکه کاربر، برنامه را در مرورگر مشاهده می‌کند، با هربار کلیک بر روی یکی از لینک‌های منوی بالای سایت، قالب آن‌را در این primary outlet مشاهده می‌کند. اگر بخواهیم پنل دیگری را در همین صفحه و در همین سطح از نمایش، درج کنیم، نیاز به تعریف outlet دیگری است که به همراه مسیرهای ثانویه‌ای نیز خواهد بود.


تعریف یک router-outlet نامدار

با توجه به اینکه هر پنل به همراه مسیریابی ثانویه، نیاز به router-outlet خودش را خواهد داشت، مسیریاب برای اینکه بداند محتوای آن‌ها را در کجای صفحه درج کند، به نام‌های آن‌ها مراجعه می‌کند. به این ترتیب می‌توان چندین router-outlet را در یک سطح از نمایش تعریف کرد؛ اما هرکدام باید دارای نامی منحصربفرد باشند.
در مثال این سری می‌خواهیم پنلی را در سمت راست صفحه‌ی اصلی درج کنیم. برای تعریف آن در همان سطحی که router-outlet اصلی قرار دارد، نیاز است فایل src\app\app.component.html را ویرایش کنیم:
<div class="container">
  <div class="row">
    <div class="col-md-10">
      <router-outlet></router-outlet>
    </div>
    <div class="col-md-2">
      <router-outlet name="popup"></router-outlet>
    </div>
  </div>
</div>
در اینجا با استفاده از امکانات بوت استرپ، دو ستون را در قالب اصلی برنامه تعریف کرده‌ایم. ستون اول حاوی router-outlet اصلی برنامه است و ستون دوم جهت درج پنل پیام‌های برنامه تعریف شده‌است. این router-outlet دوم، با نام popup مشخص گردیده‌است.


افزودن ماژول جدید پیام‌های سیستم

در ادامه ماژول جدید پیام‌های سیستم را به همراه تنظیمات ابتدایی مسیریابی آن اضافه خواهیم کرد که در آن ماژول، مدیریت نمایش پیام‌های مختلفی در router-outlet ثانویه popup صورت خواهد گرفت:
 >ng g m message --routing
به این ترتیب دو فایل src\app\message\message-routing.module.ts و src\app\message\message.module.ts به برنامه اضافه می‌شوند.

در ادامه نیاز است MessageModule را به قسمت imports فایل src\app\app.module.ts نیز معرفی کنیم (پیش از AppRoutingModule که حاوی مسیریابی catch all است):

import { MessageModule } from './message/message.module';

@NgModule({
  declarations: [
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(ProductData, { delay: 1000 }),

    ProductModule,
    UserModule,
    MessageModule,

    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

سپس کامپوننت جدید Message را به ماژول Message برنامه اضافه می‌کنیم:
 >ng g c message/message
که اینکار سبب به روز رسانی فایل message.module.ts جهت تکمیل قسمت declarations آن با MessageComponent نیز می‌شود.

پس از آن یک سرویس ابتدایی پیام‌های کاربران را نیز اضافه خواهیم کرد:
 >ng g s message/message -m message/message.module
که سبب افزوده شدن سرویس message.service.ts و همچنین به روز رسانی خودکار قسمت providers ماژول message.module.ts نیز می‌شود:
 installing service
  create src\app\message\message.service.spec.ts
  create src\app\message\message.service.ts
  update src\app\message\message.module.ts
اگر نام ماژول را ذکر نکنیم، سرویس مدنظر تولید خواهد شد، اما قسمت providers هیچ ماژولی به صورت خودکار تکمیل نمی‌شود.

پس از ایجاد قالب ابتدایی فایل message.service.ts آن‌را به نحو ذیل تکمیل می‌کنیم:
import { Injectable } from '@angular/core';

@Injectable()
export class MessageService {
  private messages: string[] = [];
  isDisplayed = false;

  addMessage(message: string): void {
    let currentDate = new Date();
    this.messages.unshift(message + ' at ' + currentDate.toLocaleString());
  }
}
هدف از این سرویس، به اشتراک گذاری اطلاعات بین کامپوننت‌های مختلف برنامه است. هر قسمت از برنامه (هر کامپوننتی) می‌تواند این سرویس را در سازنده‌ی خود تزریق کرده و پیامی را به مجموعه‌ی پیام‌های موجود اضافه کند.

اکنون جهت تکمیل کامپوننت پیام‌ها، ابتدا فایل قالب message.component.html را به نحو ذیل تکمیل می‌کنیم:
<div class="row">
  <h4 class="col-md-10">Message Log</h4>
  <span class="col-md-2">
      <a class="btn btn-default"  (click)="close()">x</a>
   </span>
</div>
<div *ngFor="let message of messageService.messages; let i=index">
  <div *ngIf="i<10" class="message-row">
    {{ message }}
  </div>
</div>
به این ترتیب تنها 10 پیام از مجموعه پیام‌های سرویس پیام‌ها، توسط قالب این کامپوننت نمایش داده خواهد شد. یک دکمه‌ی بستن نیز در اینجا اضافه شده‌است.
کدهای کامپوننت این قالب به صورت ذیل است:
import { MessageService } from './../message.service';
import { Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';

@Component({
  //selector: 'app-message',
  templateUrl: './message.component.html',
  styleUrls: ['./message.component.css']
})
export class MessageComponent implements OnInit {

  constructor(private messageService: MessageService,
    private router: Router) { }

  ngOnInit() {
  }

  close(): void {
    // Close the popup.
    this.router.navigate([{ outlets: { popup: null } }]);
    this.messageService.isDisplayed = false;
  }
}
این کامپوننت سرویس پیام‌ها را در اختیار قالب خود قرار داده و همچنین یک دکمه‌ی بستن را نیز به همراه دارد که خاصیت isDisplayed  آن‌را false می‌کند.


تکمیل سایر کامپوننت‌های برنامه در جهت استفاده از سرویس پیام‌ها

ابتدا به فایل src\app\product\product-edit\product-edit.component.ts مراجعه کرده و سرویس جدید پیام‌ها را به سازنده‌ی آن تزریق می‌کنیم:
import { MessageService } from './../../message/message.service';

@Component({
  selector: 'app-product-edit',
  templateUrl: './product-edit.component.html',
  styleUrls: ['./product-edit.component.css']
})
export class ProductEditComponent implements OnInit {

  constructor(private productService: ProductService,
    private messageService: MessageService,
    private route: ActivatedRoute,
    private router: Router) { }
سپس ابتدای متد onSaveComplete آن‌را جهت درج پیام‌های این کامپوننت تغییر می‌دهیم.
  onSaveComplete(message?: string): void {
    if (message) {
      this.messageService.addMessage(message);
    }


تنظیم مسیرهای ثانویه

نحوه‌ی تعریف مسیریابی‌های مرتبط با router-outletهای غیراصلی برنامه، همانند سایر مسیریابی‌های برنامه‌است؛ با این تفاوت که در اینجا خاصیت outlet نیز به تنظیمات مسیر اضافه خواهد شد. به این ترتیب مشخص خواهیم کرد که محتوای این مسیر باید دقیقا در کدام router-outlet نامدار، درج شود.
برای این منظور فایل src\app\message\message-routing.module.ts را گشوده و تنظیمات مسیریابی آن‌را که به صورت RouterModule.forChild تعریف می‌شوند (چون ماژول اصلی برنامه نیستند)، تکمیل خواهیم کرد:
const routes: Routes = [
  { path: 'messages', component: MessageComponent, outlet: 'popup' }
];
همانطور که مشاهده می‌کنید، تنها تفاوت آن‌ها با سایر تعاریف مسیریابی‌های برنامه، ذکر نام Outlet ایی است که باید قالب MessageComponent را نمایش دهد.


فعالسازی یک مسیر ثانویه

در اینجا نیز همانند سایر مسیریابی‌ها، از دایرکتیو routerLink برای فعالسازی مسیرهای ثانویه استفاده می‌کنیم؛ اما syntax آن کمی متفاوت است:
<a [routerLink]="[{ outlets: { popup: ['messages'] } }]">Messages</a>

<a [routerLink]="['/products', product.id, 'edit', { outlets: { popup: ['summary', product.id] } }]">Messages</a>
در اینجا می‌توان سبب فعال شدن چندین outlet به صورت همزمان شد. به همین جهت از نام جمع outlets استفاده شده‌است. سپس در ادامه key/valueهایی که بیانگر نام outlet و سپس path آن‌ها هستند، ذکر می‌شوند.
در دومین لینک تعریف شده، ابتدا یک مسیر اصلی فعال شده و سپس یک مسیر ثانویه نمایش داده می‌شود.

یک نکته: هرچند به primary outlet نامی انتساب داده نمی‌شود، اما نام آن دقیقا primary است و می‌توان قسمت outlets را به صورت ذیل نیز تعریف کرد:
{ outlets: { primary: ['/products', product.id,'edit'], popup: ['summary', product.id] }}


در ادامه فایل src\app\app.component.html را ویرایش کرده و لینک Show Messages را به آن اضافه می‌کنیم:
    <ul class="nav navbar-nav navbar-right">
      <li *ngIf="authService.isLoggedIn()">
        <a>Welcome {{ authService.currentUser.userName }}</a>
      </li>
      <li>
         <a [routerLink]="[{ outlets: { popup: ['messages'] } }]">Show Messages</a>
      </li>
که سبب نمایش لینک Show Messages در منوی بالای سایت می‌شود (تصویر فوق). در این حال اگر بر روی آن کلیک کنیم این پنل جدید به سمت راست صفحه اضافه می‌شود. برای آزمایش آن، محصولی را ویرایش کنید، تا پیام مرتبط با آن در این پنل نمایش داده شود.
آدرس آن نیز چنین شکلی را پیدا می‌کند:
 http://localhost:4200/products(popup:messages)
در اینجا مسیرثانویه داخل یک پرانتز نمایش داده شده‌است. در این حالت اگر به صفحات مختلف برنامه مراجعه کنیم، هنوز این قسمت داخل پرانتز حفظ می‌شود و نمایان خواهد بود.

اکنون می‌خواهیم قابلیت مخفی سازی این پنل را نیز پیاده سازی کنیم. به همین جهت از خاصیت isDisplayed سرویس پیام‌ها که توسط دکمه‌ی بستن MessageComponent مدیریت می‌شود، استفاده خواهیم کرد. بنابراین لینک جدیدی را که در فایل src\app\app.component.html اضافه کردیم، به نحو ذیل تغییر خواهیم داد:
      <li *ngIf="!messageService.isDisplayed">
          <a (click)="displayMessages()">Show Messages</a>
      </li>
      <li *ngIf="messageService.isDisplayed">
         <a (click)="hideMessages()">Hide Messages</a>
      </li>
ngIfها بر اساس مقدار isDisplayed، سبب درج و یا حذف لینک‌های نمایش و مخفی کردن پیام‌ها می‌شوند و چون این قالب اکنون از سرویس پیام‌ها استفاده می‌کند، نیاز است این سرویس را به کامپوننت آن نیز تزریق کنیم:

import { MessageService } from './message/message.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  constructor(private authService: AuthService,
    private router: Router,
    private messageService: MessageService) {
  }

  displayMessages(): void {
    this.router.navigate([{ outlets: { popup: ['messages'] } }]);
    this.messageService.isDisplayed = true;
  }

  hideMessages(): void {
    this.router.navigate([{ outlets: { popup: null } }]);
    this.messageService.isDisplayed = false;
  }
}
در اینجا تزریق سرویس پیام‌ها را به سازنده‌ی کامپوننت App مشاهده می‌کنید. همچنین دو متد جدید نمایش و مخفی سازی پیام‌ها نیز تعریف شده‌اند که این متدها در قالب این کامپوننت، به لینک‌های مرتبطی متصل هستند.
برای فعالسازی یک مسیرثانویه توسط متدهای برنامه، نیاز است از سرویس مسیریاب و متد navigate آن استفاده کرد که نمونه‌هایی از آن‌را در اینجا ملاحظه می‌کنید. پارامترهای ذکر شده‌ی در اینجا نیز همانند دایرکتیو routerLink هستند.

یک نکته: اگر به متد hideMessages دقت کنید، مقدار value کلید popup به نال تنظیم شده‌است. این مورد سبب خواهد شد تا outlet آن خالی شود. به این ترتیب متد hideMessages علاوه بر مخفی کردن لینک نمایش پیام‌ها، پنل آن‌را نیز از صفحه حذف می‌کند. شبیه به همین نکته در متد close کامپوننت پیام‌ها که دکمه‌ی بستن آن‌را به همراه دارد، پیاده سازی شده‌است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-routing-lab-07.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
مطالب
آموزش LightInject IoC Container - قسمت 1
LightInject در حال حاضر یکی از قدرتمند‌ترین IoC Container‌‌ها است که از لحاظ سرعت و کارآیی در بالاترین جایگاه در میان IoC Container‌‌های موجود قرار دارد. جهت بررسی کارایی IoC Container‌ها می‌توانید به این لینک مراجعه کنید . LightInject یک IoC Container فوق العاده سبک وزن می‌باشد که تمامی قابلیت‌های متداولی که از یک Service Container انتظار می‌رود را شامل می‌شود. تنها شامل یک فایل .cs می‌باشد که تمامی کدهای آن در همین یک فایل نوشته شده‌اند. در پروژه‌های کوچک تا بزرگ بدون از دست دادن کارآیی، با بالاترین سرعت ممکن عمل تزریق وابستگی را انجام می‌دهد. در این مجموعه مقالات به بررسی کامل این IoC Container می‌پردازیم و تمامی قابلیت‌های آن را آموزش می‌دهیم.

نحوه نصب و راه اندازی LightInject
در پنجره Package Manager Console می‌توانید با نوشتن دستور ذیل، نسخه باینری آن را نصب کنید که به فایل .dll آن Reference میدهد.

PM> Install-Package LightInject
 همچنین می‌توانید توسط دستور ذیل فایل .cs آن را به پروژه اضافه نمایید. 

PM> Install-Package LightInject.Source

 آماده سازی پروژه نمونه 
قبل از شروع کار با LightInject، یک پروژه Windows Forms Application را با ساختار کلاس‌های ذیل ایجاد نمایید. (در مقالات بعدی و پس از آموزش کامل LightInject نحوه استفاده از آن را در ASP.NET MVC نیز آموزش می‌دهیم)
    public class PersonModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Family { get; set; }
        public DateTime Birth { get; set; }
    }

    public interface IRepository<T> where T:class
    {
        void Insert(T entity);
        IEnumerable<T> FindAll();
    }

    public interface IPersonRepository:IRepository<PersonModel>
    {
    }

    public class PersonRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public interface IPersonService
    {
        void Insert(PersonModel entity);
        IEnumerable<PersonModel> FindAll();
    }

    public class PersonService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public PersonService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            _personRepository.Insert(entity);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
PersonModel: ساختار داده ای جدول Person در سمت Application، که در لایه Domain Model ایجاد می‌گردد.
توجه: جهت سهولت تست و تسریع کدنویسی از لایه بندی و از کلاس‌های ViewModel استفاده نکردیم.
IRepository: یک Interface عمومی برای تمامی Interface‌های مربوط به Repository که عملیات مربوط به پایگاه داده مثل بروزرسانی و واکشی اطلاعات را انجام می‌دهند.
IPersonRepository: واسط بین لایه Service و لایه Repository می‌باشد.
PersonRepository: پیاده سازی واقعی عملیات مربوط به پایگاه داده برای PersonModel می‌باشد. به کلاسهایی که حاوی پیاده سازی واقعی کد می‌باشند Concrete Class می‌گویند.
IPersonService: واسط بین رابط کاربری و لایه سرویس می‌باشد. رابط کاربری به جای دسترسی مستقیم به PersonService از IPersonService استفاده می‌کند.
PersonService: دریافت درخواست‌های رابط کاربری و بررسی قوانین تجاری، سپس ارسال درخواست به لایه Repository در صورت صحت درخواست، و در نهایت ارسال پاسخ دریافتی به رابط کاربری. در واقع واسطی بین Repository و UI می‌باشد.
پس از ایجاد ساختار فوق کد مربوط به Form1 را بصورت زیر تغییر دهید.
public partial class Form1 : Form
    {
        private readonly IPersonService _personService;
        public Form1(IPersonService personService)
        {
            _personService = personService;
            InitializeComponent();
        }
    }
توضیحات
در کد فوق به منظور ارتباط با سرویس از IPersonService استفاده نمودیم که به عنوان پارامتر ورودی برای سازنده Form1 تعریف شده است. حتما با Dependency Inversion و انواع Dependency Injection آشنا هستید که به سراغ مطالعه این مقاله آمدید و علت این نوع کدنویسی را هم می‌دانید. بنابراین توضیح بیشتری در این مورد نمی‌دهم.
حال اگر برنامه را اجرا کنید در Program.cs با خطای عدم وجود سازنده بدون پارامتر برای Form1 مواجه می‌شوید که کد آن را باید به صورت زیر تغییر می‌دهیم.
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonRepository, PersonRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
        }
توضیحات
کلاس ServiceContainer وظیفه‌ی Register کردن یک کلاس را برای یک Interface دارد. زمانی که می‌خواهیم Form1 را نمونه سازی نماییم و Application را راه اندازی کنیم، باید نمونه ای را از جنس IPersonService ایجاد نموده و به سازنده‌ی Form1 ارسال نماییم. با رعایت اصل DIP، نمونه سازی واقعی یک کلاس لایه دیگر، نباید در داخل کلاس‌های لایه جاری انجام شود. برای این منظور از شیء container استفاده نمودیم و توسط متد GetInstance، نمونه‌ای از جنس IPersonService را ایجاد نموده و به Form1 پاس دادیم. حال container از کجا متوجه می‌شود که چه کلاسی را برای IPersonService نمونه سازی نماید؟
در خطوط قبلی توسط متد Register، کلاس PersonService را برای IPersonService ثبت نمودیم. container نیز برای نمونه سازی به کلاس هایی که برایش Register نمودیم مراجعه می‌نماید و نمونه سازی را انجام می‌دهد. جهت استفاده از PersonService به پارامتر ورودی IPersonRepository برای سازنده‌ی آن نیاز داریم که کلاس PersonRepository را برای IPersonRepository ثبت کردیم.
حال اگر برنامه را اجرا کنید، به درستی اجرا خواهد شد. برنامه را متوقف کنید و به کد موجود در Program.cs مراجعه نموده و دو خط مربوط به Register را Comment نمایید. سپس برنامه را اجرا کنید و خطای تولید شده را ببینید. این خطا بیان می‌کند که امکان نمونه سازی برای IPersonService را ندارد. چون قبلا هیچ کلاسی را برای آن Register نکرده ایم.
Named Services
در برخی مواقع، بیش از یک کلاس وجود دارند که ممکن است از یک Interface ارث بری نمایند. در این حالت و در زمان Register، باید به ServiceContainer بگوییم که کدام کلاس را باید نمونه سازی نماید. برای بررسی این موضوع، کلاسهای زیر را به ساختار پروژه اضافه نمایید.
    public class WorkerModel:PersonModel
    {
        public ManagerModel Manager { get; set; }
    }

    public class ManagerModel:PersonModel
    {
        public IEnumerable<WorkerModel> Workers { get; set; }
    }

    public class WorkerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class ManagerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class WorkerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public WorkerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var worker = entity as WorkerModel;
            _personRepository.Insert(worker);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }

    public class ManagerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public ManagerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var manager = entity as ManagerModel;
            _personRepository.Insert(manager);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
دو کلاس Manager و Worker به همراه سرویس‌ها و Repository هایشان اضافه شده اند که از IPersonService و IPersonRepository مشتق شده اند.
حال کد کلاس Program را به صورت زیر تغییر می‌دهیم
...
 var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
توضیحات
در کد فوق، چون WorkerService بعد از PersonService ثبت یا Register شده است، LightInject در زمان ارسال پارامتر به Form1، نمونه ای از کلاس WorkerService را ایجاد میکند. اما اگر بخواهیم از کلاس PersonService نمونه سازی نماید باید کد را به صورت زیر تغییر دهیم.
...
            container.Register<IPersonService, PersonService>("PersonService");
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>("PersonService")));
همانطور که مشاهده می‌نمایید، در زمان Register نامی را به آن اختصاص دادیم که در زمان نمونه سازی از این نام استفاده شده است.
اگر در زمان ثبت، نامی را به نمونه‌ی مورد نظر اختصاص داده باشیم، و فقط یک Register برای آن Interface معرفی نموده باشیم، در زمان نمونه سازی، LightInject آن نمونه را به عنوان سرویس پیش فرض در نظر می‌گیرد.
  container.Register<IPersonService, PersonService>("PersonService");
  Application.Run(new Form1(container.GetInstance<IPersonService>()));
در کد فوق، چون برای IPersonService فقط یک کلاس برای نمونه سازی معرفی شده است، با فراخوانی متد GetInstance، حتی بدون ذکر نام، نمونه ای را از کلاس PersonService ایجاد می‌کند.
IEnumerable<T>
زمانی که چند کلاس را که از یک Interface مشتق شده اند، با هم Register می‌نمایید، LightInject این قابلیت را دارد که این کلاس‌های Register شده را در قالب یک لیست شمارشی برگردانید.
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetInstance<IEnumerable<IPersonService>>();
در کد فوق لیستی با دو آیتم ایجاد می‌شود که یک آیتم از نوع PersonService و دیگری از نوع WorkerService می‌باشد. همچنین از کد زیر نیز می‌توانید استفاده کنید:
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetAllInstances<IPersonService>();
به جای متد GetInstance از متد GetAllInstances استفاده شده است.
LightInject از Collection‌های زیر نیز پشتیبانی می‌نماید:
  • Array
  • ICollection<T>
  • IList<T>
  • IReadOnlyCollection<T>
  • IReadOnlyList<T>
Values
توسط LightInject می‌توانید مقادیر ثابت را نیز تعریف کنید
            container.RegisterInstance<string>("SomeValue");
            var value = container.GetInstance<string>();
متغیر value با رشته "SomeValue" مقداردهی می‌گردد. اگر چندین ثابت رشته ای داشته باشید می‌توانید نام جداگانه ای را به هر کدام اختصاص دهید و در زمان فراخوانی مقدار به آن نام اشاره کنید.
            container.RegisterInstance<string>("SomeValue","String1");
            container.RegisterInstance<string>("OtherValue","String2");
            var value = container.GetInstance<string>("String2");
متغیر value با رشته "OtherValue" مقداردهی می‌گردد.
مطالب
پردازش داده‌های جغرافیایی به کمک SQL Server و Entity framework
پشتیبانی SQL Server از Spatial data

از SQL Server 2008 به بعد، نوع داده جدیدی به نام geography به نوع‌های قابل تعریف ستون‌ها اضافه شده‌است. در این نوع ستون‌ها می‌توان طول و عرض جغرافیایی یک نقطه را ذخیره کرد و سپس به کمک توابع توکاری از آن‌ها کوئری گرفت.


در اینجا نمونه‌ای از نحوه‌ی تعریف و همچنین مقدار دهی این نوع ستون‌ها را مشاهده می‌کنید:
 CREATE TABLE [Geo](
[id] [int] IDENTITY(1,1) NOT NULL,
[Location] [geography] NULL
)

 insert into Geo( Location , long, lat ) values
( geography::STGeomFromText ('POINT(-121.527200 45.712113)', 4326))
متد geography::STGeoFromText یک SQL CLR function است. این متد در مثال فوق، مختصات یک نقطه را دریافت کرده‌است. همچنین نیاز دارد بداند که این نقطه توسط چه نوع سیستم مختصاتی ارائه می‌شود. عدد 4326 در اینجا یک SRID یا Spatial Reference System Identifier استاندارد است. برای نمونه اطلاعات ارائه شده توسط Google و یا Bing توسط این استاندارد ارائه می‌شوند.
در اینجا متدهای توکار دیگری مانند geography::STDistance برای یافتن فاصله مستقیم بین نقاط نیز ارائه شد‌ه‌اند. خروجی آن بر حسب متر است.


پشتیبانی از Spatial Data در Entity framework

پشتیبانی از نوع مخصوص geography، در EF 5 توسط نوع داده‌ای DbGeography ارائه شد. این نوع داده‌ای immutable است. به این معنا که پس از نمونه سازی، دیگر مقدار آن قابل تغییر نیست.
در اینجا برای نمونه مدلی را مشاهده می‌کنید که از نوع داده‌ای DbGeography استفاده می‌کند:
using System.Data.Entity.Spatial;

namespace EFGeoTests.Models
{
    public class GeoLocation
    {
        public int Id { get; set; }
        public DbGeography Location { get; set; }
        public string Name { get; set; }
        public string Type { get; set; }

        public override string ToString()
        {
            return string.Format("Name:{0}, Location:{1}", Name, Location);
        }
    }
}
به همراه یک Context، تا کلاس GeoLocation در معرض دید EF قرار گیرد:
using System;
using System.Data.Entity;
using EFGeoTests.Models;

namespace EFGeoTests.Config
{
    public class MyContext : DbContext
    {
        public DbSet<GeoLocation> GeoLocations { get; set; }

        public MyContext()
            : base("Connection1")
        {
            this.Database.Log = sql => Console.Write(sql);
        }
    }
}
برای مقدار دهی خاصیت Location از نوع DbGeography می‌توان از متد ذیل استفاده کرد که بسیار شبیه به متد geography::STGeoFromText عمل می‌کند:
   private static DbGeography createPoint(double longitude, double latitude,  int coordinateSystemId = 4326)
  {
       var text = string.Format(CultureInfo.InvariantCulture.NumberFormat,"POINT({0} {1})", longitude, latitude);
       return DbGeography.PointFromText(text, coordinateSystemId);
  }


تهیه منبع داده‌ی جغرافیایی

برای تدارک یک مثال واقعی جغرافیایی، نیاز به اطلاعاتی دقیق داریم. این نوع اطلاعات عموما توسط یک سری فایل مخصوص به نام Shapefiles که حاوی اطلاعات برداری جغرافیایی هستند ارائه می‌شوند. برای نمونه اطلاعات جغرافیایی به روز ایران را از آدرس ذیل می‌توانید دریافت کنید:
http://download.geofabrik.de/asia/iran.html
http://download.geofabrik.de/asia/iran-latest.shp.zip

پس از دریافت این فایل، به تعدادی فایل با پسوندهای shp، shx و dbf خواهیم رسید.
فایل‌های shp بیانگر فرمت اشکال ذخیره شده هستند. فایل‌های shx یک سری ایندکس بوده و فایل‌های dbf از نوع بانک اطلاعاتی dBase IV می‌باشند.
همچنین اگر فایل‌های prj را باز کنید، یک چنین اطلاعاتی در آن موجودند:
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
نکته‌ی مهمی که در اینجا باید مدنظر داشت، استاندارد GCS_WGS_1984 آن است. این استاندارد معادل است با استاندارد EPSG 4326. عدد 4326 آن جهت ثبت این اطلاعات در یک بانک اطلاعاتی SQL Server حائز اهمیت است (پارامتر coordinateSystemId در متد createPoint) و ممکن است از هر فایلی به فایل دیگر متفاوت باشد.



خواند‌ن فایل‌های shp در دات نت

پس از دریافت فایل‌های shp و بانک‌های اطلاعاتی مرتبط با اطلاعات جغرافیایی ایران، اکنون نوبت به پردازش این فایل‌های مخصوص با فرمت بانک اطلاعاتی فاکس پرو مانند، رسیده‌است. برای این منظور می‌توان از پروژه‌ی سورس باز ذیل استفاده کرد:

این پروژه در خواندن فایل‌های shp بدون نقص عمل می‌کند اما توانایی خواندن نام‌های فارسی وارد شده در این نوع بانک‌های اطلاعاتی را ندارد. برای رفع این مشکل، سورس آن را از Codeplex دریافت کنید. سپس فایل Shapefile.cs را گشوده و ابتدای خاصیت Current آن‌را به نحو ذیل تغییر دهید:
        /// <summary>
        /// Gets the current shape in the collection
        /// </summary>
        public Shape Current
        {
            get 
            {
                if (_disposed) throw new ObjectDisposedException("Shapefile");
                if (!_opened) throw new InvalidOperationException("Shapefile not open.");
               
                // get the metadata
                StringDictionary metadata = null;
                if (!RawMetadataOnly)
                {
                    metadata = new StringDictionary();
                    for (int i = 0; i < _dbReader.FieldCount; i++)
                    {
                        string value = _dbReader.GetValue(i).ToString();
                        if (_dbReader.GetDataTypeName(i) == "DBTYPE_WVARCHAR")
                        {
                            // برای نمایش متون فارسی نیاز است
                            value = Encoding.UTF8.GetString(Encoding.GetEncoding(720).GetBytes(value));
                        }
                        metadata.Add(_dbReader.GetName(i),
                            value);
                    }
                }
در اینجا فقط سطر استفاده از Encoding خاصی با شماره 720 و تبدیل آن به UTF8 اضافه شده‌است. پس از آن بدون مشکل می‌توان برچسب‌های فارسی را از فایل‌های dBase IV این نوع بانک‌های اطلاعاتی استخراج کرد (اصلاح شده‌ی آن در فایل پیوست مطلب موجود است).
using System.Collections.Generic;
using System.Linq;
using Catfood.Shapefile;

namespace EFGeoTests
{
    public class MapPoint
    {
        public Dictionary<string, string> Metadata { set; get; }
        public double X { set; get; }
        public double Y { set; get; }
    }

    public static class ShapeReader
    {
        public static IList<MapPoint> ReadShapeFile(string path)
        {
            var results = new List<MapPoint>();

            using (var shapefile = new Shapefile(path))
            {
                foreach (var shape in shapefile)
                {
                    if (shape.Type != ShapeType.Point)
                        continue;

                    var shapePoint = shape as ShapePoint;
                    if (shapePoint == null)
                        continue;


                     var metadataNames = shape.GetMetadataNames();
                    if(!metadataNames.Any())
                        continue;

                    var metadata = new Dictionary<string, string>();
                    foreach (var metadataName in metadataNames)
                    {
                        metadata.Add(metadataName,shape.GetMetadata(metadataName));
                    }

                    results.Add(new MapPoint
                    {
                        Metadata = metadata,
                        X = shapePoint.Point.X,
                        Y = shapePoint.Point.Y
                    });
                }
            }

            return results;
        }
    }
}
در کدهای فوق به کمک کتابخانه‌ی C# Esri Shapefile Reader، اطلاعات نقاط بانک اطلاعاتی shape files را خوانده و به صورت لیست‌هایی از MapPoint بازگشت می‌دهیم. نکته‌ی مهم آن، Metadata است که از هر فایلی به فایل دیگر می‌توان متفاوت باشد. به همین جهت این اطلاعات را به شکل ویژگی‌های key/value در این نوع بانک‌های اطلاعاتی ذخیره می‌کنند.


افزودن اطلاعات جغرافیایی به بانک اطلاعاتی SQL Server به کمک Entity framework

فایل places.shp را در مجموعه فایل‌هایی که در ابتدای بحث عنوان شدند، می‌توانید مشاهده کنید. قصد داریم اطلاعات نقاط آن‌را به مدل GeoLocation انتساب داده و سپس ذخیره کنیم:
            var points = ShapeReader.ReadShapeFile("IranShapeFiles\\places.shp");
            using (var context = new MyContext())
            {
                context.Configuration.AutoDetectChangesEnabled = false;
                context.Configuration.ProxyCreationEnabled = false;
                context.Configuration.ValidateOnSaveEnabled = false;

                if (context.GeoLocations.Any())
                    return;

                foreach (var point in points)
                {
                    context.GeoLocations.Add(new GeoLocation
                    {
                        Name = point.Metadata["name"],
                        Type = point.Metadata["type"],
                        Location = createPoint(point.X, point.Y)
                    });
                }

                context.SaveChanges();
            }
تعریف متد createPoint را که بر اساس X و Y نقاط، معادل قابل پذیرش آن‌را جهت SQL Server تهیه می‌کند، در ابتدای بحث مشاهده کردید.
در فایل‌های مرتبط با places.shp، متادیتا name، معادل نام شهرهای ایران است و type آن بیانگر شهر، روستا و امثال آن می‌باشد.
پس از اینکه اطلاعات مکان‌های ایران، در SQL Server ذخیره شدند، نمایش بصری آن‌ها را در management studio نیز می‌توان مشاهده کرد:



کوئری گرفتن از اطلاعات جغرافیایی

فرض کنید می‌خواهیم مکان‌هایی را با فاصله کمتر از 5 کیلومتر از تهران پیدا کنیم:
            var tehran = createPoint(51.4179604, 35.6884243);

            using (var context = new MyContext())
            {
                // find any locations within 5 kilometers ordered by distance
                var locations = context.GeoLocations
                    .Where(loc => loc.Location.Distance(tehran) < 5000)
                    .OrderBy(loc => loc.Location.Distance(tehran))
                    .ToList();

                foreach (var location in locations)
                {
                    Console.WriteLine(location.Name);
                }
            }
همانطور که پیشتر نیز عنوان شد، متد Distance بر اساس متر کار می‌کند. به همین جهت برای تعریف 5 کیلومتر به نحو فوق عمل شده‌است. همچنین نحوه‌ی مرتب سازی اطلاعات نیز بر اساس فاصله از یک مکان مشخص صورت گرفته‌است.
و یا اگر بخواهیم دقیقا بر اساس مختصات یک نقطه، مکانی را بیابیم، می‌توان از متد SpatialEquals استفاده کرد:
            var tehran = createPoint(51.4179604, 35.6884243);
            using (var context = new MyContext())
            {
                // find any locations within 5 kilometers ordered by distance
                var tehranLocation = context.GeoLocations.FirstOrDefault(loc => loc.Location.SpatialEquals(tehran));
                if (tehranLocation != null)
                {
                    Console.WriteLine(tehranLocation.Type);
                }
            }

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
EFGeoTests.zip
 
بازخوردهای دوره
تزریق وابستگی‌ها
- خیر. در حالت setter هم IoC Container (حین استفاده از ابزارهای متداول تزریق وابستگی‌ها) تمام وابستگی‌ها را در حین وهله سازی کلاس ایجاد می‌کند (چه استفاده شوند یا خیر؛ چون اگر اینکار را انجام ندهد استفاده کننده خطای null reference exception را حتما دریافت خواهد کرد. از این جهت که فقط در حالت استفاده از کلاس‌های Lazy است که یک محصور کننده، ارجاع واقعی به شیء را مدیریت می‌کند و به تاخیر می‌اندازد). در حالت به تاخیر افتاده، یک وابستگی فقط زمانی وهله سازی می‌شود که ارجاعی به آن در مسیر منطقی اجرای برنامه وجود داشته باشد؛ در غیراینصورت از وهله سازی آن وابستگی استفاده نشده، صرفنظر خواهد شد.
- برای آزمایش این مساله یک break point را در سازنده کلاس‌های مورد استفاده قرار دهید.
مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت ششم - تکمیل مستندات محافظت از API
ممکن است تعدادی از اکشن متدهای API طراحی شده، محافظت شده باشند. بنابراین OpenAPI Specification تولیدی نیز باید به همراه مستندات کافی در این مورد باشد تا استفاده کنندگان از آن بدانند چگونه باید با آن کار کنند. نگارش سوم OpenAPI Specification از اعتبارسنجی و احراز هویت مبتنی بر هدرها مانند basic و یا bearer، همچنین حالت کار با API Keys مانند هدرها، کوئری استرینگ‌ها و کوکی‌ها و یا حالت OAuth2 و OpenID Connect پشتیبانی می‌کند و این موارد ذیل خواص securitySchemes و security در OpenAPI Specification ظاهر می‌شوند:
"securitySchemes":
{ "basicAuth":
       {
             "type":"http",
             "description":"Input your username and password to access this API",
             "scheme":"basic"
       }
}
…
"security":[
  {"basicAuth":[]}
]
- خاصیت securitySchemes انواع حالت‌های اعتبارسنجی پشتیبانی شده را لیست می‌کند.
- خاصیت security کار اعمال Scheme تعریف شده را به کل API یا صرفا قسمت‌های خاصی از آن، انجام می‌دهد.

در ادامه مثالی را بررسی خواهیم کرد که مبتنی بر basic authentication کار می‌کند و در این حالت به ازای هر درخواست به API، نیاز است یک نام کاربری و کلمه‌ی عبور نیز ارسال شوند. البته روش توصیه شده، کار با JWT و یا OpenID Connect است؛ اما جهت تکمیل ساده‌تر این قسمت، بدون نیاز به برپایی مقدماتی پیچیده، کار با basic authentication را بررسی می‌کنیم و اصول کلی آن از دیدگاه مستندات OpenAPI Specification تفاوتی نمی‌کند.


افزودن Basic Authentication به API برنامه

برای پیاده سازی Basic Authentication نیاز به یک AuthenticationHandler سفارشی داریم:
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace OpenAPISwaggerDoc.Web.Authentication
{
    public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock)
            : base(options, logger, encoder, clock)
        {
        }

        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
            {
                return Task.FromResult(AuthenticateResult.Fail("Missing Authorization header"));
            }

            try
            {
                var authenticationHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authenticationHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
                var username = credentials[0];
                var password = credentials[1];

                if (username == "DNT" && password == "123")
                {
                    var claims = new[] { new Claim(ClaimTypes.NameIdentifier, username) };
                    var identity = new ClaimsIdentity(claims, Scheme.Name);
                    var principal = new ClaimsPrincipal(identity);
                    var ticket = new AuthenticationTicket(principal, Scheme.Name);
                    return Task.FromResult(AuthenticateResult.Success(ticket));
                }
                return Task.FromResult(AuthenticateResult.Fail("Invalid username or password"));
            }
            catch
            {
                return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization header"));
            }
        }
    }
}
کار این AuthenticationHandler سفارشی، با بازنویسی متد HandleAuthenticateAsync شروع می‌شود. در اینجا به دنبال هدر ویژه‌ای با کلید Authorization می‌گردد. این هدر باید به همراه نام کاربری و کلمه‌ی عبوری با حالت base64 encoded باشد. اگر این هدر وجود نداشت و یا مقدار هدر Authorization، با فرمتی که مدنظر ما است قابل decode و همچنین جداسازی نبود، شکست اعتبارسنجی اعلام می‌شود.
پس از دریافت مقدار هدر Authorization، ابتدا مقدار آن‌را از base64 به حالت معمولی تبدیل کرده و سپس بر اساس حرف ":"، دو قسمت را از آن جداسازی می‌کنیم. قسمت اول را به عنوان نام کاربری و قسمت دوم را به عنوان کلمه‌ی عبور پردازش خواهیم کرد. در این مثال جهت سادگی، این دو باید مساوی DNT و 123 باشند. اگر اینچنین بود، یک AuthenticationTicket دارای Claim ای حاوی نام کاربری را ایجاد کرده و آن‌را به عنوان حاصل موفقیت آمیز بودن عملیات بازگشت می‌دهیم.

مرحله‌ی بعد، استفاده و معرفی این BasicAuthenticationHandler تهیه شده به برنامه است:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(defaultScheme: "Basic")
                    .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("Basic", null);
در اینجا توسط متد services.AddAuthentication، این scheme جدید که نام رسمی آن Basic است، به همراه Handler آن، به برنامه معرفی می‌شود.
همچنین نیاز است میان‌افزار اعتبارسنجی را نیز با فراخوانی متد app.UseAuthentication، به برنامه اضافه کرد که باید پیش از فراخوانی app.UseMvc صورت گیرد تا به آن اعمال شود:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
   // ...
            app.UseStaticFiles();

            app.UseAuthentication();

            app.UseMvc();
        }
    }
}

همچنین برای اینکه تمام اکشن متدهای موجود را نیز محافظت کنیم، می‌توان فیلتر Authorize را به صورت سراسری اعمال کرد:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
     // ...
   
            services.AddMvc(setupAction =>
            {
                setupAction.Filters.Add(new AuthorizeFilter());
        // ...


تکمیل مستندات API جهت انعکاس تنظیمات محافظت از اکشن متدهای آن

پس از تنظیم محافظت دسترسی به اکشن متدهای برنامه، اکنون نوبت به مستند کردن آن است و همانطور که در ابتدای بحث نیز عنوان شد، برای این منظور نیاز به تعریف خواص securitySchemes و security در OpenAPI Specification است:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
   // ...

                setupAction.AddSecurityDefinition("basicAuth", new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "basic",
                    Description = "Input your username and password to access this API"
                });
            });
در اینجا توسط متد setupAction.AddSecurityDefinition، ابتدا یک نام تعریف می‌شود (از این نام، در قسمت بعدی تنظیمات استفاده خواهد شد). پارامتر دوم آن همان SecurityScheme است که توضیح داده شد. برای حالت basic auth، نوع آن Http است و اسکیمای آن basic. باید دقت داشت که مقدار خاصیت Scheme در اینجا، حساس به بزرگی و کوچکی حروف است.

پس از این تنظیم اگر برنامه را اجرا کنیم، یک دکمه‌ی authorize اضافه شده‌است:


با کلیک بر روی آن، صفحه‌ی ورود نام کاربری و کلمه‌ی عبور ظاهر می‌شود:


اگر آن‌را تکمیل کرده و سپس برای مثال لیست نویسندگان را درخواست کنیم (با کلیک بر روی دکمه‌ی try it out آن و سپس کلیک بر روی دکمه‌ی execute ذیل آن)، تنها خروجی 401 یا unauthorized را دریافت می‌کنیم:


- بنابراین برای تکمیل آن، مطابق نکات قسمت چهارم، ابتدا باید status code مساوی 401 را به صورت سراسری، مستند کنیم:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(setupAction =>
            {
                setupAction.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status401Unauthorized));

- همچنین هرچند با کلیک بر روی دکمه‌ی Authorize در Swagger UI و ورود نام کاربری و کلمه‌ی عبور توسط آن، در همانجا پیام Authorized را دریافت کردیم، اما اطلاعات آن به ازای هر درخواست، به سمت سرور ارسال نمی‌شود. به همین جهت در حین درخواست لیست نویسندگان، پیام unauthorized را دریافت کردیم. برای رفع این مشکل نیاز است به OpenAPI Spec اعلام کنیم که تعامل با API، نیاز به Authentication دارد:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
    // ...

                setupAction.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "basicAuth"
                            }
                        },
                        new List<string>()
                    }
                });
            });
در اینجا OpenApiSecurityRequirement یک دیکشنری از نوع <<Dictionary<OpenApiSecurityScheme, IList<string است که کلید آن از نوع OpenApiSecurityScheme تعریف می‌شود و باید آن‌را به نمونه‌ای که توسط setupAction.AddSecurityDefinition پیشتر اضافه کردیم، متصل کنیم. این اتصال توسط خاصیت Reference آن و Id ای که به نام تعریف شده‌ی توسط آن اشاره می‌کند، صورت می‌گیرد. مقدار این دیکشنری نیز لیستی از رشته‌ها می‌تواند باشد (مانند توکن‌ها و scopes در OpenID Connect) که در اینجا با یک لیست خالی مقدار دهی شده‌است.

پس از این تنظیمات، Swagger UI با افزودن یک آیکن قفل به مداخل APIهای محافظت شده، به صورت زیر تغییر می‌کند:


در این حالت اگر بر روی آیکن قفل کلیک کنیم، همان صفحه‌ی دیالوگ ورود نام کاربری و کلمه‌ی عبوری که پیشتر با کلیک بر روی دکمه‌ی Authorize ظاهر شد، نمایش داده می‌شود. با تکمیل آن و کلیک مجدد بر روی آیکن قفل، جهت گشوده شدن پنل API و سپس کلیک بر روی try it out  آن، برای مثال می‌توان به API محافظت شده‌ی دریافت لیست نویسندگان، بدون مشکلی، دسترسی یافت:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-06.zip
مطالب
ارسال انواع بی نام (Anonymous) بازگشتی توسط Entity framework به توابع خارجی
فرض کنید ساختار زیر را در مدل ساخته شده به وسیله‌ی Entity framework در پروژه‌ی خود داریم .



جدول Post با جدول GroupsDetail ارتباط یک به چند و در مقابل ان جدول GroupsDetail با جدول PostGroup ارتباط چند به یک دارد . به زبان ساده ما تعدادی گروه بندی برای  مطالب داریم (در جدول PostGroup) و میتوانیم برای هر مطلب تعدادی از گروه‌ها را در جدول GroupsDetail مشخص کنیم ...
فکر میکنم همین مقدار توضیح به اندازه‌ی کافی برای درک روابط مشخص شده در مدل رابطه ای مفروض کفایت کند.
حالا فرض کنید ما میخواهیم لیستی از عنوان مطالب موجود به همراه [نام] گروه‌های هر مطلب را داشته باشیم .
توجه شما رو به قطعه کد ساده‌ی زیر جلب میکنم:
var context = new Models.EntitiesConnection();
var query = context.Posts.Select(pst => new {
             id = pst.id, 
             Title = pst.Title, 
             GNames = pst.GroupsDetails.Select(grd => new { Name = grd.PostGroup.name }) 
             })
             .OrderByDescending(c => c.id)
             .ToList();
همانطور که شاهد هستید در قطعه کد بالا توسط خواص راهبری (Navigation Properties ) به صورت مستقیم نام گروه‌های ثبت شده برای هر مطلب در جدول GroupsDetail را از جدول  PostGroup استخراج کردیم . خب تا اینجا مسئله ای وجود نداشت ( البته با ORM‌ها این کار بسیار سهل و اسان شده است تا جایی که در حالت عادی برای همین کار باید کلی join نویسی کرد و .... در کل خدارو شکر که از دست کارهای تکراری و خسته کننده توسط این موجودات مهربان (ORM's) نجات یافتیم )...

خب میرسیم به ادامه‌ی مطلبمون . نتیجه‌ی این query چه خواهد بود ؟ کاملا واضح است که ما تعداد دلخواهی از فیلد‌ها رو برای واکشی مشخص کردیم پس در نتیجه نوع داده ای که توسط این query بازگشت داده خواهد شد یک لیست از یک نوع بی نام میباشد ... چگونه از نتیجه‌ی بازگشتی این query در صورت ارسال اون به عنوان یک پارامتر به یک تابع استفاده کنیم ؟ اگر ما تمامی فیلد‌های جدول Post را واکشی میکریم مقدار بازگشتی یک لیست از نوع Post بود که به راحتی قابل استفاده بود مثل مثال زیر

public bool myfunc(List<Post> query)
        {
            foreach (var item in query)
            {
            .....  string title =  item.Title;
            }
...return true;
        }
.
.
.

var context = new Models.EntitiesConnection();
var queryx = context.Posts.ToList();
.... myfunc(queryx );
اما حالا با یک لیست از یک نوع بی نام  رو به رو هستیم ... چند راه مختلف برای دسترسی به این گونه از مقادیر بازگشتی داریم .
1 : استفاده از Reflection برای دسترسی به فیلدهای مشخص شده .
2 : تعریف یک مدل کامل بر اساس فیلدهای مشخص شده‌ی بازگشتی و ارسال یک لیست از نوع تعریف شده به تابع . ( به نظرم این روش خسته کنندست و زیاد شکیل نیست چون برای هر query خاص مجبوریم یک مدل کلی تعریف کنیم و این باعث میشه تعداد زیادی مدل در نهایت داشته باشیم که استفاده‌ی زیادی از اونها نمیشه عملا )
3 : استفاده از یکی از روش‌های خلاقانه‌ی تبدیل نوع Anonymous‌ها .

روش سوم روش مورد علاقه‌ی من هست و انعطاف بالایی داره و خیلی هم شیکو مجلسیه ! در ضمن این روش که قراره ذکر بشه کاربردهای فراوانی داره که این مورد فقط یکی از اونهاست
خب بریم سر اصل مطلب . به کد زیر توجه کنید :

        public static List<T> CreateGenericListFromAnonymous<T>(object obj, T example)
        {
            return (List<T>)obj;
        }
        public static IEnumerable<T> CreateEmptyAnonymousIEnumerable<T>(T example)
        {
            return new List<T>(); ;
        }

////

        public bool myfunc(object query)
        {
            var cquery = CreateGenericListFromAnonymous(query, 
                              new { 
                                       id = 0, 
                                       Title = string.Empty, 
                                       GNames = CreateEmptyAnonymousIEnumerable(new { Name = string.Empty }) 
                              });
            foreach (var item in cquery)
            {
                string title = item.Title;
                foreach (var gname in item.GNames)
                {
                    string gn = gname.Name;
                }
.
.
.
            }
            return false;
        }

.
.
.
var context = new Models.EntitiesConnection();
var query = context.Posts.Select(pst => new
                              { 
                                   id = pst.id, 
                                   Title = pst.Title,
                                   GNames = pst.GroupsDetails.Select(grd => new { Name = grd.PostGroup.name }) 
                             }).OrderByDescending(c => c.id);
            myfunc(query.ToList());
در کد بالا به صورت تو در تو از انواع بی نام استفاده شده تا مطلب براتون خوب جا بیوفته . یعنی یک نوع بی نام که یکی از فیلدهای اون یک لیست از یک نوع بی نام دیگست ... خب میبینید که خیلی راحت با دو تابع ما تبدیل انواع بی نام رو به صورت inline انجام دادیم و ازش استفاده کردیم . بدون اینکه نیاز باشه ما یک مدل مجزا ایجاد کنیم ...
تابع CreateGenericListFromAnonymous : یک object رو میگیره و اون رو به یک لیست تبدیل میکنه بر اساس نوعی که به صورت inline براش مشخص میکنیم .
تابع CreateEmptyAnonymousIEnumerable یک لیست از نوع IEnumerable رو بر اساس نوعی که به صورت inline براش مشخص کردیم بر میگردونه . دلیل اینکه من در اینجا این تابع رو نوشتم این بود که ما در query یک فیلد با نام GNames داشتیم که مجموعه ای از نام گروه‌های هر مطلب بود که از نوع IEnumerable هستش . در واقع ما در اینجا نوع بی نامی داریم که یکی از فیلدهای اون یک لیست از یک نوع بی نام دیگست .  امیدوارم مطلب براتون جا افتاده باشه.
دوستان عزیز سوالی بود در قسمت نظرات مطرح کنید.