نظرات مطالب
C# 6 - The nameof Operator
یک نکته‌ی تکمیلی: تکامل اپراتور nameof در C# 12.0

همانطور که در این مطلب مشاهده کردید، اپراتور nameof، روشی بسیار مفید جهت دسترسی به نام متغیرها، نوع‌ها و یا اعضای یک کلاس است. در C# 12، این ویژگی اندکی بهبود یافته‌است و امکان دسترسی به اطلاعات اعضای یک کلاس را هم دارد:
public class NameofClass
{
    public string SomeProperty { get; set; }

    // Now legal with C# 12
    // would show "Length" on the console
    public const string NameOfSomePropertyLength = nameof(SomeProperty.Length); 
    
    public static int StaticField;
    public const string NameOfStaticFieldMinValue  = nameof(StaticField.MinValue);

    [Description($"String {nameof(SomeProperty.Length)}")]
    public int StringLength(string s)
    {
        return s.Length;
    }
}
در این مثال، اگر سعی کنیم مقدار NameOfSomePropertyLength را در کنسول نمایش دهیم، عبارت Length ظاهر خواهد شد. تا پیش از C# 12 برای دسترسی به یک چنین قابلیتی نیاز به نمونه سازی و تولید شیءای از کلاس NameofClass فوق وجود داشت تا بتوان اپراتور nameof را به خواص آن اعمال کرد. این محدودیت در C# 12 برطرف شده‌است.

همچنین همانطور که مشاهده می‌کنید، امکان دسترسی به اطلاعات فیلدهای استاتیک و یا بکارگیری این قابلیت در Attributes هم میسر شده‌است.
مطالب
الگوی Service Locator
الگوی Service Locator، به صورت گسترده‌ای به عنوان یک ضد الگو شناخته می‌شود و هنگامیکه از این الگو استفاده می‌کنیم ما را با یک سری از مشکلات رو به رو می‌کند. ولی این الگوی طراحی به خودی خود منشاء مشکل نیست. مشکل اصلی این الگو نحوه استفاده از آن است که در این مقاله درباره آن بحث می‌کنیم. 

مشکل اصلی الگوی Service Locator
زمانیکه یک کلاس، وابسته به یک Service Locator است، آن تمام وابستگی‌های واقعی کلاس را مخفی می‌کند.
 ما نمی‌توانیم وابستگی‌ها را با نگاه کردن به تعریف سازنده‌ی کلاس بیان کنیم. در عوض، ما باید کلاس و شاید مشارکت کنندگانش را بخوانیم تا برای تشخیص اینکه چه کلاس‌های دیگری برای کار آنها لازم است. 
فرض کنید ما یک کارخانه تولید ماشین را مدل می‌کنیم. کارخانه، ماشین‌ها را تولید می‌کند و آنها را به مکان فروش می‌رساند:
class Car
{

}

class CarProducer
{
    public void DeliverTo(int carsCount, string town)
    {
        Car[] cars = new Car[carsCount];
        ...
    }
}
در حال حاضر سازنده نیاز به کمک یک نهاد دیگر حمل کننده دارد که به آن کمک می‌کند تا اتومبیل را به محل مشخص شده ارسال کند: 
class Transporter
{

    public string Name { get; private set; }

    public Transporter(string name)
    {
        this.Name = name;
    }

    public void Deliver(Car[] cars, string town)
    {
        Console.WriteLine("Delivering {0} car(s) to {1} by {2}",
                            cars.Length, town, this.Name);
    }
}
چگونه می‌توانیم تولید کننده را در این راه حل ملاقات کنیم؟ یک راه برای رسیدن به آن این است که از Service Locator استفاده کنید:
static class TransporterLocator
{
    static IList<Transporter> transporters = new List<Transporter>();

    public static void Register(Transporter transporter)
    {
        transporters.Add(transporter);
    }

    public static Transporter Locate(string name)
    {
        return
            transporters
                .Where(transporter => transporter.Name == name)
                .Single();
    }
}
این کلاس استاتیک است که مجموعه‌ای از حمل کننده‌های موجود را در آن نگهداری می‌کند و هر حمل کننده به واسطۀ نام آن شناسایی می‌شود. بنابراین زمانیکه مشتری (تولید کننده خودرو در این مورد) نیاز به یک حمل کننده دارد، فقط باید نام آن را صدا بزند:
class CarProducer
{
    public void DeliverTo(int carsCount, string town)
    {
        Car[] cars = new Car[carsCount];

        Transporter transporter = null;
        if (carsCount <= 12)
            transporter = TransporterLocator.Locate("truck");
        else
            transporter = TransporterLocator.Locate("train");

        transporter.Deliver(cars, town);

    }
}

در این راه حل، تولید کننده خودرو به سادگی از مکانیزم حمل و نقل مناسبی برای روش حمل و نقل خود استفاده می‌کند. برای تعداد کمی از اتومبیل‌ها، سازنده، از کامیون‌ها استفاده می‌کند. در غیر این صورت، مهم‌ترین معیار حمل و نقل، قطار است.  

شناسایی مشکلات Service Locator
برای درک مشکلات راه حل قبلی، باید سعی کنیم تا از آن استفاده کنیم:
TransporterLocator.Register(new Transporter("truck"));
TransporterLocator.Register(new Transporter("train"));

CarProducer producer = new CarProducer();
producer.DeliverTo(7, "Tehran");
producer.DeliverTo(74, "Tehran");
همانطور که می‌بینید، ما نمی‌توانیم از کلاس CarProducer استفاده کنیم، اگر قبل از آن، مکان را مشخص نکرده باشیم. کلاس CarProducer مستقل نیست و یکی از اصول اساسی طراحی نرم افزار را نقض می‌کند: اگر ما یک ارجاع به یک شیء داشته باشیم، آن شیء به درستی تعریف شده است. اگر ما قبل از استفاده از کلاس CarProducer محل آن را مشخص نکرده باشیم، عملیات با خطا مواجه خواهد شد: 
TransporterLocator.Register(new Transporter("truck"));

