نظرات اشتراک‌ها
معرفی کتابخانه‌ی DNTPersianUtils.Core
با سلام 
موقع استفاده از dt.ToGregorianDateTime خطای
Cannot implicitly convert type 'System.DateTime?' to 'System.DateTime'. An explicit conversion exists
میگیره (  مدل:      public string UserBirthday { get; set; }  ) 
چطوری میتونم حلش کنم ؟  
و اینکه برای تبدیل یکسری روزها قبلا به مشکل برمیخوردیم مثل تاریخ‌های 02/29 یا 02/30. برای این تاریخ‌ها مشکلی بوجود نمیاد؟
برای ثبت تاریخ در دیتابیس ، شمسی وارد بشه بهتره یا میلادی؟
مطالب
MongoDb در سی شارپ (بخش هفتم)
در اینجا قصد داریم که دیتاهای استاتیک و دیتاهای پویا را بررسی کنیم. همانطور که میدانید مونگو تنها خواصی را که در کلاس وجود دارند ذخیره میکند و همان‌ها را هم در برگشت به کلاس انتساب میدهد. ولی ممکن است برای بعضی از اسناد هر بار فیلدهایی را تعریف کنیم که در کلاس اصلی پراپرتی برای آن وجود ندارد. فیلدهایی که ممکن است در زمان اجرا آن‌ها را بشناسیم. برای این کار دو روش متفاوت توسط تیم فنی مونگو پیشنهاد شده است.
اولین روش این است که یک پراپرتی را مثلا به عنوان متادیتا به کلاس اضافه و در قالب کلید و مقدار آن‌ها را مقدار دهی کنیم:
public class Book:Entity
    {
        public string Title { get; set; }
        public string ISBN { get; set; }
        public int Price { get; set; }
        public List<Author> Authors { get; set; }
        public BsonDocument ExtraFields { get; set; }
        public Language Language { get; set; }
        public ObjectId Image { get; set; }
        public int Year { get; set; }
        public DateTime LastStock { get; set; }

    }
 در مدل Book، یک فیلد را به نام ExtraField اضافه میکنیم و نوع آن را BsonDocument میگذاریم . آن را به شکل زیر مقدار دهی می‌کنیم:
     var book = new Book()
            {
      
                Title = "Gone With Wind",
                ISBN = "43442424",
                Price = 50000,
                Year = 1936,
                LastStock = DateTime.Now.AddDays(-13),
                Language = new Language()
                {
                    Name = "Persian"
                },
                Authors = new List<Author>()
               {
                   new Author()
                   {
                       Name = "Margaret Mitchell"
                   }
               },
                ExtraFields=new BsonDocument("Translator", "Ali Mahboobi")
            };
در اینجا ما یک فیلد را اضافه کرد‌ه‌ایم که نام آن Translator بوده و مقدارش را علی محبوبی گذاشتیم. اگر به سند ذخیره شده‌ی آن نگاهی بیندازیم می‌بینیم که این دیتا به شکل زیر ذخیره شده است:

همینطور که می‌بینید این فیلد جدید به عنوان یک شیء جدا یا یک سند توکار ذخیره شده‌است. ولی اگر میخواهید به عنوان یک فیلد، همانند دیگر فیلدها ذخیره شود، باید فیلد ExtraField را به ویژگی BsonExtraElement مزین کنید. پس مدل را به شکل زیر بازنویسی میکنیم:

public class Book:Entity
    {
        public string Title { get; set; }
        public string ISBN { get; set; }
        public int Price { get; set; }
        public List<Author> Authors { get; set; }
        [BsonExtraElements]
        public BsonDocument ExtraFields { get; set; }
        public Language Language { get; set; }
        public ObjectId Image { get; set; }
        public int Year { get; set; }
        public DateTime LastStock { get; set; }

    }
حال اگر مقادیر ذخیره شده را بررسی کنیم، باید شکل زیر را ببینید:


الان translator همانند دیگر فیلدها به یک شکل نمایش داده میشود.
در این روش فقط تیم مونگو اخطار داده است که مراقب باشید قبلا فیلدی به این نام نبوده باشد تا بعدا دچار مشکل و تصادم شود.
مطالب
مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
در مورد طراحی Self Referencing Entities پیشتر مطلبی را در این سایت مطالعه کرده‌اید .
یک مثال دیگر آن می‌تواند نظرات چند سطحی در یک سایت باشند. نحوه تعریف آن با مطالبی که در قسمت هشتم عنوان شود تفاوتی نمی‌کند؛ اما ... زمانیکه نوبت به نمایش آن فرا می‌رسد، چند نکته اضافی را باید درنظر گرفت. ابتدا مثال کامل زیر را در نظر بگیرید:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;

namespace EFGeneral
{
    public class BlogComment
    {
        public int Id { set; get; }

        [MaxLength]
        public string Body { set; get; }

