مطالب
مقایسه سرعت نگاشت AutoMapper

قبل ازاین مقاله، درباره راه اندازی و استفاده از کتابخانه Automapper  بحث شده ولی موردی که شاید کمتر به آن توجه شده سرعت این نگاشت میباشد. در این مقاله با استفاده از نوشتن تست، این موضوع بررسی میشود.

کلاس ساده زیر را در نظر بگیرید که برای مثال از سمت لایه دسترسی به داده گرفته شده است:

public enum PersonType
{
    Real =0,
    Legal=1
}

public class Person
{
    public long PersonId { get; set; }
    public string Name { get; set; }
    public string Family { get; set; }
    public PersonType PersonType { get; set; }

    public Person(long personId, string name, string family, PersonType personType)
    {
        PersonId = personId;
        Name = name;
        Family = family;
        PersonType = personType;
    }
}

از سازنده آن برای دریافت مقادیر مربوط به خصوصیات شیء استفاده شد.

در طرف دیگر نیز کلاسی برای نگاشت از آبجکت رسیده از سمت لایه داده ساخته میشود که برای نمایش در ویوها ایجاد شده است: 

public class PersonDto
{
    public long PersonId { get; set; }
    public string Name { get; set; }
    public string Family { get; set; }
    public PersonType PersonType { get; set; }

    public PersonDto(long personId, string name, string family, PersonType personType)
    {
        PersonId = personId;
        Name = name;
        Family = family;
        PersonType = personType;
    }
}

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

برای ایجاد لیستی که در تست مورد استفاده قرار میگیرد نیز کلاس زیر را فراهم میکنیم: 

public class PersonList
{
    readonly List<Person> _list = new List<Person>();
    public ReadOnlyCollection<Person> GetPersons()
    {
        if (!_list.Any())
        {
            for (int i = 0; i < 100*1000; i++)
            {
                _list.Add(new Person(i + 1, "Person Name" + i, "Person Family" + i, (PersonType)(i % 2)));
            }

        }
        return _list.AsReadOnly();
    }
}

در اینجا برای محسوس بودن نتیجه تست میتوان تعداد آبجکتهای لازم برای تست را تعیین کرد، فعلا 100 هزار آبجکت در نظر گرفته شده است: 

for (int i = 0; i < 100*1000; i++)
{
    _list.Add(new Person(i + 1, "Person Name" + i, "Person Family" + i, (PersonType)(i % 2)));
}

برای ارجاع به AutoMapper، با استفاده از نیوگت، پکیج را به پروژه تست ارجاع میدهیم: (در حال حاضر نسخه 5.1.1 استفاده شده است) 

<package id="AutoMapper" version="5.1.1" targetFramework="net452" />

در سمت تست نگاشت نیز از دو متد برای مقایسه استفاده میکنیم؛ یکی با استفاده از AutoMapper و دیگری بدون استفاده از آن: 

[TestMethod]
public void FillPersonDtoList_AutoMapperShouldMapPersonListToPersonDtoList_WhenLargeAmountOfPerson()
{
    // arrange
    var personDtoList = new List<PersonDto>(); persons = new PersonList().GetPersons();

    // act
    personDtoList = Mapper.Map<List<PersonDto>>(persons);

    //assert
    Assert.AreEqual(persons.Count, personDtoList.Count);
}
[TestMethod]
public void FillPersonDtoList_UsingHandlyAssignment_WhenLargeAmountOfPerson()
{
    // arrange
    var personDtoList = new List<PersonDto>(); persons = new PersonList().GetPersons();

    // act
    foreach (var person in persons)
    {
        personDtoList.Add(new PersonDto(person.PersonId, person.Name, person.Family, person.PersonType));
    }

    //assert
    Assert.AreEqual(persons.Count, personDtoList.Count);
}

سرعت نگاشت AutoMapper در نسخه حال حاضر تقریبا سه بار کندتر از استفاده معمول برای تهیه نگاشت جدید از یک آبجکت است: 

نکته: این تست با نسخه قدیمی تر(4.0.4.0) نیز انجام شده که این اختلاف سرعت نزدیک به 13 بار کندتر هم رسیده است. 

پ.ن: سورس پروژه تست

مطالب
خواندن اطلاعات از فایل اکسل با استفاده از LinqToExcel
در این مقاله مروری سریع و کاربردی خواهیم داشت بر توانایی‌های مقدماتی LinqToExcel
در ابتدا می‌بایست LinqToExcel را از طریق NuGet به پروژه افزود.
PM> Install-Package LinqToExcel
و یا از طریق solution Explorer گزینه Manage NuGet Packages 

اکنون فایل اکسل ذیل را در نظر بگیرید.

روش خواندن اطلاعات از فایل اکسل فوق تحت فرامین Linq و با مشخص کردن نام sheet مورد نظر  توسط شئ ExcelQueryFactory  بصورت زیر است.

 string pathToExcelFile = @"C:\Users\MASOUD\Desktop\ExcelFile.xlsx";
var excel = new ExcelQueryFactory(pathToExcelFile);
            string sheetName = "Sheet1";
            var persons = from a in excel.Worksheet(sheetName) select a;
            foreach (var a in persons)
            {
                MessageBox.Show(a["Name"]+" "+a["Family"]);
            }


در صورتیکه بخواهیم انتقال اطلاعات فایل اکسل به جداول بانک اطلاعاتی مانند Sql Server بطور مثال با روش EF Entity Framework را انجام دهیم کلاس زیر با نام person را فرض نمایید.

 public class Person
        {
            public string Name { get; set; }
            public string Family { get; set; }
        }