CarProducer producer = new CarProducer();
producer.DeliverTo(7, "Tehran");
producer.DeliverTo(74, "Tehran");
این قطعه از کد دارای خطاست؛ زیرا انتظار دارد قطار در Service Locator ثبت شده باشد. به صورت خلاصه همان شیء ممکن است به درستی کار کند یا با خطا رو به رو شود.
بهتر است که کلاس CarProducer را به گونه‌ای طراحی کنید که اگر اشیای مورد نیاز آن به درستی تنظیم نشده باشند، آنگاه نتوان از آن نمونه سازی کرد.

 حذف Service Locator
اگر ما ارجاعی را به یک شیء داشته باشیم، می‌خواهیم مطمئن باشیم که این شیء به خوبی تشکیل شده است و ما نمی‌خواهیم با یک سری از خطا‌های اولیه که از نیازهای اولیه شیء می‌باشند، مواجه شویم. یکی از راه‌ها برای حل این مشکل آن است که تمام وابستگی‌های اجباری  آن‌را در سازنده کلاس تعریف کنیم. به این ترتیب، اگر وابستگی‌ها در دسترس نباشند، راهی قانونی برای ساخت یک شیء وجود نخواهد داشت.
class CarProducer
{
    private Transporter truck;
    private Transporter train;

    public CarProducer(Transporter truck, Transporter train)
    {
        if (truck == null)
            throw new ArgumentNullException("truck");

        if (train == null)
            throw new ArgumentNullException("train");

        this.truck = truck;
        this.train = train;
    }

    public void DeliverTo(int carsCount, string town)
    {
        Car[] cars = new Car[carsCount];
        Transporter transporter = this.truck;
        if (carsCount > 12)
            transporter = this.train;

        transporter.Deliver(cars, town);
    }
}
در این پیاده سازی، CarProducer نیاز به تمام وابستگی‌های خود را دارد و به هیچ عنوان نمی‌توان از کلاس carProducer وهله‌ای ساخت، تا زمانیکه وابستگی‌های آن را مشخص کرده باشیم. حتی بیشتر از آن، در پیاده سازی سازنده با دو شرط محافظ آغاز می‌شود. اگر هر یک از دو حمل کننده تهی باشند، سازنده CarProducer یک استثناء را بر می‌گرداند و شیء ساخته نخواهد شد. با استفاده از این پیاده سازی، مطمئن هستیم که شیء موجود معتبر است که یک مفهوم بسیار مهم است که ما را از وضعیت ناپایدار در سیستم، در امان نگه می‌دارد.

آیا وضعیتی وجود دارد که در آن Service Locator  یک راه حل قابل قبول باشد؟

در برخی موارد بجای اینکه وابستگی‌ها را به صورت صریح قید کنیم، بهتر است از این الگو استفاده کنیم.
این مثال را میتوان از زوایای مختلفی مورد بررسی قرار داد:
    1)  ما نمی‌توانیم با نگاه کردن به پیاده سازی کلاس بفهمیم که چه شرایطی قبل از نمونه سازی از کلاس باید رعایت شده باشند.
    2) ما نمی‌توانیم بدانیم زمانیکه یک متد فراخوانی می‌شود، عملیات به درستی به انجام می‌رسد و یا با خطا رو به رو می‌شود.
    3) ما نمی‌توانیم این کلاس را در یک تست بررسی کنیم؛ زیرا آن کلاس وابسته به اشیاء مبهمی هست که در جای دیگری تنظیم شده‌اند. 
همه این مسائل جدی هستند. با این دلایل است که Service Locator به عنوان یک ضد الگو در نظر گرفته شده است. اما ... این ضد الگوی در کدها شیء گرا است. اما تمام کد‌های ما شیء گرا نیستند. 
زمانیکه ما از یک پایگاه داده رابطه‌ای در حال استفاده هستیم، منطق Persistence از حالت شیء گرایی خود خارج می‌شود. منطق Persistence به صورت عمده‌ای برای نگاشت مدل‌های داده به جداول است. منطق رابط کاربری ( User Interface ) نیز شیء گرا نیست؛ زیرا عمدتا از نگاشت بین داده ساده و عناصر رابط کاربر تشکیل شده‌است.
در نتیجه، عنصر مشترک در هر دو مورد، نگاشت است و این دقیقا همان چیزی است که Service Locator انجام می‌دهد؛ نگاشت کلید‌ها به اشیاء. پس چرا ما نباید از Service Locator در لایه‌هایی که عمدتا شیء گرا نیستند استفاده کنیم؟
 
نتیجه گیری
در این مقاله ما به الگویی پرداختیم که در عمل به صورت گسترده‌ای از آن اجتناب می‌شود. مشکل Service Locator این است که اصول طراحی شیء گرا را نقض می‌کند. اما در عین حال، مناطقی از کد وجود دارند که طبیعت آنها شیء گرا نیستند. لایه‌های Presentation و persistence شیء گرا نیستند. در عوض، آنها در حال نگاشت مدل به چیزهای دیگری، جداول و ستون در پایگاه داده و یا عناصر رابط کاربری هستند. اینها مکان هایی هستند که الگوی طراحی Service Locator را می‌توان با خیال راحت و بدون نقض هر یک از دستورالعمل‌های شیء گرایی، صرفا به این دلیل که این مکان‌ها به هیچ وجه شیء گرا نیستند، استفاده کرد.
نظرات مطالب
بررسی تفصیلی رابطه Many-to-Many در EF Code first
در صورتی که بخواهیم در جدول واسط یک مقداری را نگهداریم طرز مپ کردن به چه شکل است:
public class AA
{
     public virtual ICollection<CC> Cs { get; set; }
}