        public virtual BlogComment Reply { set; get; }
        public int? ReplyId { get; set; }
        public ICollection<BlogComment> Children { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<BlogComment> BlogComments { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // Self Referencing Entity
            modelBuilder.Entity<BlogComment>()
                        .HasOptional(x => x.Reply)
                        .WithMany(x => x.Children)
                        .HasForeignKey(x => x.ReplyId)
                        .WillCascadeOnDelete(false);

            base.OnModelCreating(modelBuilder);
        }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            var comment1 = new BlogComment { Body = "نظر من این است که" };
            var comment12 = new BlogComment { Body = "پاسخی به نظر اول", Reply = comment1 };
            var comment121 = new BlogComment { Body = "پاسخی به پاسخ به نظر اول", Reply = comment12 };

            context.BlogComments.Add(comment121);
            base.Seed(context);
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());

            using (var ctx = new MyContext())
            {
                var list = ctx.BlogComments
                          //.where ...
                          .ToList() // fills the childs list too
                          .Where(x => x.Reply == null) // for TreeViewHelper                        
                          .ToList();

                if (list.Any())
                {

                }
            }
        }
    }
}

در مثال فوق کلاس نظرات به صورت خود ارجاع دهنده (خاصیت Reply به همین کلاس اشاره می‌کند) تعریف شده است تا کاربران بتوانند تا هر چند سطح لازم، به یک نظر خاص، پاسخ دهند.
در اینجا یک چنین جدولی با اطلاعاتی که ملاحظه می‌کنید تشکیل خواهند شد:


یک نظر ارائه شده و سپس دو نظر تو در توی دیگر برای این نظر ثبت شده است.

اولین نکته اضافه‌‌تری که نسبت به قسمت هشتم قابل ملاحظه است، تعریف خاصیت جدید Children به نحو زیر می‌باشد:
    public class BlogComment
    {
        // ... 
        public ICollection<BlogComment> Children { get; set; }
    }
این خاصیت تاثیری در نحوه تشکیل جدول ندارد. علت تعریف آن به توانمندی EF در پرکردن خودکار آن بر می‌گردد.
اگر به کوئری نوشته شده در متد RunTests دقت کنید، ابتدا یک ToList نوشته شده است. این مورد سبب می‌شود که تمام رکوردهای مرتبط دریافت شوند. مثلا در اینجا سه رکورد دریافت می‌شود. سپس برای اینکه حالت درختی آن حفظ شود، در مرحله بعد ریشه‌ها فیلتر می‌شوند (مواردی که reply آن‌ها null است). سپس این مورد تبدیل به list خواهد شد. اینبار اگر خروجی را بررسی کنیم، به ظاهر فقط یک رکورد است اما ... به نحو زیبایی توسط EF به شکل یک ساختار درختی، بدون نیاز به کدنویسی خاصی، منظم شده است:


سؤال:
برای نمایش این اطلاعات درختی و تو در تو در یک برنامه وب چکار باید کرد؟
تا اینجا که توانستیم اطلاعات را به نحو صحیحی توسط EF مرتب کنیم، برای نمایش آن‌ها در یک برنامه ASP.NET MVC می‌توان از یک TreeViewHelper سورس باز استفاده کرد.
البته کد آن در اصل برای استفاده از EF Code first طراحی نشده و نیاز به اندکی تغییر به نحو زیر دارد تا با EF هماهنگ شود (متد ToList و Count موجود در سورس اصلی آن باید به نحو زیر حذف و اصلاح شوند):
private void AppendChildren(TagBuilder parentTag, T parentItem, Func<T, IEnumerable<T>> childrenProperty)
        {
            var children = childrenProperty(parentItem);
            if (children == null || !children.Any())
            {
                return;
            }
//...

 
مطالب
پراپرتی سفارشی در EF Database First
فرض کنید در روش EF Database First می‌خواهید فیلدی به مدل اضافه شود که در دیتابیس وجود ندارد، درواقع فیلدی محاسباتی به مدل اضافه کنید.راه حل چیست؟ اولین روشی که ممکن است به ذهن برسد این است که به کلاس مدل که از جدول دیتابیس ساخته شده، فیلدی محاسباتی اضافه می‌کنیم.  
public class Person
{
    public string FullName {
        get
        {
            return FirstName + " " + LastName;
        }
    }
}
 به نظر راه حل درستی می‌رسد، اما مشکل این روش چیست؟
 اگر مدل برنامه از روی دیتابیس بروزشود، چه اتفاقی می‌افتد؟
خب قاعدتا این فیلد محاسباتی از دست می‌رود و باید دوباره آن را به کلاس مدل جدید و بروز شده اضافه کنیم. که به نظر راه حل منطقی و خوبی نمیرسد. در چنین مواقعی می‌توان به جای اینکه این پراپرتی را به کلاس تولید شده از روی دیتابیس اضافه کرد، این فیلد را به یک Partial Class از کلاس تولید شده که درهمان پروژه و فضای نام کلاس تولید شده از دیتابیس قراردارد، اضافه کرد. به این ترتیب با هر بار بروز شدن مدل از روی دیتابیس، این فیلد از بین نمی‌رود. 
public partial class Person
{
    public string FirstName {get; set;}
    public string LastName {get; set; }
}
public partial class Person
{
    public string FullName {
        get
        {
            return FirstName + " " + LastName;
        }
    }
}

اما این روش محدودیت هایی نیز دارد :
1. همه قسمت‌های Partial Class باید در یک اسمبلی باشند.
2. پراپرتی‌های درون Partial Class در دیتابیس ذخیره نمی‌شوند.
3. ونیز این پراپرتی‌ها در عبارات Linq قابل استفاده نیستند.چون عبارت Linq در نهایت به یک رشته SQL تبدیل شده و در دیتابیس اجرا می‌شود.البته با فرض این که دیتاپرووایدر، SQL  باشد.
 
مطالب
نگاشت اشیاء در AutoMapper توسط Attribute ها #1
نگاشت اشیاء امری مفید و لذت بخش است. ولی بخاطر تنظیمات خاص آن و افزایش کدها، همیشه کمی دردسر ساز بوده است. استفاده از کلاس Profile راه کار مناسبی است؛ اما در این حالت کلاس مقصد (ViewModel) از تنظیمات نگاشت‌ها بی اطلاع می‌ماند و فقط حاوی داده خواهد بود. برای ادغام کلاس و تنظیمات نگاشت در اینجا راهکاری ارائه گردید که در ادامه و با الگو گیری از همین ایده، اقدام به ارائه‌ی روشی جدید می‌کنم که با استفاده از Attribute‌ها تنظیمات نگاشت اشیاء را در AutoMapper انجام می‌دهد.
در نهایت می‌خواهیم نگاشت‌ها را اینچنین تنظیم کنیم:
 [MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)]
 public class AdminStudentViewModel
 {
     // [IgnoreMap]
     public int Id { set; get; }

     [MapForMember("Name")]
     public string FirstName { set; get; }

     [MapForMember("Family")]
     public string LastName { set; get; }

     public string Email { set; get; }

     [MapForMember("RegisterDateTime")]
     public string RegisterDateTimePersian { set; get; }

     [UseValueResolver(typeof (BookCountValueResolver))]
     public int BookCounts { set; get; }

     [UseValueResolver(typeof (BookPriceValueResolver))]
     public decimal BookPrice { set; get; }
 };
  این سبک تنظیم کردن نگاشت‌های اشیاء به نظر بهتر از روش‌های دیگر است؛ چون کلاس‌های ویوومدل را معنادار کرده و همچنین برای برنامه نویسان EF و ASP.NET MVC استفاده‌ی از ویژگی‌ها، یک شیوه‌ی کاری معمول به حساب می‌آید. 
به تعریف و توضیح صفت‌های (ویژگی‌ها یا Attributes) مورد نیاز می‌پردازم:

صفت MapFromAttribute

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MapFromAttribute : Attribute
{
    public Type SourceType { get; private set; }
    public bool IgnoreAllNonExistingProperty { get; private set; }
    public bool AlsoCopyMetadata { get; private set; }    //Go to: https://www.dntips.ir/courses/topic/16/cb36bc2e-4263-431e-86a5-236322cb5576

    public MapFromAttribute(Type sourceType, bool ignoreAllNonExistingProperty = false,
        bool alsoCopyMetadata = false)
    {
        SourceType = sourceType;
        IgnoreAllNonExistingProperty = ignoreAllNonExistingProperty;
        AlsoCopyMetadata = alsoCopyMetadata;
    }
};
این صفت روی کلاس‌ها می‌نشیند و توسط آرگومان sourceType آن، نوع مبدأ را برای automapper مشخص می‌کند. در واقع همه چیز از اینجا شروع می‌شود. همچنین آرگومان ignoreAllNonExistingProperty مشخص می‌کند کلیه‌ی صفاتی که در مقصد هستند ولی معادل اسمی در مبدأ ندارند، بصورت خودکار رد (Ignore) شده و از آن‌ها صرف نظر شود تا از شکست متد AutoMapper.Mapper.AssertConfigurationIsValid جلوگیری کند (پرداخته شده در اینجا). آرگومان alsoCopyMetadata پیاده سازی نمی‌شود؛ ولی می‌تواند پرچمی باشد تا اجازه دهد  Data Annotations از مدل‌های ef به ViewModel انتقال یابند.


صفت IgnoreMapAttribute
[AttributeUsage(AttributeTargets.Property)]
public class IgnoreMapAttribute : Attribute {};
از این صفت برای رد کردن خصیصه‌‌ای در نگاشت‌ها استفاده می‌کنیم. لازم به ذکر است که صفتی مشابه در Automapper.IgnoreAttribute وجود دارد که می‌تواند به جای این صفت مورد استفاده قرار گیرد. «نگارنده جهت همخوانی با سایر صفات، اقدام به استفاده‌ی از این صفت می‌کند»