باید بدانید که بصورت پیشفرض سطر اول از فایل اکسل به عنوان نام ستون انتخاب می‌شود و می‌بایست جهت نگاشت با نام property‌های کلاس ما دقیقاً همنام باشد.

 string pathToExcelFile = @"C:\Users\MASOUD\Desktop\ExcelFile.xlsx";
            var excel = new ExcelQueryFactory(pathToExcelFile);
            string sheetName = "Sheet1";
            var persons = from a in excel.Worksheet<Person>(sheetName) select a;
            foreach (var a in persons)
            {
                MessageBox.Show(a.Name+" "+a.Family);
            }
  اگر فایل اکسل ما ستون‌های بیشتری داشته باشد تنها ستونهای همنام با propertyهای کلاس ما به کلاس نگاشت پیدا می‌کند و سایر ستونها نادیده گرفته می‌شود.
در صورتیکه نام ستونهای فایل اکسل(سطر اول) با نام property‌های کلاس یکسان نباشد جهت نگاشت آنها در کلاس می‌توان از متد AddMapping استفاده نمود.
 

 string pathToExcelFile = @"C:\Users\MASOUD\Desktop\ExcelFile.xlsx";
            var excel = new ExcelQueryFactory(pathToExcelFile);
            string sheetName = "Sheet1";
            excel.AddMapping("Name","نام");
            excel.AddMapping("Family", "نام خانوادگی");
            var persons = from a in excel.Worksheet<Person>(sheetName) select a;
            foreach (var a in persons)
            {
                MessageBox.Show(a.Name+" "+a.Family);
            }

در کدهای بالا در صورتی که sheetName قید نشود بصورت پیشفرض Sheet1 از فایل اکسل  انتخاب می‌شود.

var persons = from a in excel.Worksheet<Person>() select a;
همچنین می‌توان از اندیس جهت مشخص نمودن Sheet مورد نظر استفاده نمود که اندیس‌ها از صفر شروع می‌شوند.

var persons = from a in excel.Worksheet<Person>(0) select a;
توسط متد GetWorksheetNames می توان نام sheet‌ها را بدست آورد.

public IEnumerable<string> getWorkSheets()
{
string pathToExcelFile = @"C:\Users\MASOUD\Desktop\ExcelFile.xlsx";
    
    var excel = new ExcelQueryFactory(pathToExcelFile);

    return excel.GetWorksheetNames();
}
و توسط متد GetColumnNames   می توان نام ستونها را بدست آورد.  

var SheetColumnNames = excel.GetColumnNames(sheetName);
همانطور که می‌بینید با روش توضیح داده شده در این مقاله به راحتی از فرامین Linq مانند where می‌توان در انتخاب اطلاعات از فایل اکسل استفاده نمود و سپس نتیجه را به جداول مورد نظر انتقال داد.
مطالب
مروری بر کاربردهای Action و Func - قسمت اول
delegate‌ها، نوع‌هایی هستند که ارجاعی را به یک متد دارند؛ بسیار شبیه به function pointers در C و CPP هستند، اما برخلاف آن‌ها، delegates شی‌ء‌گرا بوده، به امضای متد اهمیت داده و همچنین کد مدیریت شده و امن به شمار می‌روند.
سیر تکاملی delegates را در مثال ساده زیر می‌توان ملاحظه کرد:
using System;

namespace ActionFuncSamples
{
    public delegate int AddMethodDelegate(int a);
    public class DelegateSample
    {
        public void UseDelegate(AddMethodDelegate addMethod)
        {
            Console.WriteLine(addMethod(5));
        }
    }

    public class Helper
    {
        public int CustomAdd(int a)
        {
            return ++a;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Helper helper = new Helper();

            // .NET 1            
            AddMethodDelegate addMethod = new AddMethodDelegate(helper.CustomAdd);
            new DelegateSample().UseDelegate(addMethod);

            // .NET 2, anonymous delegates
            new DelegateSample().UseDelegate(delegate(int a) { return helper.CustomAdd(a); });

            // .NET 3.5
            new DelegateSample().UseDelegate(a => helper.CustomAdd(a));
        }
    }
}
معنای کلمه delegate، واگذاری مسئولیت است. به این معنا که ما در متد UseDelegate، نمی‌دانیم addMethod به چه نحوی تعریف خواهد شد. فقط می‌دانیم که امضای آن چیست.
در دات نت یک، یک وهله از شیء AddMethodDelegate ساخته شده و سپس متدی که امضایی متناسب و متناظر با آن را داشت، به عنوان متد انجام دهنده مسئولیت معرفی می‌شد. در دات نت دو، اندکی نحوه تعریف delegates با ارائه delegates بی‌نام، ساده‌تر شد و در دات نت سه و نیم با ارائه lambda expressions ، تعریف و استفاده از delegates باز هم ساده‌تر و زیباتر گردید.
به علاوه در دات نت 3 و نیم، دو Generic delegate به نام‌های Action و Func نیز ارائه گردیده‌اند که به طور کامل جایگزین تعریف طولانی delegates در کدهای پس از دات نت سه و نیم شده‌اند. تفاوت‌های این دو نیز بسیار ساده است:
اگر قرار است واگذاری قسمتی از کد را به متدی محول کنید که مقداری را بازگشت می‌دهد، از Func و اگر این متد خروجی ندارد از Action استفاده نمائید:
Action<int> example1 = x => Console.WriteLine("Write {0}", x);
example1(5);