public class BB
{
    public virtual ICollection<CC> Cs { get; set; }
}

public class CC
{
        public virtual AA AA { get; set; }
        public virtual long AAId { get; set; }

        public virtual BB BB { get; set; }
        public virtual long BBId { get; set; }

        public virtual string Value{get;set}
}
نظرات مطالب
توسعه سیستم مدیریت محتوای DNTCms - قسمت ششم
تشکر .
من از جداول شما به همان روش استفاده کردم  . با تغییر یک فیلد برای حذف ، به صورت زیر
      public virtual User DeletedBy { get; set; }
        public virtual TForeignKey DeletedById { get; set; }
 و در جدول User برای هر کدام از Navigation‌ها به صورت زیر عمل کردم :
    public virtual ICollection<Category> CategoriesCreated { get; set; }
        public virtual ICollection<Category> CategoriesDeleted { get; set; }
        public virtual ICollection<Category> CategoriesModified { get; set; }

پس در این حالت بین جدول User و Category سه رابطه وجود دارد. من در سیستمم 40 جدول دارم که  در این حالت جدول User حدود 120 رابطه با جداول دیگر خواهد داشت . آیا این تعداد رابطه مشکل ساز نمی‌شود ؟ نظر شما برای این بخش چیست ؟
نظرات مطالب
EF Code First #9
برای حالت TPH فقط باید این یک تعریف را داشته باشید public DbSet<Person> People { set; get; } و دسترسی به سایر مشتقات Person، از طریق کوئری‌های ()<db.People.OfType<Coach انجام می‌شود و نه ارجاع مستقیمی به DbSet ایی که نباید داشته باشند. وجود DbSet در Context، یعنی الزام به ساخت جدول معادل.
مطالب
چند نکته کاربردی درباره Entity Framework
1) رفتار متصل و غیر متصل در EF  چیست؟
اولین نکته ای که به ذهنم می‌رسه اینه که برای استفاده از EF حتما باید درک صحیحی از رفتارها و قابلیت‌های اون داشته باشیم.  نحوه استفاده ازٍEF  رو به دو رفتار متصل و غیر متصل  تقسیم می‌کنیم.
حالت پیش فرضEF  بر مبنای رفتار متصل می‌باشد. در این حالت شما یک موجودیت رو از دیتابیس فرا می‌خونید EF  این موجودیت رو ردگیری می‌کنه اگه تغییری در اون مشاهده کنه بر روی اون برچسب "تغییر داده شد" می‌زنه و حتی اونقدر هوشمند هست که وقتی تغییرات رو ذخیره می‌کنید کوئری آپدیت رو فقط براساس فیلدهای تغییر یافته اجرا کنه. یا مثلا در صورتی که شما بخواهید به یک خاصیت رابطه ای دسترسی پیدا کنید اگر قبلا لود نشده باشه در همون لحظه از دیتابیس فراخوانی میشه،  البته این رفتارها هزینه بر خواهد بود و در تعداد زیاد موجودیت‌ها میتونه کارایی رو به شدت پایین بیاره.
رفتار متصل شاید در ویندوز اپلیکیشن کاربرد داشته باشه ولی در حالت وب اپلیکیشن کاربردی نداره چون با هر در خواستی به سرور همه چیز از نو ساخته میشه و پس از پاسخ به درخواست همه چی از بین میره.  پس DbContext  همیشه از بین می‌ره و ما برحسب نیاز، در درخواست‌های جدید به سرور ، دوباره  DbContext   رو می‌سازیم. پس از ساخته شدن DbContext  باید موجودیت مورد استفاده رو به اون معرفی کنیم و وضعیت اون موجودیت رو هم مشخص کنیم.( جدید ، تغییر یافته، حذف ، بدون تغییر ) در این حالت سیستم ردگیری تغییرات بی استفاده است و ما فقط در حال هدر دادن منابع سیستم هستیم.
در حالت متصل ما باید همیشه از یک DbContext  استفاده کنیم و همه موجودیت‌ها در آخر باید تحت نظر این DbContext باشند در یک برنامه واقعی کار خیلی سخت و پیچیده ای است. مثلا بعضی وقت‌ها مجبور هستیم از  موجودیت هایی که قبلا در حافظه برنامه بوده اند استفاده کنیم اگر این موجودیت در حافظه DbContext  جاری وجود نداشته باشه با معرفی کردن اون از طریق متد attach کار ادامه پیدا می‌کنه ولی اگر قبلا موجودیتی  در  سیستم ردگیری DbContext با همین شناسه وجود داشته باشد با خطای زیر مواجه می‌شویم.
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
 این خطا مفهوم ساده و مشخصی داره ، دو شی با یک شناسه نمی‌توانند در یک DbContext وجود داشته باشند. معمولا در این حالت ما بااین اشیا تکراری کاری نداریم و فقط به شناسه اون شی برای نشان دادن روابط نیاز داریم و از دیگر خاصیت‌های اون جهت نمایش به کاربر استفاده می‌کنیم ولی متاسفانه DbContext   نمی‌دونه چی تو سر ما می‌گذره و فقط حرف خودشو می‌زنه! البته اگه خواستید با DbContext  بر سر این موضوع گفتگو کنید از کدهای زیر استفاده کنید:
T attachedEntity = set.Find(entity.Id);
var attachedEntry = dbContext.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
خوب با توجه به صحبت‌های بالا اگر بخواهیم از رفتار غیر متصل استفاده کنیم باید تنظیمات زیر رو به متد  سازنده DbContext   اضافه کنیم. از اینجا به بعد همه چیز رو خودمون در اختیار می‌گیریم و ما مشخص می‌کنیم که کدوم موجودیت باید چه وضعیتی داشته باشه (افزودن ، بروز رسانی، حذف )و اینکه چه موقع روابط خودش را با دیگر موجودیتها فراخوانی کنه.
public DbContext()
        {
            this.Configuration.ProxyCreationEnabled = false;
            this.Configuration.LazyLoadingEnabled = false;
            this.Configuration.AutoDetectChangesEnabled = false;
         }
2) تعیین وضعیت یک موجودیت و راوبط آن در  EF چگونه است؟
با کد زیر می‌تونیم وضعیت یک موجدیت رو مشخص کنیم ، با اجرای هر یک از دستورات زیر موجودیت تحت نظر DbContext قرار می‌گیره یعنی عمل attach نیز صورت گرفته است :
dbContext.Entry(entity).State = EntityState.Unchanged ;
dbContext.Entry(entity).State = EntityState.Added ; //or  Dbset.Add(entity)
dbContext.Entry(entity).State = EntityState.Modified ;
dbContext.Entry(entity).State = EntityState.Deleted ; // or  Dbset.Remove(entity)

با اجرای این کد موجودیت  از سیستم ردگیری DbContext خارج می‌شه.
 dbContext.Entry(entity).State = EntityState.Detached;
در موجودیت‌های ساده با دستورات بالا نحوه ذخیره سازی را مشخص می‌کنیم در وضعیتی که با موجودیت‌های رابطه ای سروکار داریم باید به نکات زیر توجه کنیم.
در نظر بگیرید یک گروه از قبل وجود دارد و ما مشتری جدیدی می‌سازیم در این حالت انتظار داریم که فقط یک مشتری جدید ذخیره شده باشد:
// group id=19  Name="General"  
var customer = new Customer();
customer.Group = group;
customer.Name = "mohammadi";
dbContext.Entry(customer).State = EntityState.Added;
var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added
var groupstate = dbContext.Entry(group);// groupstate=EntityState.Added

 اگه از روش بالا استفاده کنید می‌بینید گروه General   جدیدی به همراه مشتری در  دیتابیس ساخته می‌شود.نکته مهمی که اینجا وجود داره اینه که DbContext  به id  موجودیت گروه توجهی نداره ، برای جلو گیری از این مشکل باید قبل از معرفی موجودیت‌های جدید رابطه هایی که از قبل وجود دارند را به صورت بدون تغییر attach  کنیم و بعد وضعیت جدید موجودیت رو اعمال کنیم.
// group id=19  Name="General"  
var customer = new Customer();
customer.Group = group;
 customer.Name = "mohammadi";
dbContext.Entry(group).State = EntityState.Unchanged;
dbContext.Entry(customer).State = EntityState.Added;
var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added
 var groupstate = dbContext.Entry(group);// groupstate=EntityState.Unchanged

در مجموع بهتره که موجودیت ریشه رو attach کنیم و بعد با توجه به نیاز تغییرات رو اعمال کنیم.
  // group id=19  Name="General"  
var customer = new Customer();
 customer.Group = group;
customer.Name = "mohammadi";
dbContext.Entry(customer).State = EntityState.Unchanged;
dbContext.Entry(customer).State = EntityState.Added;
var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added
var groupstate = dbContext.Entry(group);//// groupstate=EntityState.Unchanged

3) AsNoTracking   و Include  دو ابزار مهم در رفتار غیر متصل:
درصورتیکه ما تغییراتی روی داده‌ها نداشته باشیم و یا از روش‌های غیر متصل از موجودیت‌ها استفاده کنیم با استفاده از متد AsNoTracking() در زمان و حافظه سیستم صرف جویی می‌کنیم در این حالت موجودیت‌های فراخوانی شده از دیتابیس در سیستم ردگیری DbContext قرار نمی‌گیرند و  اگر  وضعیت آنها را بررسی کنیم در وضعیت Detached قرار دارند.
var customer  = dbContext.Customers.FirstOrDefault();
 var customerAsNoTracking  = dbContext.Customers.AsNoTracking().FirstOrDefault();
var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Unchanged
var customerstateAsNoTracking  = dbContext.Entry(customerAsNoTracking).State;// customerstate=EntityState.Detached

نحوه بررسی کردن موجودیت‌های موجود در سیستم ردگیری DbContext :
var Entries = dbContext.ChangeTracker.Entries();    
var AddedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Added);
var ModifiedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Modified);
var UnchangedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Unchanged);
var DeletedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Deleted);
var DetachedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Detached);//* not working !
* در نظر داشته باشید وضعیت Detached وجود خارجی ندارد و به حالتی گفته می‌شود که DbContext در سیستم رد گیری خود اطلاعی از موجودیت مورد نظر نداشته باشد.
وقتی که سیستم فراخوانی خودکار رابطه‌ها خاموش باشد باید موقع فراخوانی موجودیت‌ها روابط مورد نیاز را هم با دستور Include  در سیستم فراخوانی کنیم.
 var CustomersWithGroup = dbContext.Customers.AsNoTracking().Include("Group").ToList();
 var CustomerFull = dbContext.Customers.AsNoTracking().Include("Group").Include("Bills").Include("Bills.BillDetails").ToList();