صفت MapForMemberAttribute
[AttributeUsage(AttributeTargets.Property)]
public class MapForMemberAttribute : Attribute
{
    public string MemberToMap { get; private set; }
    public MapForMemberAttribute(string memberToMap)
    {
        MemberToMap = memberToMap;
    }
};
اگر نام خصیصه‌ها در مبدأ و مقصد یکی نباشند، از این صفت برای همگام سازی این دو استفاده می‌کنیم.


صفت UseValueResolverAttribute
[AttributeUsage(AttributeTargets.Property)]
public class UseValueResolverAttribute : Attribute
{
    public IValueResolver ValueResolver { get; private set; }
    public UseValueResolverAttribute(Type valueResolver)
    {
        ValueResolver = valueResolver.GetConstructors()[0].Invoke(new object[] {}) as IValueResolver;
    }
};
استفاده از ValueResolver‌ها در اینجا ذکر شده است. از این صفت برای تنظیم این مقدار برای یک خصیصه استفاده می‌شود. برای مثال فیلد FullName را در مقصد درنظر بگیرد که از دو فیلد Name و Family در مبدأ تشکیل می‌شود.

تا اینجا صفات پیش نیاز کار فراهم شدند. حال باید این صفت‌ها را به نگاشت متناسبی در automapper تبدیل کنیم.
دریافت کدها
ادامه دارد...
مطالب
به روز رسانی تمام فیلدهای رشته‌ای تمام جداول بانک اطلاعاتی توسط Entity framework 6.x
یکی از مراحلی که پس از ارتقاء یک سایت به HTTPS باید صورت گیرد، به روز رسانی آدرس‌های قدیمی درج شده‌ی در صفحات مختلف، از HTTP به HTTPS است؛ وگرنه با خطای «قسمتی از صفحه امن نیست» توسط مرورگر مواجه خواهیم شد:


روش‌های زیادی برای مدیریت این مساله وجود دارند؛ مانند استفاده از ماژول‌های URL Rewrite برای بازنویسی آدرس‌های نهایی صفحه‌ی در حال رندر و یا ... به روز رسانی مستقیم بانک اطلاعاتی، یافتن تمام فیلدهای رشته‌ای ممکن در تمام جداول موجود و سپس اعمال تغییرات.


یافتن لیست تمام جداول قابل مدیریت توسط Entity framework

در ابتدا می‌خواهیم لیست پویای تمام جداول مدیریت شده‌ی توسط EF را پیدا کنیم. از این جهت که نمی‌خواهیم به ازای هر کدام یک کوئری جداگانه بنویسیم.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EFReplaceAll.Models;

namespace EFReplaceAll.Config
{
    public class DbSetInfo
    {
        public IQueryable<object> DbSet { set; get; }
        public Type DbSetType { set; get; }
    }

    public class MyContext : DbContext
    {
        public DbSet<Product> Products { set; get; }
        public DbSet<Category> Categories { set; get; }
        public DbSet<User> Users { set; get; }

        public MyContext()
            : base("Connection1")
        {
            this.Database.Log = sql => Console.Write(sql);
        }