Func<int, string> example2 = x => string.Format("{0:n0}", x);
Console.WriteLine(example2(5000));
در دو مثال فوق، نحوه تعریف inline یک Action و یا Func را ملاحظه می‌کنید. Action به متدی اشاره می‌کند که خروجی ندارد و در اینجا تنها یک ورودی int را می‌پذیرد. Func در اینجا به تابعی اشاره می‌کند که یک ورودی int را دریافت کرده و یک خروجی string را باز می‌گرداند.

پس از این مقدمه، در ادامه قصد داریم مثال‌های دنیای واقعی Action و Func را که در سال‌های اخیر بسیار متداول شده‌اند، بررسی کنیم.


مثال یک) ساده سازی تعاریف API ارائه شده به استفاده کنندگان از کتابخانه‌های ما
عنوان شد که کار delegates، واگذاری مسئولیت انجام کاری به کلاس‌های دیگر است. این مورد شما را به یاد کاربردهای interfaceها نمی‌اندازد؟
در interfaceها نیز یک قرارداد کلی تعریف شده و سپس کدهای یک کتابخانه، تنها با امضای متدها و خواص تعریف شده در آن کار می‌کنند و کتابخانه ما نمی‌داند که این متدها قرار است چه پیاده سازی خاصی را داشته باشند.
برای نمونه طراحی API زیر را درنظر بگیرید که در آن یک interface جدید تعریف شده که تنها حاوی یک متد است. سپس کلاس Runner از این interface استفاده می‌کند:
using System;

namespace ActionFuncSamples
{
    public interface ISchedule
    {
        void Run();
    }

    public class Runner
    {
        public void Exceute(ISchedule schedule)
        {
            schedule.Run();
        }
    }