4) از متد AddOrUpdate در در فضای نام  System.Data.Entity.Migrations استفاده نکنیم، چرا؟
 در صورتی که از فیلد RowVersion و کنترل مسایل همزمانی استفاده کرده باشیم هر وقتی متد AddOrUpdate رو فراخوانی کنیم، تغییر اطلاعات توسط دیگر کاربران نادیده گرفته می‌شود.  با توجه به این که  متد AddOrUpdate  برای عملیات Migrations در نظر گرفته شده است، این رفتار کاملا طبیعی است. برای حل این مشکل می‌تونیم این متد رو با بررسی شناسه به سادگی پیاده سازی کنیم:
 public virtual void AddOrUpdate(T entity)
        {
            if (entity.Id == 0)
                Add(entity);
            else
                Update(entity);

        }

5) اگر بخواهیم موجودیت‌های رابطه ای در دیتا گرید ویو (ویندوز فرم) نشون بدیم باید چه کار کنیم؟

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

 public class DataGridViewChildRelationTextBoxCell : DataGridViewTextBoxCell
    {
        protected override object GetValue(int rowIndex)
        {
            try
            {
                var bs = (BindingSource)DataGridView.DataSource;
                var cl = (DataGridViewChildRelationTextBoxColumn)DataGridView.Columns[ColumnIndex];
                return getChildValue(bs.List[rowIndex], cl.DataPropertyName).ToString();
            }
            catch (Exception)
            {
                return "";
            }
        }

        private object getChildValue(object dataSource, string childMember)
        {
            int nextPoint = childMember.IndexOf('.');
            if (nextPoint == -1) return dataSource.GetType().GetProperty(childMember).GetValue(dataSource, null);
            string proName = childMember.Substring(0, nextPoint);
            object newDs = dataSource.GetType().GetProperty(proName).GetValue(dataSource, null);
            return getChildValue(newDs, childMember.Substring(nextPoint + 1));
        }
    }

    public class DataGridViewChildRelationTextBoxColumn : DataGridViewTextBoxColumn
    {
        public string DataMember { get; set; }

        public DataGridViewChildRelationTextBoxColumn()
        {
            CellTemplate = new DataGridViewChildRelationTextBoxCell();
        }
    }

نحوه استفاده را در ادامه می‌بینید. این روش توسط ویزارد گریدویو هم قابل استفاده است. موقع Add کردن Column نوع اون رو روی DataGridViewChildRelationTextBoxColumn   تنظیم کنید.

GroupNameColumn= new DataGridViewChildRelationTextBoxColumn(); //from your class
GroupNameColumn.HeaderText = "گروه مشتری";
GroupNameColumn.DataPropertyName = "Group.Name"; //EF  Property: Customer.Group.Name
GroupNameColumn.Visible = true;
GroupNameColumn.Width = 300;
DataGridView.Columns.Add(GroupNameColumn);
نظرات مطالب
VS Code برای توسعه دهندگان ASP.NET Core - قسمت دوم - ایجاد و اجرای اولین برنامه
افرایش سرعت در نوشتن صفت (Attribute)‌های ویوو مدل ها
در اکثر پروژه‌ها، متن خطای اکثر ویوو مدل‌ها شبیه هم است و تفاوت خاصی ندارند؛ مثلا اتریبیوت Required، متن خطایش معمولا با این مضمون است: "لطفا فیلد ... را وارد نمایید." ما میتوانیم تمام این متن‌های خطاها را در جایی دیگر تعریف و در متن خطای اتریبیوت‌هایمان از آن استفاده کنیم که باعث میشود بعدا اگر خواستیم متن خطا‌ها را تغییر دهیم (مثلا در مورد اتریبیوت Required متن "لطفا فیلد ... را وارد نمایید" را با * عوض کنیم) در تمام پروژه این تغییر اعمال میشود و دیگر نیازی نیست تمامی متن خطاها را یکی یکی تغییر دهیم و نگهداری کد‌ها برای بعد راحت‌تر میشود.
برای شروع یک کلاس را برای متن خطاهای اتریبیوت‌هایمان تعریف میکنیم:
public static class AttributesErrorMessages
{
    public const string RequiredMessage = "لطفا {0} را وارد نمایید";
    public const string MinLengthMessage = "{0} نباید کمتر از {1} کاراکتر باشد";
    public const string MaxLengthMessage = "{0} نباید بیشتر از {1} کاراکتر باشد";
    public const string RegularExpressionMessage = "{0} را به درستی وارد نمایید";
    public const string StringLengthMessage = "{0} باید بین {2} کاراکتر و {1} کاراکتر باشد";
    public const string RemoteMessage = "با این {0} قبلا ثبت نام شده است";
}
حال برای تعریف هر ویوو مدل تنها کافی است آن را تعریف و در بالای آن، از اتریبیوت دلخواه استفاده و متن ارور آن را مطابق کلاس فوق وارد میکنیم، مثلا:
[StringLength(110, MinimumLength = 5, ErrorMessage = AttributesErrorMessages.StringLengthMessage)]
public string TestProp { get; set; }
حال میتوان کار را ساده‌تر نیز کرد و تمام اتریبیوت‌ها را به یک قطعه‌کد (Snippet) تبدیل کرد. برای این کار از طریق File>Preferences وارد منوی User Snippet میشویم و بعد زبان سی شارپ را انتخاب و بعد Snippet‌های خود را اضافه میکنیم:
{
   "Required":{
      "prefix":"required",
      "body":[
         "[Required(ErrorMessage = AttributesErrorMessages.RequiredMessage)]"
      ],
      "description":"Required attribute"
   },
   "Max Length":{
      "prefix":"maxlength",
      "body":[
         "[MaxLength(${1:number}, ErrorMessage = AttributesErrorMessages.MaxLengthMessage)]"
      ],
      "description":"Max length attribute"
   },
   "Min Length":{
      "prefix":"minlength",
      "body":[
         "[MinLength(${1:number}, ErrorMessage = AttributesErrorMessages.MinLengthMessage)]"
      ],
      "description":"Min length attribute"
   },
   "String Length":{
      "prefix":"stringlength",
      "body":[
         "[StringLength(${1:maximumNumber}, MinimumLength = ${2:minmumNumber}, ErrorMessage = AttributesErrorMessages.StringLengthMessage)]"
      ],
      "description":"String length attribute"
   },
   "Email Address":{
      "prefix":"emailaddress",
      "body":[
         "[EmailAddress(ErrorMessage = AttributesErrorMessages.RegularExpressionMessage)]"
      ],
      "description":"Email address attribute"
   },
   "Regular Expression":{
      "prefix":"regularexpression",
      "body":[
         "[RegularExpression(\"${1:patternString}\", ErrorMessage = AttributesErrorMessages.RegularExpressionMessage)]"
      ],
      "description":"Regular expression attribute"
   },
   "Remote Expression":{
      "prefix":"remote",
      "body":[
         "[Remote(\"${1:action}\", \"${2:controller}\", ErrorMessage = AttributesErrorMessages.RemoteMessage)]"
      ],
      "description":"Remote attribute"
   }
}
حال تنها کافی است اسم اتریبیوت مورد نظر را تایپ کنیم و آن Snippet را از پنجره Intellisense انتخاب کنیم.
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 6
پروژه را اجرا کنید و در WCF Test Client به وسیله‌ی متد AddNews دو خبر جدید درج کنید. 