        public IList<DbSetInfo> GetAllDbSets()
        {
            return this.GetType()
                .GetProperties()
                .Where(p => p.PropertyType.IsGenericType &&
                            p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
                .Select(p => new DbSetInfo
                {
                    DbSet = (IQueryable<object>)p.GetValue(this, null),
                    DbSetType = p.PropertyType.GetGenericArguments().First()
                })
                .ToList();
        }
    }
}
در اینجا متد GetAllDbSets، به صورت پویا لیست DbSetها را به همراه نوع جنریک آن‌ها، بازگشت می‌دهد. با استفاده از این لیست می‌توان رکوردهای تمام جداول را واکشی و سپس تغییر داد.


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

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

namespace EFReplaceAll.Utils
{
    public class ReplaceOp
    {
        public string ToFind { set; get; }
        public string ToReplace { set; get; }
        public StringComparison Comparison { set; get; }
    }
}
در اینجا خاصیت Comparison امکان جستجو و جایگزینی غیرحساس به حروف کوچک و بزرگ را نیز میسر می‌کند.

سپس متدی که کار یافتن تمام فیلدهای رشته‌ای و سپس جایگزین کردن آن‌ها را انجام می‌دهد به صورت زیر خواهد بود:
using System.Collections.Generic;
using System.Linq;
using EFReplaceAll.Config;

namespace EFReplaceAll.Utils
{
    public static class UpdateDbContextContents
    {
        public static void ReplaceAllStringsAcrossTables(IList<ReplaceOp> replaceOps)
        {
            int dbSetsCount;
            using (var uow = new MyContext())
            {
                dbSetsCount = uow.GetAllDbSets().Count;
            }

            for (var i = 0; i < dbSetsCount; i++)
            {
                using (var uow = new MyContext()) // using a new context each time to free resources quickly.
                {
                    var dbSetResult = uow.GetAllDbSets()[i];
                    var stringProperties = dbSetResult.DbSetType.GetProperties()
                        .Where(p => p.PropertyType == typeof(string))
                        .ToList();
                    var dbSetEntities = dbSetResult.DbSet;
                    var haveChanges = false;
                    foreach (var entity in dbSetEntities)
                    {
                        foreach (var stringProperty in stringProperties)
                        {
                            var oldPropertyValue = stringProperty.GetValue(entity, null) as string;
                            if (string.IsNullOrWhiteSpace(oldPropertyValue))
                            {
                                continue;
                            }

                            var newPropertyValue = oldPropertyValue;
                            foreach (var replaceOp in replaceOps)
                            {
                                newPropertyValue = newPropertyValue.ReplaceString(replaceOp.ToFind, replaceOp.ToReplace, replaceOp.Comparison);
                            }
                            if (oldPropertyValue != newPropertyValue)
                            {
                                stringProperty.SetValue(entity, newPropertyValue, null);
                                haveChanges = true;
                            }
                        }
                    }

                    if (haveChanges)
                    {
                        uow.SaveChanges();
                    }
                }
            }
        }

    }
}
توضیحات:
- در اینجا using (var uow = new MyContext()) را زیاد مشاهده می‌کنید. علت اینجا است که اگر تنها با یک Context کار کنیم، EF تمام تغییرات و تمام رکوردهای وارد شده‌ی به آن‌را کش می‌کند و مصرف حافظه‌ی برنامه با توجه به خواندن تمام رکوردهای بانک اطلاعاتی توسط آن، ممکن است به چند گیگابایت برسد. به همین جهت از Contextهایی با طول عمر کوتاه استفاده شده‌است تا میزان مصرف RAM این متد سبب کرش برنامه نشود.
- در ابتدای کار توسط متد GetAllDbSets که به Context اضافه کردیم، تعداد DbSetهای موجود را پیدا می‌کنیم تا بتوان بر روی آن‌ها حلقه‌ای را تشکیل داد و به ازای هر کدام یک (()using (var uow = new MyContext را تشکیل داد.
- سپس با استفاده از نوع DbSet که توسط خاصیت dbSetResult.DbSetType در دسترس است، خواص رشته‌ای ممکن این DbSet یافت می‌شوند.
- در ادامه dbSetResult.DbSet یک Data Reader را به صورت پویا بر روی DbSet جاری باز کرده و تمام رکوردهای این DbSet را یک به یک بازگشت می‌دهد.
- در اینجا با استفاده از Reflection، از رکورد جاری، مقادیر خواص رشته‌ای آن دریافت شده و سپس کار جستجو و جایگزینی انجام می‌شود.
- در آخر هم فراخوانی uow.SaveChanges کار ثبت تغییرات صورت گرفته را انجام می‌دهد.


متدی برای جایگزینی غیرحساس به حروف بزرگ و کوچک

متد استاندارد Replace رشته‌ها، حساس به حروف بزرگ و کوچک است. یک نمونه‌ی عمومی‌تر را که در آن بتوان StringComparison.OrdinalIgnoreCase را تعیین کرد، در ذیل مشاهده می‌کنید که از آن در متد ReplaceAllStringsAcrossTables فوق استفاده شده‌است:
using System;
using System.Text;

namespace EFReplaceAll.Utils
{
    public static class StringExtensions
    {
        public static string ReplaceString(this string src, string oldValue, string newValue, StringComparison comparison)
        {
            if (string.IsNullOrWhiteSpace(src))
            {
                return src;
            }

            if (string.Compare(oldValue, newValue, comparison) == 0)
            {
                return src;
            }

            var sb = new StringBuilder();

            var previousIndex = 0;
            var index = src.IndexOf(oldValue, comparison);

            while (index != -1)
            {
                sb.Append(src.Substring(previousIndex, index - previousIndex));
                sb.Append(newValue);
                index += oldValue.Length;

                previousIndex = index;
                index = src.IndexOf(oldValue, index, comparison);
            }

            sb.Append(src.Substring(previousIndex));

            return sb.ToString();
        }
    }
}
و در آخر یک مثال از استفاده‌ی این متد تهیه شده، جهت به روز رسانی لینک‌های HTTP به HTTPS در تمام جداول برنامه به صورت زیر است:
            UpdateDbContextContents.ReplaceAllStringsAcrossTables(
                new[]
                {
                    new ReplaceOp
                    {
                        ToFind = "https://www.dntips.ir",
                        ToReplace = "https://www.dntips.ir",
                        Comparison = StringComparison.OrdinalIgnoreCase
                    },
                    new ReplaceOp
                    {
                        ToFind = "https://www.dntips.ir",
                        ToReplace = "https://www.dntips.ir",
                        Comparison = StringComparison.OrdinalIgnoreCase
                    }
                });

کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: EFReplaceAll.zip
مطالب
Functional Programming یا برنامه نویسی تابعی - قسمت سوم – Immutability

در ادامه مطالب مربوط به برنامه نویسی تابعی، قصد دارم بیشتر وارد کد شویم و مباحث عنوان شده را در دنیای کد پیاده سازی کنیم. هدف این قسمت، refactor کردن کد موجود به یک معماری immutable هست.  پیشتر درباره immutable ‌ها صحبت کردیم. ابتدا برای یکسان سازی ادبیات مورد استفاده، چند کلمه را مجددا تعریف خواهیم کرد:

  • Immutability: عدم توانایی تغییر داده
  • State: داده‌هایی که در طول زمان تغییر می‌کنند
  • Side Effect: تغییری که روی داده‌ها اتفاق می‌افتد

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

    //Stateful
    public class UserProfile
    {
        private User _user;
        private string _address;

        public void UpdateUser(int userId, string name)
        {
            _user = new User(userId, name);
        }
    }

    //Stateless
    public class User
    {
        public User(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; }
        public string Name { get; }
    }


چرا Immutable بودن مهم است؟ 

هر عمل mutable  معادل کدی غیر شفاف است. در واقع وابستگی هر عملی که انجام می‌دهیم به state، باعث می‌شود که شرایط ناپایداری را در کد داشته باشیم. به طور مثال در یک عملیات چند نخی تصور کنید که چندین نخ به طور همزمان می‌توانند state را تغییر دهند و مدیریت این قضیه باعث به وجود آمدن کد‌هایی ناخوانا و تحمیل پیچیدگی بیشتر به کد خواهد شد. 

در واقع انتظار داریم که به ازای یک ورودی بر اساس بدنه‌ی متد، یک خروجی داشته باشیم؛ ولی در واقعیت تاثیری که اجرای متد بر روی state کل کلاس خواهد گذاشت، از دید ما پنهان است و باعث به وجود آمدن مشکلات بعدی خواهد شد. برای مثال قطعه کد بالا را به صورت Honest بازنویسی میکنیم: 

    public class UserProfile
    {
        private readonly User _user;
        private readonly string _address;

        public UserProfile(User user,string address)
        {
            _user = user;
            _address = address;
        }
        public UserProfile UpdateUser(int userId, string name)
        {
            var newUser = new User(userId, name);
            return  new UserProfile(newUser,_address);
        }
    }

    public class User
    {
        public User(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; }
        public string Name { get; }
    }

در این مثال متد UpdateUser به جای  void، یک شی از جنس کلاس UserProfile را بر می‌گرداند. کلاس UserProfile هم برای وهله سازی نیاز به یک شیء از جنس User و Address را دارد. بنابراین مطمئن هستیم که مقدار دهی شده‌اند. نکته دیگر در قطعه کد بالا این است که به ازای هر بار فراخوانی متد، یک شیء جدید بدون وابستگی به وهله سازی اشیاء دیگر، برگردانده میشود.


Immutable بودن باعث می‌شود: 

  • خوانایی کد افزایش پیدا کند
  • جای واحدی برای Validate کردن داشته باشیم
  • به صورت ذاتی Thread Safe باشیم


در مورد محدودیت‌هایی که در کار با اشیاء Immutable باید در نظر داشته باشیم، می‌توان به مصرف بالای رم و سی پی یو، اشاره کرد. در واقع به نسبت حالت mutate، تعداد اشیاء بیشتری ساخته خواهند شد. در فریمورک دات نت برای کار با اشیا immutable امکاناتی در نظر گرفته شده که این هزینه را کاهش می‌دهند. به طور مثال می‌توانیم از کلاس ImmutableList استفاده کنیم و از ایجاد اشیاء اضافه‌تر و تحمیل بار اضافی به GC جلوگیری کنیم. یک مثال: 

//Create Immutable List
ImmutableList<string> list = ImmutableList.Create<string>();
ImmutableList<string> list2 = list.Add("Salam");

//Builder
ImmutableList<string>.Builder builder = ImmutableList.CreateBuilder<string>();
builder.Add("avali");
builder.Add("dovomi");
builder.Add("sevomi");

ImmutableList<string> immutableList = builder.ToImmutable();


چطور با side effect کنار بیایم؟ 

یکی از الگوهای رایج برای این کار، مفهوم جدا سازی Command/Query است. به طور ساده تمامی عملیاتی را که تاثیر گذار هستند، به صورت Command در نظر میگیریم. Command ‌ها معمولا هیچ نوعی را بازگشت نمیدهند و همینطور بر عکس این قضیه برای Query ‌ها صادق است. اشتباه رایج درباره این الگو، محدود کردن این الگو به معماری‌های خاصی مانند Domain Driven می‌باشد؛ در صورتیکه الزامی برای رعایت این الگو در سایر معماری‌ها وجود ندارد. 

به مثال زیر دقت کنید. سعی کردم قسمت‌های Command و Query را از هم جدا کنم: 

در واقع هر برنامه می‌تواند شامل دو قسمت باشد:

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

در واقع یک هسته Immutable، ورودی را دریافت کرده و خروجی‌های مورد نیاز را تولید میکند و همه این‌ها در دل یک پوستهMutable پیاده سازی می‌شوند که ما در اینجا به آن اصطلاحا Mutable Shell میگوییم.   

برای مسائلی که در بالا صحبت شد، نمونه‌‌ای را آماده کرده‌ام. این نمونه به طور ساده یک سیستم مدیریت نوبت است که نوبت‌ها را در فایلی ذخیره و بازیابی میکند ( mutate ) و منطق مربوط به نوبت‌ها و زمان ویزیت آن میتواند به صورت immutable پیاده سازی شود. این کد در دو حالت functional و غیر functional پیاده سازی شده تا به خوبی تفاوت آن را در حالت قبل و بعد از برنامه نویسی تابعی بتوانیم درک کنیم. به جهت خوانایی بیشتر و دسترسی به کد‌ها، آن‌ها را روی گیت‌هاب قرار داده و شما میتوانید از اینجا سورس کد مورد نظر را بررسی کنید. سعی شده در این مثال تمامی مواردی که در این قسمت ذکر شد را پیاده سازی کنیم. امیدوارم که مطالب مربوط به برنامه نویسی تابعی یا functional programming توانسته باشد دیدگاه جدیدی را به کدهایی که مینویسیم بدهد. در  قسمت‌های بعدی به مواردی مانند مدیریت exception ‌ها و کار با null ‌ها و ... خواهیم پرداخت.

مطالب
الگوی شیء نال Null Object Pattern
این الگو شاید به نظر ساده برسد، ولی در بعضی موارد می‌تواند در سطوح بالاتر، کدهای تمیزتر و خلوت‌تری را در اختیار شما بگذارد. در مورد این الگو، در کتاب توسعه چابک عمو باب نیز آمده است. بسیاری ممکن است نسبت به این الگو جبهه بگیرند و بگویند که بررسی نال بودن یک شیء بهتر است و یا حتی رخ دادن خطای Null Pointer Exception در برنامه باعث می‌شود بتوانیم باگ‌ها را پیدا کنیم. در جواب باید گفت که این الگو قرار نیست در همه جا مورد استفاده قرار گیرد. در مثال زیر می‌توانید تا حدی به جایگاه استفاده از این الگو برسید. اینکه چگونه و در کجا از یک الگو استفاده کنید، به عهده برنامه نویس است.
کار این الگو در یک جمله این است که اگر متدی نتواند خروجی مناسبی را بدهد و به جای آن قرار باشد نال را برگشت دهد، به جای برگشت دادن نال، از یک شیء که هیچ رفتاری ندارد استفاده می‌کند و آن شیء را برمی‌گرداند تا در ادامه کد، بررسی نال بودن، یا خطای NPE رخ ندهد.

به عنوان مثال فرض کنید قرار است یک کاربر با نام کاربری Ali به سیستم وارد شود؛ در اینجا سه حالت وجود دارد:
  1. این کاربر یافت شده و اجازه دسترسی دارد.
  2. این کاربر یافت شده و اجازه دسترسی ندارد.
  3. این کاربر یافت نمی‌شود.

اگر در حالتیکه کاربر یافت نشود، بخواهیم نال برگردانیم، در ادامه‌ی کد باید بررسی نال بودن و یا گاها انتظار خطای NPE را داشته باشیم؛ یا اینکه در عوض از الگوی شیء نال بهره ببریم.

بدون استفاده از الگو
در این مثال ابتدا کلاس یوزر را می‌سازیم:
 public class User
    {
        public String Usernam { get; set; }
        public bool Authenticated { get; set; }
    }
در لایه سرویس هم خروجی را برمی‌گردانیم:
 public User GeUser(string uname)
        {
            if (uname == "Ali")
            {
                return new User()
                {
                    Usernam = "Ali",
                    Authenticated = true
                };
            }
            else if (uname == "Reza")
            {
                return new User()
                {
                    Usernam = "Reza",
                    Authenticated = false
                };
            }
            else
            {
                return null;
            }
        }
در این حالت کد بعدی شما باید اینگونه باشد:
 var userServices=new UserServices();
            var user = userServices.GeUser("Ali");
            if (user != null && user.Authenticated)
            {
                Console.WriteLine("You are Authorized");
            }
همانطور که می‌بینید یک خط کد شرطی به سیستم اضافه شد و در یک سیستم بزرگتر این بررسی‌ها بیشتر شده و حتی در بعضی نقاط ممکن است با یک عدم بررسی، وقوع خطای NPE را افزایش دهید. حالا همین مثال بالا را با همین الگو جلو می‌بریم.

استفاده از الگو

ابتدا یک کلاس جدید را با ارث بری از کلاس یوزر می‌سازیم:
public class NullUser:User
    {
        public NullUser()
        {
            Authenticated = false;
        }
    }
این کلاس همان شیء نالی است که قرار است به جای خود عبارت Null برگشت دهیم:
  public User GeUser(string uname)
        {
            if (uname == "Ali")
            {
                return new User()
                {
                    Usernam = "Ali",
                    Authenticated = true
                };
            }
            else if (uname == "Reza")
            {
                return new User()
                {
                    Usernam = "Reza",
                    Authenticated = false
                };
            }
                return new NullUser();
        }
بدین ترتیب در ادامه کد الزامی به بررسی نال نیست:
var userServices=new UserServices();
            var user = userServices.GeUser("xxx");
            if (user.Authenticated)
                Console.WriteLine("You are Authorized");

یک نکته اضافه تر اینکه، در صورتی که قصد دارید متدی را در کلاس پدر تحریف کنید، بهتر است یک اینترفیس یا کلاس انتزاعی را تعریف و هر دو کلاس را از آن ارث بری کنید که برای مثال بالا می‌شود اینترفیس IUser و  دو کلاس User و NullUser هم مشتقات آن.
مطالب دوره‌ها
تزریق وابستگی‌ها در حالتی‌که از یک اینترفیس چندین کلاس مشتق شده‌اند
حین کار با ASP.NET Identity به اینترفیسی به نام IIdentityMessageService شبیه به اینترفیس ذیل می‌رسیم:
namespace SameInterfaceDifferentClasses.Services.Contracts
{
  public interface IMessageService
  {
   void Send(string message);
  }
}
فرض کنید از آن دو پیاده سازی در برنامه برای ارسال پیام‌ها توسط ایمیل و همچنین توسط SMS، وجود دارد:
public class EmailService : IMessageService
{
  public void Send(string message)
  {
   // ...
  }
}

public class SmsService : IMessageService
{
  public void Send(string message)
  {
   //todo: ...
  }
}
اکنون کلاس مدیریت کاربران برنامه، در سازنده‌ی خود نیاز به دو وهله، از این سرویس‌های متفاوت، اما در اصل مشتق شده‌ی از یک اینترفیس دارد:
public interface IUsersManagerService
{
  void ValidateUserByEmail(int id);
}

public class UsersManagerService : IUsersManagerService
{
  private readonly IMessageService _emailService;
  private readonly IMessageService _smsService;
 
  public UsersManagerService(IMessageService emailService, IMessageService smsService)
  {
   _emailService = emailService;
   _smsService = smsService;
  }
 
  public void ValidateUserByEmail(int id)
  {
   _emailService.Send("Validated.");
  }
}
در این حالت صرف تنظیمات ابتدایی انتساب یک اینترفیس، به یک کلاس مشخص کافی نیست:
ioc.For<IMessageService>().Use<SmsService>();
ioc.For<IMessageService>().Use<EmailService>();
از این جهت که در سازنده‌ی کلاس UsersManagerService دقیقا مشخص نیست، پارامتر اول باید سرویس SMS باشد یا ایمیل؟
برای حل این مشکل می‌توان به نحو ذیل عمل کرد:
public static class SmObjectFactory
{
  private static readonly Lazy<Container> _containerBuilder =
   new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);
 
  public static IContainer Container
  {
   get { return _containerBuilder.Value; }
  }
 
  private static Container defaultContainer()
  {
   return new Container(ioc =>
   {
    // map same interface to different concrete classes
    ioc.For<IMessageService>().Use<SmsService>();
    ioc.For<IMessageService>().Use<EmailService>();
 
    ioc.For<IUsersManagerService>().Use<UsersManagerService>()
     .Ctor<IMessageService>("smsService").Is<SmsService>()
     .Ctor<IMessageService>("emailService").Is<EmailService>();
   });
  }
}
در اینجا توسط متد Ctor که مخفف Constructor یا سازنده‌ی کلاس است، مشخص می‌کنیم که اگر به پارامتر smsService رسیدی، از کلاس SmsService استفاده کن و در مورد کلاس سرویس ایمیل نیز به همین ترتیب. اینبار اگر برنامه را اجرا کنیم:
 var usersManagerService = SmObjectFactory.Container.GetInstance<IUsersManagerService>();
usersManagerService.ValidateUserByEmail(id: 1);


همانطور که در تصویر مشخص است، هر کدام از پارامترها، توسط کلاس‌های متفاوتی مقدار دهی شده‌اند؛ هرچند از یک اینترفیس مشخص استفاده می‌کنند.

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
Dependency-Injection-Samples/DI09