    public class HelloSchedule : ISchedule
    {
        public void Run()
        {
            Console.WriteLine("Just Run!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Runner().Exceute(new HelloSchedule());
        }
    }
}
در اینجا ابتدا باید این interface را در طی یک کلاس جدید (مثلا HelloSchedule) پیاده سازی کرد و سپس حاصل را در کلاس Runner استفاده نمود.
نظر شما در مورد این طراحی ساده شده چیست؟
using System;

namespace ActionFuncSamples
{
    public class Schedule
    {
        public void Exceute(Action run)
        {
            run();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Schedule().Exceute(() => Console.WriteLine("Just Run!"));
        }
    }
}
با توجه به اینکه هدف از معرفی interface در طراحی اول، واگذاری مسئولیت نحوه تعریف متد Run به کلاسی دیگر است، به همین طراحی با استفاده از یک Action delegate نیز می‌توان رسید. مهم‌ترین مزیت آن، حجم بسیار کمتر کدنویسی استفاده کننده نهایی از API تعریف شده ما است. به علاوه امکان inline coding نیز فراهم گردیده است و در همان محل تعریف Action، بدنه آن‌را نیز می‌توان تعریف کرد.
بدیهی است delegates نمی‌توانند به طور کامل جای interfaceها را پر کنند. اگر نیاز است قرارداد تهیه شده بین ما و استفاده کنندگان از کتابخانه، حاوی بیش از یک متد باشد، استفاده از interfaceها بهتر هستند.
از دیدگاه بسیاری از طراحان API، اشیاء delegate معادل interface ایی با یک متد هستند و یک وهله از delegate معادل وهله‌ای از کلاسی است که یک interface را پیاده سازی کرده‌است.
علت استفاده بیش از حد interfaceها در سایر زبان‌ها برای ابتدایی‌ترین کارها، کمبود امکانات پایه‌ای آن زبان‌ها مانند نداشتن lambda expressions، anonymous methods و anonymous delegates هستند. به همین دلیل مجبورند همیشه و در همه‌جا از interfaceها استفاده کنند.

ادامه دارد ...

 
مطالب
ساختارهای داده‌ی توکار ES 6
در ES 5 تنها آرایه (Array) و آبجکت (Object) را به عنوان ساختار داده‌ایی، به صورت توکار در اختیار داریم.
Array یک کالکشن مبتنی بر ایندکس است. همچنین می‌توان هر نوع مقداری را در آن ذخیره کرد:
var collection = ['a', 1, /3/, {}];
یعنی هر کدام از اعضای آرایه می‌توانند جنس متفاوتی داشته باشند. همانطور که در کد فوق مشاهده می‌کنید اعضای آرایه به ترتیب از کاراکتر، عدد، عبارت با قاعده و در نهایت یک شیء خالی تشکیل شده است. همانطور که عنوان شد آرایه‌ها در جاوا اسکریپت همانند دیگر زبان‌های برنامه‌نویسی مبتنی بر ایندکس هستند، یعنی می‌توان براساس ایندکس به هر کدام از اعضای آرایه دسترسی داشت:
collection[0];
می‌توان از پراپرتی length نیز برای دریافت سایز آرایه استفاده کرد:
collection.length
همانطور که در مثال ابتدای بحث مشاهده کردید، آرایه‌ها در جاوا اسکریپت توسط سینتکس [] قابل تعریف هستند. تعدادی تابع توکار برای کار با آرایه‌ها موجود است که در اینجا می‌توانید لیست کامل آنها را مشاهد نمائید. همچنین می‌توانید از کتابخانه‌های دیگری مانند Underscore.js که در واقع هدف آن‌ها افزودن یکسری قابلیت‌ها به جاوا اسکریپت است، استفاده کنید:
var numbers = [1, 2, 3];

_.each(numbers, function (num) {
    write(num);
});
در ES 6 تعدادی تابع جدید به Array اضافه شده که کار با آرایه‌ها را ساده‌تر کرده است. در ادامه تعدادی از این توابع را بررسی خواهیم کرد.
تابع find
این تابع از ورودی، یک callback را گرفته و نتایج یافته شده را در خروجی برمی‌گرداند:
var ary = [1, 5, 10];
var match = ary.find(item => item > 8);
تابع findIndex
این تابع مشابه تابع find عمل می‌کند با این تفاوت که در خروجی ایندکس عنصر یافته شده را برمی‌گرداند:
var match = ary.findIndex(item => item > 8);
تابع fill
از این تابع می‌توان جهت مقداردهی اعضای آرایه با پارامتر موردنظر استفاده کرد:
ary.find('a');
// خروجی ["a", "a", "a"]
لازم به ذکر است، به این تابع می‌توانیم دو پارامتر دیگر را نیز پاس دهیم:
var ary = [1, 5, 10, 5, 6];
ary.fill('a', 2, 3)
در کد فوق پارامتر دوم یعنی نقطه شروع و پارامتر سوم یعنی نقطه پایان:
// خروجی
[ 1, 5, "a", 5, 6 ]
تابع copyWithin
با کمک این تابع می‌توانیم قسمتی از یک آرایه را کپی کرده و در محل دیگری از آرایه ذخیره کنیم:
[1, 2, 3, 4, 5].copyWithin(0, 3);
کد فوق از نقطه‌ی سوم شروع به کپی کردن آیتم‌ها کرده و آنها را در موقعیت صفرم آرایه به بعد قرار می‌دهد. در نتیجه خروجی آن به این صورت است:
// [4, 5, 3, 4, 5]
لازم به ذکر است که یک پارامتر سوم را نیز می‌توانیم جهت تعیین نقطه‌ی پایان به تابع فوق اضافه کنیم:
[1, 2, 3, 4, 5].copyWithin(0, 3, 4);
// خروجی 
// [4, 2, 3, 4, 5]
در ES 6 علاوه بر سینتکس literal می‌توان از سازنده‌ی کلاس Array نیز جهت تعریف آرایه‌ها، استفاده کرد:
var ary = new Array(1, 2);
کد فوق یک آرایه را با دو مقدار 1 و 2 ایجاد می‌کند. اگر بخواهیم یک آیتم جدید را به آرایه‌ی فوق اضافه کنیم، باید آن را نیز به پارامترهای فوق اضافه کرد:
var ary = new Array(1, 2, 3);
ممکن است فکر کنید توسط کد زیر آرایه‌ایی تنها با یک آیتم برای ما ایجاد خواهد شد:
var ary = new Array(3);
در واقع کد فوق یک آرایه با اندازه‌ی سه و محتوای undefined را برای شما ایجاد خواهد کرد. در نتیجه برای ایجاد آرایه‌ایی با یک آیتم و مقدار 3 باید از متد Of کلاس Array استفاده کنیم:
var Ofary = Array.of(3);

Set
Set یک ساختار داده‌ایی جدید در ES 6 است. این ساختار داده‌ایی امکان تعریف کالکشنی از مقادیر را به صورت unique، در اختیارمان قرار می‌دهد. برخلاف آرایه‌ها مقادیر درون Set نمی‌تواند یکسان باشند. در کد زیر نحوه‌ی ایجاد یک Set نشان داده شده است:
var set = new Set();
set.add(1);
set.add(2);
set.add(3);
console.log(set.size); // logs 3
 Set به صورت یک مجموعه‌ی  Iterable است یعنی می‌توان اعضای این مجموعه را آیتم به آیتم پیمایش کرد. همانطور که در کد فوق مشاهده می‌کنید توسط add می‌توانیم آیتم جدیدی را به مجموعه اضافه کنیم. همچنین اگر مایل بودید می‌توانید مجموعه را توسط یک آرایه به صورت زیر نیز مقداردهی کنید:
var set = new Set([1, 2, 3]);
console.log(set.size); // logs 3
از توابع has, delete, clear نیز به ترتیب می‌توان جهت خالی کردن مجموعه، حذف یک آیتم از مجموعه و بررسی یک آیتم در مجموعه استفاده کرد:
var set = new Set();
set.has(1); // false
set.add(1);
set.has(1); // true
set.clear();
set.has(1); // false
set.add(1);
set.add(2);
set.size;   // 2
set.delete(2);
set.size;   // 1
از تابع feorach نیز می‌توانیم برای حرکت بین آیتم‌های مجموعه استفاده کنیم:
var set = new Set();
set.add('Vahid');
set.add('Sirwan');

var i = 0;
set.forEach(item => i++);
console.log(i);
همچنین از سینتکس for...of نیز می‌توان برای پیمایش مجموعه استفاده کرد:
var set = new Set();
set.add('Vahid');
set.add('Sirwan');

var i = 0;
for(let item of set) {
    i++;
}
console.log(i);
Set دارای یک تابع دیگر با نام entries است. با کمک این تابع یک iterator از مجموعه برگردانده خواهد شد که با کمک تابع next می‌توان به عناصر بعدی مجموعه دسترسی پیدا کرد:
var set = new Set();
set.add("Sirwan");
set.add(1);
set.add("Afifi");

var setIter = set.entries();

console.log(setIter.next().value); // ["Sirwan", "Sirwan"]
console.log(setIter.next().value); // [1, 1]
console.log(setIter.next().value); // ["Afifi", "Afifi"]

Map
برخلاف Set که یک مجموعه از مقادیر (values) است، Map یک مجموعه از کلید/مقدار (key/value) می‌باشد. در اینجا نیز کلیدها باید unique باشند. همچنین می‌توان از هر نوعی برای کلید استفاده کرد. برای افزودن یک مقدار به این مجموعه باید از تابع set استفاه کنیم:
var map = new Map();
map.set('name', 'Sirwan');

map.get('name'); // Sirwan
همانطور که مشاهده می‌کنید توسط تابع get نیز می‌توانیم با استفاده از کلید، به مقدار آن دسترسی داشته باشیم. همچنین می‌توانیم آرایه‌ایی از آرایه‌ها را به عنوان کلید در یک Map ذخیره کنیم:
var map = new Map([['name', 'Sirwan'], ['age', 27]]);
map.has('age'); // true
map.get('age'); // 27
map.get('name'); // Sirwan

نکته‌ایی که در استفاده از Map باید به آن دقت کنید این است که در اینجا هیچ تبدیل نوعی را بر روی کلیدها نداریم:
var map = new Map();

map.set(1, true);
map.has("1"); // false

map.set("1", true);
map.has("1"); // true
همانند Set برای Map نیز می‌توانیم از توابع delete و clear استفاده کنیم. برای استفاده از foreach باید برای callback دو پارامتر را ارائه دهیم. یکی برای value و دیگری برای key:
var map = new Map([['name', 'Sirwan'], ['age', 27]]);
var i = 0;
map.forEach(function (value, key) {
    i++;
});
console.log(i); // log 2
برای سینتکس for...of نیز می‌توانیم به اینصورت عمل کنیم:
for (var [key, value] of map) {
    i++;
}
شاید بپرسید که همین کار را می‌توان با استفاده از آرایه‌ها نیز انجام داد و چه نیازی به یک ساختار داده‌ایی جدید است؟
اگر بخواهید Map را با استفاده از آرایه‌ها شبیه‌سازی کنید باید از Associative Arrays استفاده کنید؛ به زبان ساده در این‌حالت به جای استفاده از عدد به جای ایندکس می‌توان رشته‌ها نیز استفاده کرد. به عنوان مثال کد زیر را در نظر بگیرید:
var newArray = new Array();
newArray["name"] = "Sirwan";
newArray["lastName"] = "Afifi";
در اینجا ایندکس‌ها به ترتیب name و lastName هستند و به عنوان کلید مورد استفاده قرار می‌گیرند. کلیدها نیز به مقادیر "Sirwan" و "Afifi" مپ شده‌اند. حالت فوق شبیه به یک دیکشنری عمل می‌کند. اما همانطور که عنوان شد در اینجا کلید به صورت رشته‌ایی است و نمی‌توان از اشیاء به عنوان کلید استفاده کرد؛ زیرا در نهایت تبدیل به رشته خواهند شد:
let user1 = { name: "Vahid" };
let user2 = { name: "Sirwan" };

let result = {};
result[user1] = 10;
result[user2] = 20;

console.log(result[user1]); // logs 20
console.log(result[user2]); // logs 20
در کد فوق هر کدام از شیء‌ها را به عنوان کلید در نظر گرفته‌ایم و برای هر کدام مقادیر 10 و 20 را ست کرده‌ایم. اما خروجی هر کدام 20 است؛ در حالیکه باید به ترتیب عدد 10 و سپس عدد 20 در خروجی نمایش داده شود. دلیل آن نیز کاملاً مشخص است زیرا اگر در جاوا اسکریپت برای یک شیء تابع toString را فراخوانی کنیم، مقدار "[object object]" در خروجی نمایش داده خواهد شد. در نتیجه در کد فوق در واقع هر بار ایندکس "[object object]" را به‌روز رسانی کرده‌ایم:
result["[object object]"] = 10;
result["[object object]"] = 20;

console.log(result["[object object]"]); // logs 20
console.log(result["[object object]"]); // logs 20

WeakMap and WeakSet
فرض کنید درون DOM سه عنصر div دارید و می‌خواهید این سه div را درون یک Set ذخیره کنید:

در این‌حالت آیتم‌های درون Set ارجاع مستقیمی را به عناصر موجود در DOM دارند. اکنون حالتی را در نظر بگیرید که بخواهیم یکی از عناصر موجود درون DOM را حذف کنیم. در اینحالت آیتم درون Set که به این عنصر اشاره دارد هنوز حذف نشده است و همچنان ارجاعی را به آن عنصر دارد. بنابراین تا زمانیکه آیتم از Set حذف نشود Garbage Collector نمی‌تواند حافظه‌ی اختصاص داده شده را مجدداً بازیابی کند. در نتیجه استفاده از Set و یا Map در چنین سناریوهایی منجر به نشتی حافظه خواهد شد. برای حل این مشکل می‌توانیم از WeakMap و یا WeakSet استفاده کنیم. در این‌حالت WeakMap و WeakSet ارجاع مستقیمی به اشیایی که به آنها اضافه می‌شوند، ندارند. در نتیجه GC به راحتی می‌تواند حافظه‌ی اختصاص داده شده را بعد از حذف اشیاء بازیابی کند.
صرف‌نظر از رفع مشکل حافظه، WeakMap و WeakSet شبیه به Map و Set عمل می‌کنند، اما یکسری محدودیت‌هایی در استفاده از آنها وجود دارد:
  • WeakMap و WeakSet فاقد پراپرتی‌های size, entries, values و متد foreach هستند.
  • WeakMap همچنین فاقد keys است.
نظرات مطالب
چند نکته کاربردی درباره Entity Framework
در حالت Detached (مثل ایجاد یک شیء CLR ساده)
در متد Updateایی که نوشتید، قسمت Find حتما اتفاق می‌افته. چون Tracking خاموش هست (مطابق تنظیماتی که عنوان کردید)، بنابراین Find چیزی رو از کشی که وجود نداره نمی‌تونه دریافت کنه و میره سراغ دیتابیس. ماخذ :
The Find method on DbSet uses the primary key value to attempt to find an entity tracked by the context.
If the entity is not found in the context then a query will be sent to the database to find the entity there.
Null is returned if the entity is not found in the context or in the database.
حالا تصور کنید که در یک حلقه می‌خواهید 100 آیتم رو ویرایش کنید. یعنی 100 بار رفت و برگشت خواهید داشت با این متد Update سفارشی که ارائه دادید. البته منهای کوئری‌های آپدیت متناظر. این 100 تا کوئری فقط Find است.
قسمت Find متد Update شما در حالت detached اضافی است. یعنی اگر می‌دونید که این Id در دیتابیس وجود داره نیازی به Findاش نیست. فقط State اون رو تغییر بدید کار می‌کنه.

در حالت نه آنچنان Detached ! (دریافت یک لیست از Context ایی که ردیابی نداره)
با خاموش کردن Tracking حتما نیاز خواهید داشت تا متد  context.ChangeTracker.DetectChanges رو هم پیش از ذخیره سازی یک لیست دریافت شده از بانک اطلاعاتی فراخوانی کنید. وگرنه چون این اطلاعات ردیابی نمی‌شوند، هر تغییری در آن‌ها، وضعیت Unchanged رو خواهد داشت و نه Detached. بنابراین SaveChanges عمل نمی‌کنه؛ مگر اینکه DetectChanges فراخوانی بشه.

سؤال: این سربار که می‌گن چقدر هست؟ ارزشش رو داره که راسا خاموشش کنیم؟ یا بهتره فقط برای گزارشگیری این کار رو انجام بدیم؟
یک آزمایش:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Diagnostics;
using System.Linq;

namespace EF_General.Models.Ex21
{
    public abstract class BaseEntity
    {
        public int Id { set; get; }
    }