روی متدهای GetAllCategory و GetAllNews به صورت جداگانه کلیک کنید. متوجه خواهید شد که هرچند در کلاس tblNews شی‌ای از نوع tblCategory و در کلاس tblCategory شی‌ای از نوع مجموعه‌ی tblNews به صورت Virtual تعریف شده است ولی در بر خلاف انتظارمان اثری از آن در این‌جا دیده نمی‌شود. نتیجه‌ی مشاهده‌شده به خاطر است که در هر دو تعریف صفت DataMember را به ویژگی‌های ناوبری اختصاص نداده ایم و این می‌تواند راهبرد ما در طراحی WCF باشد. ولی اگر می‌خواهید ویژگی ناوبری میان موجودیت‌ها در متدهای ما هم دیده شود ادامه‌ی این درس را بخوانید وگرنه ممکن است تصمیم داشته باشید در صورت نیاز به پیوند میان موجودیت‌ها، متد جدیدی بنویسید و از دستورهای Linq استفاده کنید و یا برای این‌کار از Stored Procedured بهره ببرید.

در اینجا من این سناریو را دنبال می‌کنم که در صورتی که متد GetAllNews اجرا شود؛ بدون این‌که نیاز باشد برای دانستن نام دسته‌ی خبر از متد دیگری مانند GetAllCategory استفاده کنیم؛ رکورد وابسته موجودیت دسته در هر خبر نشان داده شود.

از Solution Explorer فایل MyNewsModel.tt را باز کنید و دنبال کد زیر بگردید:

  public string NavigationProperty(NavigationProperty navigationProperty)
    {
        var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1} {2} {{ {3}get; {4}set; }}",
            AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
            navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
            _code.Escape(navigationProperty),
            _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
            _code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
    }

سپس آن‌را به صورت زیر ویرایش کنید:

  public string NavigationProperty(NavigationProperty navigationProperty)
    {
        var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0}{1} {2} {3} {{ {4}get; {5}set; }}",
navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many ? "[DataMember]" + Environment.NewLine : "",
            AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
            navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
            _code.Escape(navigationProperty),
            _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
            _code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
    }  

پس از ذخیره‌ی فایل، خواهید دید که صفت DataMember در کلاس tblNews پیش از ویژگی tblCategory افزوده شده است. بار دیگر پروژه را اجرا کنید. روی متد GetAllNews کلیک کنید و روی دکمه Invoke بفشارید. خواهید دید که هرچند tblCategory در ویژگی‌های آن قرار گرفته است ولی مقدار آن Null است. برای حل این مشکل باید از Solution Explorer فایل MyNewsService.cs را باز کنید و به به جای کد مربوط به متدهای GetAllNews و GetNews کدهای زیر را قرار دهید:

       public List<tblNews> GetAllNews()
        {
            return dbMyNews.tblNews.Include(p=>p.tblCategory).Where(c=>c.IsDeleted == false).ToList(); 
        }

        public tblNews GetNews(int tblNewsId)
        {
            return dbMyNews.tblNews.Include(p => p.tblCategory).FirstOrDefault(p => p.tblNewsId == tblNewsId);
        }

این بار اگر پروژه را اجرا کنید با نتیجه‌ای مانند شکل زیر روبه‌رو خواهید شد:

در بخش هفتم پیرامون میزبانی WCF Library خواهم نوشت.

مطالب
بارگذاری UserControl در WPF به کمک الگوی MVVM
در نرم افزارهای تحت ویندوز روشها و سلیقه‌های متفاوتی برای چینش فرمها ، منو‌ها و دیگر اجزای برنامه وجود دارد. در یک نرم افزار اتوماسیون اداری که فرمهای ورود اطلاعات زیادی دارد فضای کافی برای نمایش همه‌ی فرم‌ها به کاربر نیست. یکی از روش هایی که می‌تواند به کار رود تقسیم قسمت‌های مختلف نرم افزار در View‌های جداگانه است. این کار استفاده‌ی مجدد از قسمت‌های مختلف و نگهداری کد را سهولت می‌بخشد. 