    public class Factor : BaseEntity
    {
        public int TotalPrice { set; get; }
    }

    public class MyContext : DbContext
    {
        public DbSet<Factor> Factors { get; set; }

        public MyContext() { }
        public MyContext(bool withTracking)
        {
            if (withTracking)
                return;

            this.Configuration.ProxyCreationEnabled = false;
            this.Configuration.LazyLoadingEnabled = false;
            this.Configuration.AutoDetectChangesEnabled = false;
        }

        public void CustomUpdate<T>(T entity) where T : BaseEntity
        {
            if (entity == null)
                throw new ArgumentException("Cannot add a null entity.");


            var entry = this.Entry<T>(entity);
            if (entry.State != EntityState.Detached)
                return;

            /*var set = this.Set<T>(); // این‌ها اضافی است
            //متد فایند اگر اینجا باشه حتما به بانک اطلاعاتی رجوع می‌کنه در حالت منقطع از زمینه و در یک حلقه به روز رسانی کارآیی مطلوبی نخواهد داشت
            T attachedEntity = set.Find(entity.Id);
            if (attachedEntity != null)
            {
                var attachedEntry = this.Entry(attachedEntity);
                attachedEntry.CurrentValues.SetValues(entity);
            }
            else
            {*/
            entry.State = EntityState.Modified;
            //}
        }
    }

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

        protected override void Seed(MyContext context)
        {
            if (!context.Factors.Any())
            {
                for (int i = 0; i < 20; i++)
                {
                    context.Factors.Add(new Factor { TotalPrice = i });
                }
            }
            base.Seed(context);
        }
    }

    public class Performance
    {
        public TimeSpan ListDisabledTracking { set; get; }
        public TimeSpan ListNormal { set; get; }
        public TimeSpan DetachedEntityDisabledTracking { set; get; }
        public TimeSpan DetachedEntityNormal { set; get; }
    }

    public static class Test
    {
        public static void RunTests()
        {
            startDb();

            var results = new List<Performance>();
            var runs = 20;
            for (int i = 0; i < runs; i++)
            {
                Console.WriteLine("\nRun {0}", i + 1);

                var tsListDisabledTracking = PerformanceHelper.RunActionMeasurePerformance(() => updateListTotalPriceDisabledTracking());
                var tsListNormal = PerformanceHelper.RunActionMeasurePerformance(() => updateListTotalPriceNormal());
                var tsDetachedEntityDisabledTracking = PerformanceHelper.RunActionMeasurePerformance(() => updateDetachedEntityTotalPriceDisabledTracking());
                var tsDetachedEntityNormal = PerformanceHelper.RunActionMeasurePerformance(() => updateDetachedEntityTotalPriceNormal());
                results.Add(new Performance
                {
                    ListDisabledTracking = tsListDisabledTracking,
                    ListNormal = tsListNormal,
                    DetachedEntityDisabledTracking = tsDetachedEntityDisabledTracking,
                    DetachedEntityNormal = tsDetachedEntityNormal
                });
            }

            var detachedEntityDisabledTrackingAvg = results.Average(x => x.DetachedEntityDisabledTracking.TotalMilliseconds);
            Console.WriteLine("detachedEntityDisabledTrackingAvg: {0} ms.", detachedEntityDisabledTrackingAvg);

            var detachedEntityNormalAvg = results.Average(x => x.DetachedEntityNormal.TotalMilliseconds);
            Console.WriteLine("detachedEntityNormalAvg: {0} ms.", detachedEntityNormalAvg);

            var listDisabledTrackingAvg = results.Average(x => x.ListDisabledTracking.TotalMilliseconds);
            Console.WriteLine("listDisabledTrackingAvg: {0} ms.", listDisabledTrackingAvg);

            var listNormalAvg = results.Average(x => x.ListNormal.TotalMilliseconds);
            Console.WriteLine("listNormalAvg: {0} ms.", listNormalAvg);
        }