الگوی متداولی که در نرم افزار‌های WPF و Silverlight استفاده می‌شود الگوی MVVM است. (این الگو در جاوااسکریپت هم به سبب Statefull بودن استفاده می‌شود.) قبلا مطالب زیادی در این سایت جهت آموزش و توضیح این الگوی منتشر شده است.
فرض کنید نرم افزار از چند بخش تشکیل شده :

  • صفحه‌ی اصلی
  • منو
  • یک صفحه‌ی خوش آمدگویی
  • صفحه‌ی ورود و نمایش اطلاعات
می توان اجزا و تعریف هر یک از این قسمت‌ها را در یک UserControl قرار داد و در زمان مناسب آن را بارگذاری کرد. 
سوالی که مطرح است بارگذاری UserControl‌ها به کمک الگوی MVVM چگونه است ؟ 
کدهای XAML صفحه‌ی اصلی : 
<Window x:Class="TwoViews.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MVVM Light View Switching"
        d:DesignHeight="300"
        d:DesignWidth="300"
        DataContext="{Binding Main,
                              Source={StaticResource Locator}}"
        ResizeMode="NoResize"
        SizeToContent="WidthAndHeight"
        mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <ContentControl Content="{Binding CurrentViewModel}" />

        <DockPanel Grid.Row="1" Margin="5">
            <Button Width="75"
                    Height="23"
                    Command="{Binding SecondViewCommand}"
                    Content="Second View"
                    DockPanel.Dock="Right" />
            <Button Width="75"
                    Height="23"
                    Command="{Binding FirstViewCommand}"
                    Content="First View"
                    DockPanel.Dock="Left" />
        </DockPanel>
    </Grid>
</Window>

2 دکمه در صفحه‌ی اصلی وجود دارد ، یکی از آنها وظیفه‌ی بارگذاری View اول و دیگری وظیفه‌ی بارگذاری View دوم را دارد ،  این دکمه‌ها نقش منو را در یک نرم افزار واقعی به عهده دارند. 
کدهای View-Model گره خورده (به کمک الگوی ViewModolLocator ) به View اصلی  : 
    /// This is our MainViewModel that is tied to the MainWindow via the 
    /// ViewModelLocator class.
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        /// <summary>
        /// Static instance of one of the ViewModels.
        /// </summary>
        private static readonly SecondViewModel SecondViewModel = new SecondViewModel();
        /// <summary>
        /// Static instance of one of the ViewModels.
        /// </summary>
        private static readonly FirstViewModel FirstViewModel = new FirstViewModel();
        /// <summary>
        /// The current view.
        /// </summary>
        private ViewModelBase _currentViewModel;
        /// <summary>
        /// Default constructor.  We set the initial view-model to 'FirstViewModel'.
        /// We also associate the commands with their execution actions.
        /// </summary>
        public MainViewModel()
        {
            CurrentViewModel = FirstViewModel;
            FirstViewCommand = new RelayCommand(ExecuteFirstViewCommand);
            SecondViewCommand = new RelayCommand(ExecuteSecondViewCommand);
        }
        /// <summary>
        /// The CurrentView property.  The setter is private since only this 
        /// class can change the view via a command.  If the View is changed,
        /// we need to raise a property changed event (via INPC).
        /// </summary>
        public ViewModelBase CurrentViewModel
        {
            get { return _currentViewModel; }
            set
            {
                if (_currentViewModel == value)
                    return;
                _currentViewModel = value;
                RaisePropertyChanged("CurrentViewModel");
            }
        }
        /// <summary>
        /// Simple property to hold the 'FirstViewCommand' - when executed
        /// it will change the current view to the 'FirstView'
        /// </summary>
        public ICommand FirstViewCommand { get; private set; }
        /// <summary>
        /// Simple property to hold the 'SecondViewCommand' - when executed
        /// it will change the current view to the 'SecondView'
        /// </summary>
        public ICommand SecondViewCommand { get; private set; }
        /// <summary>
        /// Set the CurrentViewModel to 'FirstViewModel'
        /// </summary>
        private void ExecuteFirstViewCommand()
        {
            CurrentViewModel = FirstViewModel;
        }
        /// <summary>
        /// Set the CurrentViewModel to 'SecondViewModel'
        /// </summary>
        private void ExecuteSecondViewCommand()
        {
            CurrentViewModel = SecondViewModel;
        }
    }

این ViewModel از کلاس پایه‌ی چارچوب MVVM Light مشتق شده است.  Command‌ها جهت Handle کردن کلیک دکمه‌ها هستند . نکته‌ی اصلی این ViewModel پراپرتی CurrentViewModel می‌باشد.  این پراپرتی به ویژگی Content کنترل ContentControl مقید (Bind) شده است. با کلیک شدن روی دکمه‌ها View مورد نظر به کاربر نمایش داده می‌شود.
WPF از کجا می‌داند کدام View را به ازای ViewModel خاص render کند ؟ 
در فایل App.xaml یک سری DataTemplate تعریف شده است : 
    <Application.Resources>
        <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
        <!--
            We define the data templates here so we can apply them across the
            entire application.
         
            The data template just says that if our data type is of a particular
            view-model type, then render the appropriate view.  The framework
            takes care of this dynamically.  Note that the DataContext for
            the underlying view is already set at this point, so the
            view (UserControl), doesn't need to have it's DataContext set
            directly.
        -->
        <DataTemplate DataType="{x:Type vm:SecondViewModel}">
            <views:SecondView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:FirstViewModel}">
            <views:FirstView />
        </DataTemplate>
    </Application.Resources>

به کمک این DataTemplate‌ها مشخص شده اگر نوع داده‌ی ما از یک نوع View-Model خاص می‌باشد View مناسب را به ازای آن Render کند. با تعریف DataTemplate‌ها در App.Xaml می‌توان از آنها در سطح نرم افزار استفاده کرد. می‌توان DataTemplate‌ها را جهت خلوت کردن App.xaml به Resource دیگری انتقال داد.
دریافت مثال : TwoViews.zip 

 
مطالب
MEF و الگوی Singleton

در مورد معرفی مقدماتی MEF می‌توانید به این مطلب مراجعه کنید و در مورد الگوی Singleton به اینجا.


کاربردهای الگوی Singleton عموما به شرح زیر هستند:
1) فراهم آوردن دسترسی ساده و عمومی به DAL (لایه دسترسی به داده‌ها)
2) دسترسی عمومی به امکانات ثبت وقایع سیستم در برنامه logging -
3) دسترسی عمومی به تنظیمات برنامه
و موارد مشابهی از این دست به صورتیکه تنها یک روش دسترسی به این اطلاعات وجود داشته باشد و تنها یک وهله از این شیء در حافظه قرار گیرد.

با استفاده از امکانات MEF دیگر نیازی به نوشتن کدهای ویژه تولید کلاس‌های Singleton نمی‌باشد زیرا این چارچوب کاری دو نوع روش وهله سازی از اشیاء (PartCreationPolicy) را پشتیبانی می‌کند: Shared و NonShared . حالت Shared دقیقا همان نام دیگر الگوی Singleton است. البته لازم به ذکر است که حالت Shared ، حالت پیش فرض تولید وهله‌ها بوده و نیازی به ذکر صریح آن همانند ویژگی زیر نیست:
[PartCreationPolicy(CreationPolicy.Shared)]

مثال:
فرض کنید قرار است از کلاس زیر تنها یک وهله بین صفحات یک برنامه‌ی Silverlight توزیع شود. با استفاده از ویژگی‌ Export به MEF اعلام کرده‌ایم که قرار است سرویسی را ارائه دهیم :

using System;
using System.ComponentModel.Composition;

namespace SlMefTest
{
[Export]
public class WebServiceData
{
public int Result { set; get; }

public WebServiceData()
{
var rnd = new Random();
Result = rnd.Next();
}
}

}
اکنون برای اثبات اینکه تنها یک وهله از این کلاس در اختیار صفحات مختلف قرار خواهد گرفت، یک User control جدید را به همراه یک دکمه که مقدار Result را نمایش می‌دهد به برنامه اضافه خواهیم کرد. دکمه‌ی دیگری را نیز به همین منظور به صفحه‌ی اصلی برنامه اضافه می‌کنیم.
کدهای صفحه اصلی برنامه (که از یک دکمه و یک Stack panel جهت نمایش محتوای یوزر کنترل تشکیل شده) به شرح بعد هستند:
<UserControl x:Class="SlMefTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<StackPanel>
<Button Content="MainPageButton" Height="23"
HorizontalAlignment="Left"
Margin="10,10,0,0" Name="button1"
VerticalAlignment="Top" Width="98" Click="button1_Click" />
<StackPanel Name="panel1" Margin="5"/>
</StackPanel>
</UserControl>

using System.ComponentModel.Composition;
using System.Windows;

namespace SlMefTest
{
public partial class MainPage
{
[Import]
public WebServiceData Data { set; get; }

public MainPage()
{
InitializeComponent();
this.Loaded += mainPageLoaded;
}

void mainPageLoaded(object sender, RoutedEventArgs e)
{
CompositionInitializer.SatisfyImports(this);
panel1.Children.Add(new SilverlightControl1());
}

private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Data.Result.ToString());
}
}
}
با استفاده از ویژگی Import به MEF اعلام می‌کنیم که به اطلاعاتی از نوع شیء WebServiceData نیاز داریم و توسط متد CompositionInitializer.SatisfyImports کار وهله سازی و پیوند زدن export و import های همانند صورت می‌گیرد. سپس استفاده‌ی مستقیم از Data.Result مجاز بوده و مقدار آن null نخواهد بود.

کدهای User control ساده اضافه شده به شرح زیر هستند:

<UserControl x:Class="SlMefTest.SilverlightControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<Button Content="UserControlButton"
Height="23"
HorizontalAlignment="Left"
Margin="10,10,0,0"
Name="button1"
VerticalAlignment="Top"
Width="125"
Click="button1_Click" />
</Grid>
</UserControl>

using System.ComponentModel.Composition;
using System.Windows;

namespace SlMefTest
{
public partial class SilverlightControl1
{
[Import]
public WebServiceData Data { set; get; }

public SilverlightControl1()
{
InitializeComponent();
this.Loaded += silverlightControl1Loaded;
}

void silverlightControl1Loaded(object sender, RoutedEventArgs e)
{
CompositionInitializer.SatisfyImports(this);
}

private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Data.Result.ToString());
}
}
}
اکنون قبل از شروع برنامه یک break point را در سازنده‌ی کلاس WebServiceData قرار دهید. سپس برنامه را آغاز نمائید. تنها یکبار این سازنده فراخوانی خواهد شد (هر چند در دو کلاس کار Import اطلاعات WebServiceData صورت گرفته است). همچنین با کلیک بر روی دو دکمه‌ای که اکنون در صفحه‌ی اصلی برنامه ظاهر می‌شوند، فقط یک عدد مشابه نمایش داده می‌شود (با توجه به اینکه اطلاعات هر دکمه در یک وهله‌ی جداگانه قرار دارد؛ یکی متعلق است به صفحه‌ی اصلی و دیگری متعلق است به user control اضافه شده).