        private static void updateDetachedEntityTotalPriceNormal()
        {
            using (var context = new MyContext(withTracking: true))
            {
                var detachedEntity = new Factor { Id = 1, TotalPrice = 10 };

                var attachedEntity = context.Factors.Find(detachedEntity.Id);
                if (attachedEntity != null)
                {
                    attachedEntity.TotalPrice = 100;

                    context.SaveChanges();
                }
            }
        }

        private static void updateDetachedEntityTotalPriceDisabledTracking()
        {
            using (var context = new MyContext(withTracking: false))
            {
                var detachedEntity = new Factor { Id = 2, TotalPrice = 10 };
                detachedEntity.TotalPrice = 200;

                context.CustomUpdate(detachedEntity); // custom update with change tracking disabled.
                context.SaveChanges();
            }
        }

        private static void updateListTotalPriceNormal()
        {
            using (var context = new MyContext(withTracking: true))
            {
                foreach (var item in context.Factors)
                {
                    item.TotalPrice += 10; // normal update with change tracking enabled.
                }
                context.SaveChanges();
            }
        }

        private static void updateListTotalPriceDisabledTracking()
        {
            using (var context = new MyContext(withTracking: false))
            {
                foreach (var item in context.Factors)
                {
                    item.TotalPrice += 10;
                    //نیازی به این دو سطر نیست
                    //context.ChangeTracker.DetectChanges();  // هربار باید محاسبه صورت گیرد در غیراینصورت وضعیت تغییر نیافته گزارش می‌شود
                    //context.CustomUpdate(item); // custom update with change tracking disabled.
                }
                context.ChangeTracker.DetectChanges();  // در غیراینصورت وضعیت تغییر نیافته گزارش می‌شود
                context.SaveChanges();
            }
        }

        private static void startDb()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            // Forces initialization of database on model changes.
            using (var context = new MyContext())
            {
                context.Database.Initialize(force: true);
            }
        }
    }

    public class PerformanceHelper
    {
        public static TimeSpan RunActionMeasurePerformance(Action action)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            action();

            stopwatch.Stop();
            return stopwatch.Elapsed;
        }
    }
}
نتیجه این آزمایش بعد از 20 بار اجرا و اندازه گیری:
 detachedEntityDisabledTrackingAvg: 22.32089 ms.
detachedEntityNormalAvg: 54.546815 ms.
listDisabledTrackingAvg: 413.615445 ms.
listNormalAvg: 393.194625 ms.
در حالت کار با یک شیء ساده، به روز رسانی حالت منقطع بسیار سریعتر است (چون یکبار رفت و برگشت کمتری داره به دیتابیس).
در حالت کار با لیستی از اشیاء دریافت شده از بانک اطلاعاتی، به روز رسانی حالت متصل به Context سریعتر است.
اشتراک‌ها
پیاده سازی مفاهیم SOLID در ASP.NET Core توسط نویسنده‌ی AutoMapper

ASP.NET Core 1.0 is the ground-up rewrite of ASP.NET, MVC and Web API, bringing a new paradigm in building web applications and APIs in .NET. With this rewrite brought new techniques in building SOLID applications, and updated some existing patterns and tools.
In this session, we'll take a lap around some of the major extension points of ASP.NET Core 1.0, walking through how these features can help us build cleaner, more maintainable systems. We'll cover web APIs, traditional MVC applications, controllers, views, filters, dependency injection, tag helpers and more. With a SOLID foundation, our ASP.NET Core applications will be dead simple to build and maintain.
 

پیاده سازی مفاهیم SOLID در ASP.NET Core توسط نویسنده‌ی AutoMapper
مطالب
اعتبارسنجی در Entity framework Code first قسمت اول
در سری مباحث آموزشی EntityFramework وحیدی نصیری عزیز بصورت مختصر با اعتبار سنجی داده‌ها آشنا شدیم، در این آموزش سه قسمتی سعی می‌کنیم شناخت بیشتری از اعتبار سنجی داده در EF  بدست بیاریم. 
در EF CodeFirst بصورت پیش فرض پس از فراخوانی متد ()SaveChanges اعتبار سنجی داده‌ها انجام می‌پذیرد؛ در صورتیکه که اعتبار سنجی با موفقیت انجام نشود با استثنای DbEntityValidationException روبرو می‌شویم که در اینجا از خاصیت  EntityValidationErrors جهت اعلام خطاهای اعتبارسنجی استفاده می‌شود. EntityValidationErrors مجموعه‌ای از خطاهای مربوط به هر موجودیت می‌باشد  و هر EntityValidationErrors شامل یک خاصیت ValidationErrors که خود مجموعه‌ای از خطاهای مربوط به property‌ها می‌باشد. در تصویر زیر به خوبی می‌تونید این قضیه رو مشاهده کنید.


برای درک بهتر موضوع به ساختار کلاس‌های اعتبارسنجی در تصویر بعدی دقت کنید.

  • اعتبار سنجی در چه قسمت هایی اتفاق می‌افتد:
1.Property   
2.Entity
3.Context
الف - Property :
در بخش سوم آموزش EF Code First با متادیتای مورد استفاده در EF و طرز استفاده از آنها آشنا شدیم.
        [Required(ErrorMessage = "لطفا نام نویسنده را مشخص نمائید")]
        public string AuthorName { set; get; }

        [StringLength(100,MinimumLength=3,ErrorMessage="حداقل سه حرف و حداکثر 100 حرف وارد نمایید.")]
        public string AuthorName { set; get; }
همانطور که در ادامه می‌بینید برای اعتبار سنجی فقط به متادیتا‌های واقع در DataAnnotations.dll نیاز داریم. بقیه متادیتاها مربوط به نگاشت روابط موجودیتها و نحوه ذخیره اون در دیتابیس می‌باشد.
Validation Attributes
AssemblySystem.ComponentModel.DataAnnotations.dll
NamespaceSystem.ComponentModel.DataAnnotations
StringLength
RegularExpression
DataType
Required
Range
CustomValidation
Mapping Attributes
AssemblyEntityFramework.dll
NamespaceSystem.ComponentModel.DataAnnotations
Key
Column,Table
ComplexType
ConCurrency
TimeStamp
DatabaseGenerated
ForeignKey
InverseProperty
MaxLength
MinLength
NotMapped
ب- Entity  :
برای اعتبار سنجی یک موجودیت باید اینترفیس  IValidatableObject پیاده سازی شود:
public class Blog : IValidatableObject
    {
       
        public int blogID { set; get; }

        [Required(ErrorMessage = "لطفا عنوان وبلاگ را مشخص نمائید")]
        public string Title { set; get; }
        [Required(ErrorMessage = "لطفا نام نویسنده را مشخص نمائید")]
        [StringLength(100,MinimumLength=3,ErrorMessage="حداقل سه حرف و حداکثر 100 حرف وارد نمایید.")]
        public string AuthorName { set; get; }
    

        public IEnumerable<ValidationResult> Validate(ValidationContext ValidationContext)
        {
             if (this.AuthorName =="بدون نام")
                yield return new ValidationResult
                    ("این نام برای نام نویسنده مجاز نمی‌باشد.", new[] { "AuthorName"});

            if (this.AuthorName == this.Title)
                yield return new ValidationResult
                    (" نام نویسنده وبلاگ و عنوان وبلاگ نمی‌تواند همسان باشد.!", new[] { "AuthorName", "Title" });
        }
    }

نکته: در بررسی هم نام بودن نام نویسنده و  نام وبلاگ هردو خاصیت  ("AuthorName", "Title") رو درج کردیم اینکار باعث ایجاد دو خطای اعتبارسنجی می‌شود.

پ- Context  :
برای اعتبار سنجی در سطح Context  باید متد ()ValidateEntity واقع در کلاس DbContext را تحریف کنیم. این قسمت در بخش دوم مقاله کامل شرح داده خواهد شد.
  protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, System.Collections.Generic.IDictionary<object, object> items)
        {
            return base.ValidateEntity(entityEntry, items);
        }

نحوه فراخوانی اعتبار سنجی ها:
// اعتبار سنجی یک خاصیت
ICollection<DbValidationError> ValidationProperty = Context.Entry(Blog).Property(p => p.AuthorName).GetValidationErrors();

// اعتبار سنجی یک موجودیت
DbEntityValidationResult ValidationEntity = Context.Entry(Blog).GetValidationResult();

// اعتبار سنجی همه موجودیت‌ها
 IEnumerable<DbEntityValidationResult> ValidationContext = Context.GetValidationErrors();

نکته 
: در اعتبار  سنجی Context  بصورت پیش فرض فقط موجودیت‌های جدید و یا تغییر یافته اعتبار سنجی می‌شوند. 
EntityState.Added ||  EntityState.Modified

ترتیب فراخوانی اعتبار سنجی‌ها :
ابتدا اعتبار سنجی روی Property انجام می‌گیرد در صورتی که خطایی وجود نداشته باشد اعتبار سنجی مرحله بعد یعنی موجودیت‌ها بررسی می‌شود. اگر در مرحله اعتبار سنجی خاصیت‌ها خطایی وجود داشته باشد اعتبار سنجی موجودیت انجام نمی‌گیرد. ترتیب اعتبار سنجی در مرحله Context  بستگی به نحوه پیاده سازی ما دارد که در بخش دوم آموزش شرح داده خواهد شد.

سوال:
  1. اعتبار سنجی چند زبانی رو چگونه تعریف کنیم؟
  2. متد GetValidationErrors() رو در الگوی Repository , UOW  چگونه پیاده سازی کنیم؟
  3. آیا اعتبار سنجی در کنار موجودیت‌ها از نظر معماری چند لایه کار درستی می‌باشد؟ 
با پاسخ دادن به سوالات بالا در قالب نظر و یا مقاله به تکمیل موضوع کمک کنید و فضای آموزشی سایت رو رونق ببخشید.
نظرات مطالب
وی‍‍ژگی های پیشرفته ی AutoMapper - قسمت دوم
سلام جناب صاحب
ببخشید من یه کاربر مبتدیم سوالم رو خیلی ساده میگم.
من یه پروژه سیلور mvvm دارم با سرویس web api
اول اینکه Automapper تو سیلور به چه صورت عمل میکنه ؟
دوم اینکه مدل‌های اصلی سمت سرور نوشته میشن. حالا DTO این مدل‌ها باید سمت سیلور نوشته بشن ؟
سوم اینکه امکان نگاشت دو یا چند مدل به صورت همزمان به یک DTO وجود دارد ؟
چهارم اینکه تعریف نگاشت باید در سمت سرور باشد یا سیلور ؟ به عبارت دیگه در کنترلر سمت سرور یا در viewmodel سمت سیلور یا هیچکدام ؟
با تشکر
بازخوردهای دوره
ایجاد یک کلاس جدید پویا و وهله‌ای از آن در زمان اجرا توسط Reflection.Emit
برای ساده سازی و همچنین کپسوله کردن این عملیات، مراجعه کنید به مطالب زیر. در اینجا یک ClassGenerator با استفاده از Reflection Emit تهیه کرده‌اند:
Power of Reflection Emit 
How to create a class with properties at run time