مطالب
Microbenchmark
What Is Micro Benchmark? Micro benchmark is a benchmark designed to measure the performance of a very small and specific piece of code. (^)
البته این موضوع امروزه بیشتر در Java مطرحه تا دات نت (^ و ^ و ^) اما مفاهیم اصلی مختص یک زبان یا پلتفرم نیست.
وقتی در مورد آزمایش بار برای مقایسه کارایی کلاس StrigBuilder تحقیق میکردم به مطلب جالبی برخورد کردم. خلاصش این میشه که برای تست بار قسمتهایی از کدتون میتونین زمان موردنیاز برای اجرای اون کد رو بررسی کنین و چون ممکنه انجام این کار چندین بار نیاز بشه بهتره از متد زیر برای اینکار استفاده کنین:
static void Profile(string description, int iterations, Action func) {
    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    // warm up 
    func();

    var watch = Stopwatch.StartNew();
    for (int i = 0; i < iterations; i++) {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
سه خط اول متد بالا برای آماده‌سازی حافظه جهت اجرای تست موردنظر است. برای آشنایی بیشتر با نحوه عملکرد Garbage Collector (^ و ^ و ^) خوندن کتاب فوق العاده CLR via C# - 3rd ed رو پیشنهاد میکنم (فصلهای 21 و 22).
سپس یکبار اکشن موردنظر (که حاوی قطعه کد موردنظره) اجرا میشه تا مسائل مربوطه به بارگذاری‌های اولیه در نتیجه تست تاثیر نزاره (warm up).
در نهایت هم آزمایش بار برای تعداد تکرار درخواست شده انجام میشه و زمان اجرای اون در خروجی چاپ میشه.
برای استفاده از متد فوق میشه از کد زیر استفاده کرد:
Profile("a descriptions", how_many_iterations_to_run, () =>
{
   // ... code being profiled
});
و برای استفاده از این متد در آزمایش کارایی کلاس StringBuilder میشه از کدی شبیه به کد زیر استفاده کزد:
var iterations = 10000000;
var testString = ".NET Tips is awesme!";
do
{
  var sb1 = new StringBuilder(testString);
  var sb2 = new StringBuilder(testString) { Capacity = testString.Length * iterations };
  try
  {
    Profiler.Profile("StringBuilder Profiler", iterations, () => sb1.Append(testString));
    Profiler.Profile("StringBuilder Capacity Profiler", iterations, () => sb2.Append(testString));
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex.Message);
  }
  finally
  {
    Console.WriteLine("----------------------------------------------------------------");
    sb1.Clear();
    sb2.Clear();
  }
} while (Console.ReadKey(true).Key == ConsoleKey.C); // C = continue
البته برای اینکه عملیات مقدار دهی خاصیت Capacity در قسمت warm up متد profile نتایج رو تحت تاثیر قرار نده برای این تست من اون قسمت رو کامنت کردم (اگر این کار رو نکنین زمانهای بدست اومده برای هر دو مورد یکی خواهد بود). اجرای کد بالا نتایج زیر رو تو سیستم من ارائه داد:

می‌بینین که نتایج استفاده از متد موردبحث کمی فرق داره و افزایش کارایی در حالت استفاده از پراپرتی Capacity دیگه حدود 3 برابر نیست و حدود 2 دو برابره. البته زمان بدست اومده برای هر دو مورد نسبت به قبل کاهش داشته که بیشترش میتونه مربوطه به عدم درنظر گرفتن زمان موردنیاز برای ایجاد کلاس StringBuilder در این تست جدید باشه (چون بعید میدونم عملیات پاکسازی حافظه توسط GC تو این تست تاثیر چندانی داشته باشه). درهر حال نتایج این تست بیشتر به واقعیت نزدیکه!
مطالب دوره‌ها
ایندکس‌ها در RavenDB
RavenDB یک Document database است و در این نوع بانک‌های اطلاعاتی، اسکیما و ساختار مشخصی وجود ندارد. شاید اینطور به نظر برسد، زمانیکه با دات نت کلاینت RavenDB کار می‌کنیم، یک سری کلاس مشخص دات نتی داشته و این‌ها ساختار اصلی کار را مشخص می‌کنند. اما در عمل RavenDB چیزی از این کلاس‌ها و خواص نمی‌داند و این کلاس‌های دات نتی صرفا کمکی هستند جهت سهولت اعمال Serialization و Deserialization اطلاعات. زمانیکه اطلاعاتی را در RavenDB ذخیره می‌کنیم، هیچ نوع قیدی در مورد ساختار نوع سندی که در حال ذخیره است، اعمال نمی‌شود.
خوب؛ اکنون این سؤال مطرح می‌شود که RavenDB چگونه اطلاعاتی را در این اسناد بدون اسکیما جستجو می‌کند؟ اینجا است که مفهوم و کاربرد ایندکس‌ها مطرح می‌شوند. ما در قسمت قبل که کوئری نویسی مقدماتی را بررسی کردیم، عملا ایندکس خاصی را به صورت دستی جهت انجام جستجو‌ها ایجاد نکردیم؛ از این جهت که خود RavenDB به کمک امکانات dynamic indexing آن، پیشتر اینکار را انجام داده است. برای نمونه به سطر ارسال کوئری به سرور، که در قسمت قبل ارائه شد، دقت کنید. در اینجا ارسال کوئری به indexes/dynamic کاملا مشخص است:
Request #   2: GET     - 3,818 ms - <system>   - 200 - /indexes/dynamic/Questions?&query=Title%3ARaven*&pageSize=128

Dynamic Indexes یا ایندکس‌های پویا

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


Static indexes یا ایندکس‌های ایستا

ایندکس‌های پویا به دلیل وقفه ابتدایی که برای تولید آن‌ها وجود خواهد داشت، شاید آنچنان مطلوب به نظر نرسند. اینجا است که مفهوم ایندکس‌های ایستا مطرح می‌شوند. در این حالت ما به RavenDB خواهیم گفت که چه چیزی را ایندکس کند. برای تولید ایندکس‌های ایستا، از مفاهیم Map/Reduce که در پیشنیازهای دوره جاری در مورد آن بحث شد، استفاده می‌گردد. خوشبختانه تهیه Map/Reduceها در RavenDB پیچیده نبوده و کل عملیات آن توسط کوئری‌های LINQ قابل پیاده سازی است.
تهیه ایندکس‌های پویا نیز در تردهای پس‌زمینه انجام می‌شوند. از آنجائیکه RavenDB برای اعمال Read، بهینه سازی شده است، با ارسال یک کوئری به آن، این بانک اطلاعاتی، کلیه اطلاعات آماده را در اختیار شما قرار خواهد داد؛ صرفنظر از اینکه کار تهیه ایندکس تمام شده است یا خیر.


چگونه یک ایندکس ایستا را ایجاد کنیم؟

اگر به کنسول مدیریتی سیلورلایت RavenDB مراجعه کنیم، حاصل کوئری‌های LINQ قسمت قبل را در برگه‌ی ایندکس‌های آن می‌توان مشاهده کرد:


در اینجا بر روی دکمه Edit کلیک نمائید، تا با نحوه تهیه این ایندکس پویا آشنا شویم:


این ایندکس، یک نام داشته به همراه قسمت Map از پروسه Map/Reduce که توسط یک کوئری LINQ تهیه شده است. کاری که در اینجا انجام شده، ایندکس کردن کلیه سؤالات، بر اساس خاصیت عنوان آن‌ها است.
اکنون اگر بخواهیم همین کار را با کدنویسی انجام دهیم، به صورت زیر می‌توان عمل کرد:
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;
using Raven.Client;
using Raven.Client.Linq;
using Raven.Client.Indexes;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                store.DatabaseCommands.PutIndex(
                name: "Questions/ByTitle",
                indexDef: new IndexDefinitionBuilder<Question>
                {
                    Map = questions => questions.Select(question => new { Title = question.Title } )
                });
            }
        }
    }
}
کار با شیء DatabaseCommands یک DocumentStore شروع می‌شود. سپس توسط متد PutIndex آن می‌توان یک ایندکس جدید را تعریف کرد. این متد نیاز به نام ایندکس ایجاد شده و همچنین حداقل، متد Map آن‌را دارد. برای این منظور از شیء IndexDefinitionBuilder برای تعریف نحوه جمع آوری اطلاعات ایندکس کمک خواهیم گرفت. در اینجا خاصیت Map آن‌را باید توسط یک کوئری LINQ که فیلدهای مدنظر را بازگشت می‌دهد، مقدار دهی کنیم.
برنامه را اجرا کرده و سپس به کنسول مدیریتی تحت وب RavenDB، قسمت ایندکس‌های آن مراجعه کنید. در اینجا می‌توان ایندکس جدید ایجاد شده را مشاهده کرد:


هرچند همین اعمال را در کنسول مدیریتی نیز می‌توان انجام داد، اما مزیت آن در سمت کدها، دسترسی به intellisense و نوشتن کوئری‌های strongly typed است.

روش استفاده از store.DatabaseCommands.PutIndex اولین روش تولید Index در RavenDB با کدنویسی است. روش دوم، بر اساس ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود و مناسب است برای حالتیکه نمی‌خواهید کدهای تولید ایندکس، با کدهای سایر قسمت‌های برنامه مخلوط شوند:
    public class QuestionsByTitle : AbstractIndexCreationTask<Question>
    {
        public QuestionsByTitle()
        {
            Map = questions => questions.Select(question => new { Title = question.Title });
        }
    }
در اینجا با ایجاد یک کلاس جدید و ارث بری از کلاس AbstractIndexCreationTask کار شروع می‌شود. سپس در سازنده این کلاس، خاصیت Map را مقدار دهی می‌کنیم. مقدار آن نیز یک کوئری LINQ است که کار Select فیلدهای شرکت دهنده در کار تهیه ایندکس را انجام می‌دهد.
اکنون برای معرفی آن به برنامه باید از متد IndexCreation.CreateIndexes استفاده کرد. این متد، نیاز به دریافت اسمبلی محل تعریف کلاس‌های تولید ایندکس را دارد. به این ترتیب تمام کلاس‌های مشتق شده از AbstractIndexCreationTask را یافته و ایندکس‌های متناظری را تولید می‌کند.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsByTitle).Assembly, store);
            }
این روش، قابلیت نگهداری و نظم بهتری دارد.


استفاده از ایندکس‌های ایستای ایجاد شده

تا اینجا موفق شدیم ایندکس‌های ایستای خود را با کد نویسی ایجاد کنیم. در ادامه قصد داریم از این ایندکس‌ها در کوئری‌های خود استفاده نمائیم.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    var questions = session.Query<Question>(indexName: "QuestionsByTitle")
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }
                }
            }
استفاده از ایندکس تعریف شده نیز بسیار ساده می‌باشد. تنها کافی است نام آن‌را به متد Query ارسال نمائیم. اینبار اگر به خروجی کنسول سرور RavenDB دقت کنیم، از ایندکس indexes/QuestionsByTitle بجای ایندکس‌های پویا استفاده کرده است:
Request # 147: GET     -    58 ms - <system>   - 200 - /indexes/QuestionsByTitle?&query=Title%3ARaven*&pageSize=128
        Query: Title:Raven*
        Time: 7 ms
        Index: QuestionsByTitle
        Results: 2 returned out of 2 total.
روش مشخص سازی نام ایندکس با استفاده از رشته‌ها، با هر دو روش store.DatabaseCommands.PutIndex و استفاده از AbstractIndexCreationTask سازگار است. اما اگر ایندکس‌های خود را با ارث بری از AbstractIndexCreationTask ایجاد کرده‌ایم، می‌توان نام کلاس مشتق شده را به صورت یک آرگومان جنریک دوم به متد Query به شکل زیر ارسال کرد تا از مزایای تعریف strongly typed آن نیز بهره‌مند شویم:
                    var questions = session.Query<Question, QuestionsByTitle>()
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);

ایجاد ایندکس‌های پیشرفته با پیاده سازی Map/Reduce

حالتی را در نظر بگیرید که در آن قصد داریم تعداد عنوان‌های سؤالات مانند هم را بیابیم (یا تعداد مطالب گروه‌های مختلف یک وبلاگ را محاسبه کنیم). برای انجام اینکار با سرعت بسیار بالا، می‌توانیم از ایندکس‌هایی با قابلیت محاسباتی در RavenDB استفاده کنیم. کار با ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود. آرگومان جنریک اول آن، نام کلاسی است که در تهیه ایندکس شرکت خواهد داشت و آرگومان دوم (و اختیاری) ذکر شده، نتیجه عملیات Reduce است:
    public class QuestionsCountByTitleReduceResult
    {
        public string Title { set; get; }
        public int Count { set; get; }
    }

    public class QuestionsCountByTitle : AbstractIndexCreationTask<Question, QuestionsCountByTitleReduceResult>
    {
        public QuestionsCountByTitle()
        {
            Map = questions => questions.Select(question =>
                                                    new
                                                    {
                                                        Title = question.Title,
                                                        Count = 1
                                                    });
            Reduce = results => results.GroupBy(x => x.Title)
                                       .Select(g =>
                                                   new
                                                   {
                                                       Title = g.Key,
                                                       Count = g.Sum(x => x.Count)
                                                   });
        }
    }
در اینجا یک ایندکس پیشرفته را تعریف کرده‌ایم که در آن در قسمت Map، کار ایندکس کردن تک تک عنوان‌ها انجام خواهد شد. به همین جهت مقدار Count در این حالت، عدد یک است. در قسمت Reduce، بر روی نتیجه قسمت Map کوئری LINQ دیگری نوشته شده و تعداد عنوان‌های همانند، با گروه بندی اطلاعات، شمارش گردیده است.
اکنون برای استفاده از این ایندکس، ابتدا توسط متد IndexCreation.CreateIndexes، کار معرفی آن به RavenDB صورت گرفته و سپس متد Query سشن باز شده، دو آرگومان جنریگ را خواهد پذیرفت. اولین آرگومان، همان نتیجه Map/Reduce است و دومین آرگومان نام کلاس ایندکس جدید تعریف شده می‌باشد:
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsCountByTitle).Assembly, store);

                using (var session = store.OpenSession())
                {
                    var result = session.Query<QuestionsCountByTitleReduceResult, QuestionsCountByTitle>()
                                         .FirstOrDefault(x => x.Title == "Raven") ?? new QuestionsCountByTitleReduceResult();
                    Console.WriteLine(result.Count);
                }
            }
در کوئری فوق چون عملیات بر روی نتیجه نهایی باید صورت گیرد از FirstOrDefault استفاده شده است. این کوئری در حقیقت بر روی قسمت Reduce پیشتر محاسبه شده، اجرا می‌شود.
مطالب دوره‌ها
کوئری نویسی مقدماتی در RavenDB
با شروع کوئری نویسی مقدماتی در RavenDB، در قسمت اول این مباحث، توسط فراخوانی متد Load یک سشن، آشنا شدید. در ادامه مباحث تکمیلی آن‌را مرور خواهیم کرد.

امکان استفاده از LINQ در RavenDB

RavenDB از LINQ جهت کوئری نویسی پشتیبانی می‌کند. برای استفاده از آن، در ادامه مطلب اول، ابتدا سرور RavenDB را اجرا نموده و سپس برنامه کنسول را به نحو ذیل تغییر دهید:
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    var questions = session.Query<Question>().Where(x => x.Title.StartsWith("Raven"));
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }
                }
            }
        }
    }
}
در RavenDB برای دسترسی به امکانات LINQ، کار با متد Query یک سشن آغاز می‌شود و پس از آن، امکان استفاده از متدهای متداول LINQ مانند مثال فوق وجود خواهد داشت. البته بدیهی است مباحثی مانند JOIN و امثال آن در یک بانک اطلاعاتی NoSQL پشتیبانی نمی‌شود. ضمنا باید درنظر داشت که مبحث safe by default در اینجا نیز اعمال می‌شود. برای مثال اگر به کنسول سرور RavenDB که در حال اجرا است مراجعه کنید، یک چنین خروجی را حین اجرای مثال فوق می‌توان مشاهده کرد که در آن pageSize پیش فرضی اعمال شده است:
Available commands: cls, reset, gc, q
Request #   1: GET     -   179 ms - <system>   - 404 - /docs/Raven/Replication/Destinations
Request #   2: GET     - 3,818 ms - <system>   - 200 - /indexes/dynamic/Questions?&query=Title%3ARaven*&pageSize=128
        Query: Title:Raven*
        Time: 3,494 ms
        Index: Auto/Questions/ByTitle
        Results: 2 returned out of 2 total.
یعنی در عمل کوئری‌را که اجرا کرده است، شبیه به کوئری ذیل می‌باشد و یک Take پیش فرض بر روی آن اعمال شده است:
var questions = session.Query<Question>().Where(x => x.Title.StartsWith("Raven")).Take(128);
علت این مساله نیز به تصمیم نویسنده اصلی آن بر می‌گردد؛ ایشان پیش از شروع به تهیه RavenDB، کار تهیه انواع و اقسام پروفایلرهای مهم ORMهای معروف مانند NHibernate و Entity framework را انجام داده است و در این حین، یکی از مهم‌ترین مشکلاتی را که با آن‌ها در کدهای متداول برنامه نویس‌ها یافته است، unbounded queries است. کوئری‌هایی که حد و مرزی برای بازگشت اطلاعات قائل نمی‌شوند. داشتن این نوع کوئری‌ها با تعداد بالای کاربر، یعنی مصرف بیش از حد RAM بر روی سرور، به همراه بار پردازشی بیش از حد و غیر ضروری. چون عملا حتی اگر 10 هزار رکورد بازگشت داده شوند، عموم برنامه نویس‌ها حداکثر 100 رکورد آن‌را در یک صفحه نمایش می‌دهند و نه تمام رکوردها را.


ارتباط Lucene.NET و RavenDB

کل LINQ API تهیه شده در RavenDB یک محصور کننده امکانات Lucene.NET است. اگر پیشتر با Lucene.NET کار کرده باشید، در خروجی حالت دیباگ کنسول سرور فوق، سطر «Query: Title:Raven*» آشنا به نظر خواهد رسید. دقیقا کوئری LINQ نوشته شده به یک کوئری با Syntax مخصوص Lucene.NET ترجمه شده‌است. برای نمونه اگر علاقمند باشید که مستقیما کوئری‌های خاص لوسین را در RavenDB اجرا کنید، از Syntax ذیل می‌توان استفاده کرد:
var questions = session.Advanced.LuceneQuery<Question>().Where("Title:Raven*").ToList();
و یا اگر علاقمند به حفظ کردن Syntax خاص لوسین نیستید، یک سری متد الحاقی خاص نیز در اینجا برای LuceneQuery تدارک دیده شده است. برای مثال کوئری رشته‌ای فوق، معادل کوئری strongly typed ذیل است:
var questions = session.Advanced.LuceneQuery<Question>().WhereStartsWith(x => x.Title, "Raven").ToList();

استفاده مجدد از کوئری‌ها در RavenDB

در RavenDB، متد Query به صورت immutable تعریف شده است و متد LuceneQuery حالت mutable دارد (ترکیبات آن نیز یک وهله است).
یک مثال:
var query = session.Query<User>().Where(x => x.Name.StartsWith("A"));
var ageQuery = query.Where(x => x.Age > 21);
var eyeQuery = query.Where(x => x.EyeColor == "blue");
در اینجا از کوئری ابتدایی، در دو کوئری مجزا استفاده مجدد شده است. ترجمه خروجی سه کوئری فوق به نحو زیر است:
query - Name:A*
ageQuery - (Name:A*) AND (Age_Range:{Ix21 TO NULL})
eyeQuery - (Name:A*) AND (EyeColor:blue)
به این معنا که زمانیکه به eyeQuery رسیدیم، نتیجه ageQuery با آن ترکیب نمی‌شود؛ چون متد Query از نوع immutable است.
در ادامه اگر همین سه کوئری فوق را با فرمت LuceneQuery تهیه کنیم، به عبارات ذیل خواهیم رسید:
var luceneQuery = session.Advanced.LuceneQuery<User>().WhereStartsWith(x => x.Name, "A");
var ageLuceneQuery = luceneQuery.WhereGreaterThan(x => x.Age, 21);
var eyeLuceneQuery = luceneQuery.WhereEquals(x => x.EyeColor, "blue");
در خروجی‌های این سه کوئری، مورد سوم مهم است:
luceneQuery - Name:A* 
ageLuceneQuery - Name:A* Age_Range:{Ix21 TO NULL}
eyeLuceneQuery - Name:A* Age_Range:{Ix21 TO NULL} EyeColor:blue
همانطور که مشاهده می‌کنید، کوئری سوم، عبارت کوئری دوم را نیز به همراه دارد؛ این مورد دقیقا مفهوم اشیاء mutable یا تک وهله‌ای است مانند LuceneQuery در اینجا.


And و Or شدن کوئری‌های ترکیبی در RavenDB
در مثال استفاده مجدد از کوئری‌ها، زمانیکه از Where استفاده شد، بین عبارات حاصل AND قرار گرفته است. این مورد را به نحو ذیل می‌توان تنظیم کرد و مثلا به OR تغییر داد:
session.Advanced.LuceneQuery<User>().UsingDefaultOperator(QueryOperator.And);

صفحه بندی اطلاعات در RavenDB

در ابتدای بحث عنوان شد که کوئری LINQ اجرا شده در RavenDB، یک Take مخفی و پیش فرض تنظیم شده به 128 آیتم را دارد. اکنون سؤال این خواهد بود که چگونه می‌توان اطلاعات را به صورت صفحه بندی شده، بر اساس شماره صفحه خاصی نمایش داد.
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    int pageNumber = 0;
                    int resultsPerPage = 2;

                    var questions = session.Query<Question>()
                                           .Where(x => x.Title.StartsWith("Raven"))
                                           .Skip(pageNumber * resultsPerPage)
                                           .Take(resultsPerPage);
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }
                }
            }
        }
    }
}
برای انجام صفحه بندی در RavenDB، کافی است از متدهای Skip و Take بر اساس محاسباتی که مشاهده می‌کنید، استفاده گردد.


دریافت اطلاعات آماری کوئری اجرا شده

در RavenDB امکان دریافت یک سری اطلاعات آماری از کوئری اجرا شده نیز وجود دارد؛ برای مثال یک کوئری چند ثانیه طول کشیده است، چه تعدادی رکورد را بازگشت داده است و امثال آن. برای پیاده سازی آن، نیاز است از متد الحاقی Statistics به نحو ذیل استفاده کرد:
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;
using Raven.Client;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    int pageNumber = 0;
                    int resultsPerPage = 2;
                    RavenQueryStatistics stats;
                    var questions = session.Query<Question>()
                                           .Statistics(out stats)
                                           .Where(x => x.Title.StartsWith("Raven"))
                                           .Skip(pageNumber * resultsPerPage)
                                           .Take(resultsPerPage);
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }

                    Console.WriteLine("TotalResults: {0}", stats.TotalResults);
                }
            }
        }
    }
}
متد الحاقی Statistics پس از متد Query که نقطه آغازین نوشتن کوئری‌های LINQ است، فراخوانی شده و یک پارامتر out از نوع RavenQueryStatistics تعریف شده در فضای نام Raven.Client را دریافت می‌کند. پس از پایان کوئری می‌توان از این خروجی جهت نمایش اطلاعات آماری کوئری استفاده کرد.


امکانات ویژه فضای نام Raven.Client.Linq

یک سری متد الحاقی خاص جهت تهیه ساده‌تر کوئری‌های LINQ در فضای نام Raven.Client.Linq قرار دارند که پس از تعریف آن قابل دسترسی خواهند بود:
var list = session.Query<Question>().Where(q => q.By.In<string>(arrayOfUsers))).ToArray()
برای مثال در اینجا متد الحاقی جدید In را مشاهده می‌کنید که شبیه به کوئری SQL ذیل در دنیای بانک‌های اطلاعاتی رابطه‌ای عمل می‌کند:
 SELECT * FROM tbl WHERE data IN (1, 2, 3)

اتصال به RavenDB با استفاده از برنامه معروف LINQPad

اگر علاقمند باشید که کوئری‌های خود را در محیط برنامه معروف LINQPad نیز آزمایش کنید، درایور مخصوص RavenDB آن‌را از آدرس ذیل می‌توانید دریافت نمائید:
مطالب
تهیه XML امضاء شده جهت تولید مجوز استفاده از برنامه
اگر به فایل مجوز استفاده از برنامه‌‌ای مانند EF Profiler دقت کنید، یک فایل XML به ظاهر ساده بیشتر نیست:
<?xml version="1.0" encoding="utf-8"?>
<license id="17d46246-a6cb-4196-98a0-ff6fc08cb67f" expiration="2012-06-12T00:00:00.0000000" type="Trial" prof="EFProf">
  <name>MyName</name>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>b8N0bDE4gTakfdGKtzDflmmyyXI=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>IPELgc9BbkD8smXSe0sGqp5vS57CtZo9ME2ZfXSq/thVu...=</SignatureValue>
  </Signature>
</license>
در این فایل ساده متنی، نوع مجوز استفاده از برنامه، Trial ذکر شده است. شاید عنوان کنید که خوب ... نوع مجوز را به Standard تغییر می‌دهیم و فایل را ذخیره کرده و نهایتا استفاده می‌کنیم. اما ... نتیجه حاصل کار نخواهد کرد! حتی اگر یک نقطه به اطلاعات این فایل اضافه شود، دیگر قابل استفاده نخواهد بود. علت آن هم به قسمت Signature فایل XML فوق بر می‌گردد.
در ادامه به نحوه تولید و استفاده از یک چنین مجوزهای امضاء شده‌ای در برنامه‌های دات نتی خواهیم پرداخت.


تولید کلیدهای RSA

برای تهیه امضای دیجیتال یک فایل XML نیاز است از الگوریتم RSA استفاده شود.
برای تولید فایل XML امضاء شده، از کلید خصوصی استفاده خواهد شد. برای خواندن اطلاعات مجوز (فایل XML امضاء شده)، از کلیدهای عمومی که در برنامه قرار می‌گیرند کمک خواهیم گرفت (برای نمونه برنامه EF Prof این کلیدها را در قسمت Resourceهای خود قرار داده است).
استفاده کننده تنها زمانی می‌تواند مجوز معتبری را تولید کند که دسترسی به کلیدهای خصوصی تولید شده را داشته باشد.
        public static string CreateRSAKeyPair(int dwKeySize = 1024)
        {
            using (var provider = new RSACryptoServiceProvider(dwKeySize))
            {
                return provider.ToXmlString(includePrivateParameters: true);
            }
        }
امکان تولید کلیدهای اتفاقی مورد استفاده در الگوریتم RSA، در دات نت پیش بینی شده است. خروجی متد فوق یک فایل XML است که به همین نحو در صورت نیاز توسط متد provider.FromXmlString مورد استفاده قرار خواهد گرفت.


تهیه ساختار مجوز

در ادامه یک enum که بیانگر انواع مجوزهای برنامه ما است را مشاهده می‌کنید:
namespace SignedXmlSample
{
    public enum LicenseType
    {
        None,
        Trial,
        Standard,
        Personal
    }
}
به همراه کلاسی که اطلاعات مجوز تولیدی را دربر خواهد گرفت:
using System;
using System.Xml.Serialization;

namespace SignedXmlSample
{
    public class License
    {
        [XmlAttribute]
        public Guid Id { set; get; }
        
        public string Domain { set; get; }

        [XmlAttribute]
        public string IssuedTo { set; get; }

        [XmlAttribute]
        public DateTime Expiration { set; get; }

        [XmlAttribute]
        public LicenseType Type { set; get; }
    }
}
خواص این کلاس یا عناصر enum یاد شده کاملا دلخواه هستند و نقشی را در ادامه بحث نخواهند داشت؛ از این جهت که از مباحث XmlSerializer برای تبدیل وهله‌ای از شیء مجوز به معادل XML آن استفاده می‌شود. بنابراین المان‌های آن‌را مطابق نیاز خود می‌توانید تغییر دهید. همچنین ذکر ویژگی XmlAttribute نیز اختیاری است. در اینجا صرفا جهت شبیه سازی معادل مثالی که در ابتدای بحث مطرح شد، از آن استفاده شده است. این ویژگی راهنمایی است برای کلاس XmlSerializer تا خواص مزین شده با آن‌را به شکل یک Attribute در فایل نهایی ثبت کند.


تولید و خواندن مجوز دارای امضای دیجیتال

کدهای کامل کلاس تولید و خواندن یک مجوز دارای امضای دیجیتال را در اینجا مشاهده می‌کنید:
using System;
using System.IO;
using System.Security.Cryptography; // needs a ref. to `System.Security.dll` asm.
using System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace SignedXmlSample
{
    public static class LicenseGenerator
    {
        public static string CreateLicense(string licensePrivateKey, License licenseData)
        {
            using (var provider = new RSACryptoServiceProvider())
            {
                provider.FromXmlString(licensePrivateKey);
                var xmlDocument = createXmlDocument(licenseData);
                var xmlDigitalSignature = getXmlDigitalSignature(xmlDocument, provider);
                appendDigitalSignature(xmlDocument, xmlDigitalSignature);
                return xmlDocumentToString(xmlDocument);
            }
        }

        public static string CreateRSAKeyPair(int dwKeySize = 1024)
        {
            using (var provider = new RSACryptoServiceProvider(dwKeySize))
            {
                return provider.ToXmlString(includePrivateParameters: true);
            }
        }

        public static License ReadLicense(string licensePublicKey, string xmlFileContent)
        {
            var doc = new XmlDocument();
            doc.LoadXml(xmlFileContent);

            using (var provider = new RSACryptoServiceProvider())
            {
                provider.FromXmlString(licensePublicKey);

                var nsmgr = new XmlNamespaceManager(doc.NameTable);
                nsmgr.AddNamespace("sig", "http://www.w3.org/2000/09/xmldsig#");

                var xml = new SignedXml(doc);
                var signatureNode = (XmlElement)doc.SelectSingleNode("//sig:Signature", nsmgr);
                if (signatureNode == null)
                    throw new InvalidOperationException("This license file is not signed.");

                xml.LoadXml(signatureNode);
                if (!xml.CheckSignature(provider))
                    throw new InvalidOperationException("This license file is not valid.");

                var ourXml = xml.GetXml();
                if (ourXml.OwnerDocument == null || ourXml.OwnerDocument.DocumentElement == null)
                    throw new InvalidOperationException("This license file is coruppted.");

                using (var reader = new XmlNodeReader(ourXml.OwnerDocument.DocumentElement))
                {
                    var xmlSerializer = new XmlSerializer(typeof(License));
                    return (License)xmlSerializer.Deserialize(reader);
                }
            }
        }

        private static void appendDigitalSignature(XmlDocument xmlDocument, XmlNode xmlDigitalSignature)
        {
            xmlDocument.DocumentElement.AppendChild(xmlDocument.ImportNode(xmlDigitalSignature, true));
        }

        private static XmlDocument createXmlDocument(License licenseData)
        {
            var serializer = new XmlSerializer(licenseData.GetType());
            var sb = new StringBuilder();
            using (var writer = new StringWriter(sb))
            {
                var ns = new XmlSerializerNamespaces(); ns.Add("", "");
                serializer.Serialize(writer, licenseData, ns);
                var doc = new XmlDocument();
                doc.LoadXml(sb.ToString());
                return doc;
            }
        }

        private static XmlElement getXmlDigitalSignature(XmlDocument xmlDocument, AsymmetricAlgorithm key)
        {
            var xml = new SignedXml(xmlDocument) { SigningKey = key };
            var reference = new Reference { Uri = "" };
            reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
            xml.AddReference(reference);
            xml.ComputeSignature();
            return xml.GetXml();
        }

        private static string xmlDocumentToString(XmlDocument xmlDocument)
        {
            using (var ms = new MemoryStream())
            {
                var settings = new XmlWriterSettings { Indent = true, Encoding = Encoding.UTF8 };
                var xmlWriter = XmlWriter.Create(ms, settings);
                xmlDocument.Save(xmlWriter);
                ms.Position = 0;
                return new StreamReader(ms).ReadToEnd();
            }
        }
    }
}
توضیحات:
در حین کار با متد CreateLicense، پارامتر licensePrivateKey همان اطلاعاتی است که به کمک متد CreateRSAKeyPair قابل تولید است. توسط پارامتر licenseData، اطلاعات مجوز در حال تولید اخذ می‌شود. در این متد به کمک provider.FromXmlString، اطلاعات کلیدهای RSA دریافت خواهند شد. سپس توسط متد createXmlDocument، محتوای licenseData دریافتی به یک فایل XML نگاشت می‌گردد (بنابراین اهمیتی ندارد که حتما از ساختار کلاس مجوز یاد شده استفاده کنید). در ادامه متد getXmlDigitalSignature با در اختیار داشتن معادل XML شیء مجوز و کلیدهای لازم، امضای دیجیتال متناظری را تولید می‌کند. با استفاده از متد appendDigitalSignature، این امضاء را به فایل XML اولیه اضافه می‌کنیم. از این امضاء جهت بررسی اعتبار مجوز برنامه در متد ReadLicense استفاده خواهد شد.
برای خواندن یک فایل مجوز امضاء شده در برنامه خود می‌توان از متد ReadLicense استفاده کرد. توسط آرگومان licensePublicKey، اطلاعات کلید عمومی دریافت می‌شود. این کلید دربرنامه، ذخیره و توزیع می‌گردد. پارامتر xmlFileContent معادل محتوای فایل XML مجوزی است که قرار است مورد ارزیابی قرار گیرد.


مثالی در مورد نحوه استفاده از کلاس تولید مجوز

در ادامه نحوه استفاده از متدهای CreateLicense و  ReadLicense را ملاحظه می‌کنید؛ به همراه آشنایی با نمونه کلیدهایی که باید به همراه برنامه منتشر شوند:
using System;
using System.IO;

namespace SignedXmlSample
{
    class Program
    {
        static void Main(string[] args)
        {
            //Console.WriteLine(LicenseGenerator.CreateRSAKeyPair());
            writeLicense();            
            readLicense();

            Console.WriteLine("Press a key...");
            Console.ReadKey();
        }

        private static void readLicense()
        {
            var xml = File.ReadAllText("License.xml");
            const string publicKey = @"<RSAKeyValue>
                                    <Modulus>
                                        mBNKFIc/LkMfaXvLlB/+6EujPkx3vBOvLu8jdESDSQLisT8K96RaDMD1ORmdw2XNdMw/6ZBuJjLhoY13qCU9t7biuL3SIxr858oJ1RLM4PKhA/wVDcYnJXmAUuOyxP/vfvb798o6zAC1R2QWuzG+yJQR7bFmbKH0tXF/NOcSgbc=
                                    </Modulus>
                                    <Exponent>
                                        AQAB
                                    </Exponent>
                                 </RSAKeyValue>";

            var result = LicenseGenerator.ReadLicense(publicKey, xml);

            Console.WriteLine(result.Domain);
            Console.WriteLine(result.IssuedTo);
        }

        private static void writeLicense()
        {
            const string rsaData = @"<RSAKeyValue>
                    <Modulus>
                        mBNKFIc/LkMfaXvLlB/+6EujPkx3vBOvLu8jdESDSQLisT8K96RaDMD1ORmdw2XNdMw/6ZBuJjLhoY13qCU9t7biuL3SIxr858oJ1RLM4PKhA/wVDcYnJXmAUuOyxP/vfvb798o6zAC1R2QWuzG+yJQR7bFmbKH0tXF/NOcSgbc=
                    </Modulus>
                    <Exponent>
                        AQAB
                    </Exponent>
                    <P>
                        xwPKN77EcolMTD2O2Csv6k9Y4aen8UBVYjeQ4PtrNGz0Zx6I1MxLEFzRpiKC/Ney3xKg0Icwj0ebAQ04d5+HAQ==
                    </P>
                    <Q>
                        w568t0Xe6OBUfCyAuo7tTv4eLgczHntVLpjjcxdUksdVw7NJtlnOLApJVJ+U6/85Z7Ji+eVhuN91yn04pQkAtw==
                    </Q>
                    <DP>
                        svkEjRdA4WP5uoKNiHdmMshCvUQh8wKRBq/D2aAgq9fj/yxlj0FdrAxc+ZQFyk5MbPH6ry00jVWu3sY95s4PAQ==
                    </DP>
                    <DQ>
                        WcRsIUYk5oSbAGiDohiYeZlPTBvtr101V669IUFhhAGJL8cEWnOXksodoIGimzGBrD5GARrr3yRcL1GLPuCEvQ==
                    </DQ>
                    <InverseQ>
                        wIbuKBZSCioG6MHdT1jxlv6U1+Y3TX9sHED9PqGzWWpVGA+xFJmQUxoFf/SvHzwbBlXnG0DLqUvxEv+BkEid2w==
                    </InverseQ>
                    <D>                        Yk21yWdT1BfXqlw30NyN7qNWNuM/Uvh2eaRkCrhvFTckSucxs7st6qig2/RPIwwfr6yIc/bE/TRO3huQicTpC2W3aXsBI9822OOX4BdWCec2txXpSkbZW24moXu+OSHfAdYoOEN6ocR7tAGykIqENshRO7HvONJsOE5+1kF2GAE=
                    </D>
                  </RSAKeyValue>";

            string data = LicenseGenerator.CreateLicense(
                                                rsaData,
                                                new License
                                                {
                                                     Id = Guid.NewGuid(),
                                                     Domain = "dotnettips.info",
                                                     Expiration = DateTime.Now.AddYears(2),
                                                     IssuedTo = "VahidN",
                                                     Type = LicenseType.Standard
                                                });
            File.WriteAllText("License.xml", data);
        }
    }
}
ابتدا توسط متد CreateRSAKeyPair کلیدهای لازم را تهیه و ذخیره کنید. این کار یکبار باید صورت گیرد.
همانطور که مشاهده می‌کنید، اطلاعات کامل یک نمونه از آن، در متد writeLicense مورد نیاز است. اما در متد readLicense تنها به قسمت عمومی آن یعنی Modulus و Exponent نیاز خواهد بود (موارد قابل انتشار به همراه برنامه).

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

دریافت نسخه جنریک این مثال:
SignedXmlSample.zip
مطالب
تولید MiniDump در حین کرش برنامه‌های دات نت
با مطالعه‌ی سورس‌های محصولات اخیرا سورس باز شده‌ی مایکروسافت، نکات جالبی را می‌توان استخراج کرد. برای نمونه اگر سورس پروژه‌ی Orleans را بررسی کنیم، در حین بررسی اطلاعات استثناءهای رخ داده‌ی در برنامه، متد TraceLogger.CreateMiniDump نیز بکار رفته‌است. در این مطلب قصد داریم، این متد و نحوه‌ی استفاده‌ی از حاصل آن‌را بررسی کنیم.


تولید MiniDump در برنامه‌های دات نت


خلاصه‌ی روش تولید MiniDump در پروژه‌ی Orleans به صورت زیر است:
الف) حالت‌های مختلف تولید فایل دامپ که مقادیر آن قابلیت Or شدن را دارا هستند:
    [Flags]
public enum MiniDumpType
{
    MiniDumpNormal = 0x00000000,
    MiniDumpWithDataSegs = 0x00000001,
    MiniDumpWithFullMemory = 0x00000002,
    MiniDumpWithHandleData = 0x00000004,
    MiniDumpFilterMemory = 0x00000008,
    MiniDumpScanMemory = 0x00000010,
    MiniDumpWithUnloadedModules = 0x00000020,
    MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
    MiniDumpFilterModulePaths = 0x00000080,
    MiniDumpWithProcessThreadData = 0x00000100,
    MiniDumpWithPrivateReadWriteMemory = 0x00000200,
    MiniDumpWithoutOptionalData = 0x00000400,
    MiniDumpWithFullMemoryInfo = 0x00000800,
    MiniDumpWithThreadInfo = 0x00001000,
    MiniDumpWithCodeSegs = 0x00002000,
    MiniDumpWithoutManagedState = 0x00004000
}

ب) متد توکار ویندوز برای تولید فایل دامپ
public static class NativeMethods
{
    [DllImport("Dbghelp.dll")]
    public static extern bool MiniDumpWriteDump(
        IntPtr hProcess,
        int processId,
        IntPtr hFile,
        MiniDumpType dumpType,
        IntPtr exceptionParam,
        IntPtr userStreamParam,
        IntPtr callbackParam);
}

ج) فراخوانی متد تولید دامپ در برنامه
در اینجا نحوه‌ی استفاده از enum و متد MiniDumpWriteDump ویندوز را مشاهده می‌کنید:
public static class DebugInfo
{
    public static void CreateMiniDump(
        string dumpFileName, MiniDumpType dumpType = MiniDumpType.MiniDumpNormal)
    {
        using (var stream = File.Create(dumpFileName))
        {
            var process = Process.GetCurrentProcess();
            // It is safe to call DangerousGetHandle() here because the process is already crashing.
            NativeMethods.MiniDumpWriteDump(
                            process.Handle,
                            process.Id,
                            stream.SafeFileHandle.DangerousGetHandle(),
                            dumpType,
                            IntPtr.Zero,
                            IntPtr.Zero,
                            IntPtr.Zero);
        }
    }
 
    public static void CreateMiniDump(MiniDumpType dumpType = MiniDumpType.MiniDumpNormal)
    {
        const string dateFormat = "yyyy-MM-dd-HH-mm-ss-fffZ"; // Example: 2010-09-02-09-50-43-341Z
        var thisAssembly = Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly();
        var dumpFileName = string.Format(@"{0}-MiniDump-{1}.dmp",
                    thisAssembly.GetName().Name,
                    DateTime.UtcNow.ToString(dateFormat, CultureInfo.InvariantCulture));
 
        var path = Path.Combine(getApplicationPath(), dumpFileName);
        CreateMiniDump(path, dumpType);
    }
 
    private static string getApplicationPath()
    {
        return HttpContext.Current != null ?
            HttpRuntime.AppDomainAppPath :
            Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    }
}
متد MiniDumpWriteDump نیاز به اطلاعات پروسه‌ی جاری، به همراه هندل فایلی که قرار است اطلاعات را در آن بنویسد، دارد. همچنین dump type آن نیز می‌تواند ترکیبی از مقادیر enum مرتبط باشد.

یک مثال:
class Program
{
    static void Main(string[] args)
    {
        try
        {
            var zero = 0;
            Console.WriteLine(1 / zero);
        }
        catch (Exception ex)
        {
            Console.Write(ex);
            DebugInfo.CreateMiniDump(dumpType:
                                MiniDumpType.MiniDumpNormal |
                                MiniDumpType.MiniDumpWithPrivateReadWriteMemory |
                                MiniDumpType.MiniDumpWithDataSegs |
                                MiniDumpType.MiniDumpWithHandleData |
                                MiniDumpType.MiniDumpWithFullMemoryInfo |
                                MiniDumpType.MiniDumpWithThreadInfo |
                                MiniDumpType.MiniDumpWithUnloadedModules);
 
            throw;
        }
    }
}
در اینجا نحوه‌ی فراخوانی متد CreateMiniDump را در حین کرش برنامه مشاهده می‌کنید. پارامترهای اضافی دیگر سبب خواهند شد تا اطلاعات بیشتری از حافظه‌ی جاری سیستم، در دامپ نهایی قرار گیرند. اگر پس از اجرای برنامه، به پوشه‌ی bin\debug مراجعه کنید، فایل dmp تولیدی را مشاهده خواهید کرد.


نحوه‌ی بررسی فایل‌های dump


الف) با استفاده از Visual studio 2013

از به روز رسانی سوم VS 2013 به بعد، فایل‌های dump را می‌توان داخل خود VS.NET نیز آنالیز کرد (^ و ^ و ^). برای نمونه تصویر ذیل، حاصل کشودن فایل کرش مثال فوق است:


در اینجا اگر بر روی لینک debug managed memory کلیک کنید، پس از چند لحظه، آنالیز کامل اشیاء موجود در حافظه را در حین تهیه‌ی دامپ تولیدی، می‌توان مشاهده کرد. این مورد برای آنالیز نشتی‌های حافظه‌ی یک برنامه بسیار مفید است.


ب) استفاده از برنامه‌ی Debug Diagnostic Tool

برنامه‌ی Debug Diagnostic Tool را از اینجا می‌توانید دریافت کنید. این برنامه نیز قابلیت آنالیز فایل‌های دامپ را داشته و اطلاعات بیشتری را پس از آنالیز ارائه می‌دهد.


برای نمونه پس از آنالیز فایل دامپ تهیه شده توسط این برنامه، خروجی ذیل حاصل می‌شود:



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
MiniDumpTest.zip
مطالب
بیرون نگاه داشتن پکیج های NuGet از سورس کنترل Git
ابزار NuGet بسیار کار آمد و مفید است. یکی از مشکلات رایج هنگامی پیش می‌آید که پروژه را بهمراه بسته‌های نصب شده به سورس کنترل push می‌کنید. با این کار حجم زیادی از فایل‌ها را به مخزن سورس کنترل آپلود می‌کنید و هنگام clone کردن پروژه توسط هر شخصی، این اطلاعات باید دریافت شوند. بد‌تر از این هنگامی است که برخی از بسته‌ها از سورس حذف می‌شوند و باید به اعضای تیم پروژه اطلاع دهید که چه بسته‌هایی باید دریافت و نصب شوند.

برای رفع این موارد به NuGet Package Restore وارد شوید.


به ویژوال استودیو اجازه دهید بسته‌های NuGet را در صورت لزوم احیا کند
پیش از آنکه بتوانیم از قابلیت Package Restore استفاده کنیم باید آن را روی ماشین خود فعال کنیم. این کار روی هر ماشین باید انجام شود (per-machine requirement). بدین منظور به منوی Tools -> Options -> Package Manager بروید.

در دیالوگ باز شده تنظیمات مربوطه را مانند تصویر زیر بروز رسانی کنید.

حال که ماشین ما برای بازیابی خودکار بسته‌های NuGet پیکربندی شده است، باید این قابلیت را برای Solution مورد نظر هم فعال کنیم.


فعال سازی NuGet Package Restore برای پروژه‌ها

بدین منظور روی Solution کلیک راست کنید و گزینه Enable Package Restore را انتخاب نمایید.

این کار ممکن است چند ثانیه زمان ببرد. پس از آنکه ویژوال استودیو پردازش‌های لازم را انجام داد، می‌توانید ببینید که پوشه جدیدی در مسیر ریشه پروژه ایجاد شده است.

همانطور که می‌بینید فایلی با نام NuGet.exe در این پوشه قرار دارد که باید به سورس کنترل آپلود شود. هنگامیکه شخصی پروژه شما را از سورس کنترل دریافت کند و بخواهد پروژه را Build کند، بسته‌های مورد نیاز توسط این ابزار بصورت خودکار دریافت و نصب خواهند شد.

مرحله بعد حذف کردن تمام بسته‌های NuGet از سورس کنترل است. برای اینکار باید فایل gitignore. را ویرایش کنید. فرض بر این است که سورس کنترل شما Git است، اما قواعد ذکر شده برای دیگر فریم ورک‌ها نیز صادق است. تنها کاری که باید انجام دهید این است که به سورس کنترل خود بگویید چه چیز هایی را در بر گیرد و از چه چیزهایی صرفنظر کند.

ویرایش فایل gitignore. برای حذف بسته‌ها و شامل کردن NuGet.exe

یک پروژه معمولی ASP.NET MVC 5 که توسط قالب استاندارد VS 2013 ایجاد می‌شود شامل 161 فایل از بسته‌های مختلف می‌شود (در زمان تالیف این پست). این مقدار قابل توجهی است که حجم زیادی از اطلاعات غیر ضروری را به مخزن سورس کنترل اضافه می‌کند. با استفاده از نسخه پیش فرض فایل gitignore. (یا فایل‌های مشابه دیگر برای سورس کنترل‌های مختلف مثل TFS) تعداد فایل هایی که در کل به مخزن سورس کنترل ارسال می‌شوند بیش از 200 آیتم خواهد بود. قابل ذکر است که این تعداد فایل شامل فایل‌های اجرایی (binary) و متعلق به ویژوال استودیو نیست. به بیان دیگر نزدیک به 75% از فایل‌های یک پروژه معمولی ASP.NET MVC 5 که توسط VS 2013 ساخته می‌شود را بسته‌های NuGet تشکیل می‌دهد، که حالا می‌توانند بجای ارسال شدن به مخزن سورس کنترل، بصورت خودکار بازیابی و نصب شوند.

برای حذف این فایل‌ها از سورس کنترل، فایل gitignore. را ویرایش می‌کنیم. اگر از سورس کنترل‌های دیگری استفاده میکنید نام این فایل hgignore. یا tfsignore. یا غیره خواهد بود. محتوای فایل شما ممکن است با لیست زیر متفاوت باشد اما جای نگرانی نیست. تنها تغییرات اندکی بوجود خواهیم آورد و مابقی محتویات فایل مهم نیستند.

چشم پوشی از پوشه Packages

فایل gitignore. را باز کنید و برای نادیده گرفتن پوشه بسته‌های NuGet در سورس، خط زیر را به آن اضافه کنید.

packages*/

استثنایی برای در نظر گرفتن NuGet.exe ایجاد کنید
به احتمال زیاد فایل gitignore. شما از فایل هایی با فرمت .exe چشم پوشی می‌کند. برای اینکه بسته‌های NuGet بتوانند بصورت خودکار دریافت شوند باید استثنایی تعریف کنیم. فایل gitignore. خود را باز کنید و به دنبال خط زیر بگردید.
*.exe
سپس خط زیر را بعد از آن اضافه کنید. دقت داشته باشید که ترتیب قرارگیری این دستوارت مهم است.
*.exe
!NuGet.exe
دستورات بالا به Git می‌گوید که فایل‌های .exe را نادیده بگیرد؛ اما برای فایل NuGet.exe استثناء قائل شود.
انجام مرحله بالا انتخابی (optional) است. اگر کسی که پروژه را از مخزن سورس کنترل دریافت می‌کند قابلیت Package Restore را روی Solution فعال کند ابزار NuGet.exe دریافت می‌شود. اما با انجام این مراحل دیگر نیازی به این فعالسازی نخواهد بود، پس در کار اعضای تیم هم صرفه جویی کرده اید.

اطلاع رسانی به اعضای تیم و مشتریان بالقوه
دیگر نیاز نیست بسته‌های NuGet را به مخزن سورس کنترل ارسال کنیم. اما باید به مخاطبین خود اطلاع دهید تا پیکربندی‌های لازم برای استفاده از قابلیت Package Restore را انجام دهند (مثلا در فایل README.txt پروژه).
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 1

در این نوشتار که به صورت آموزش تصویری ارائه می‌‏شود؛ یک سرویس WCF در Visual Studio 2013 ایجاد می‌کنم، سپس روش استفاده از آن‏را در یک برنامه ویندوزی آموزش خواهم داد. در اینجا در نظرگرفته شده است که شما افزونه‎ی Resharper را روی ویژوال استودیوی خود نصب دارید. پس در صورتیکه هنوز به سراغ آن نرفته اید درنگ نکنید و واپسین نگارش آن را دانلود کنید.

در این پروژه‌ی ساده در نظر می‎گیریم که دو جدول یکی برای اخبار، شامل عنوان، متن خبر و تاریخ ثبت و دسته بندی و دیگری برای نگهداری دسته‎ها در پایگاه داده داریم و می‏خواهیم سرویس‏های مناسب با این دو جدول را بسازیم. با کد زیر، پایگاه داده‌‏ی dbTest و جدول‌های tblNews و tblCategory در SQL Server 2012 ساخته می‌شود:

USE [master]
GO
/****** Object:  Database [dbMyNews]    Script Date: 2014/01/14 09:46:04 ب.ظ ******/
CREATE DATABASE [dbMyNews]
 CONTAINMENT = NONE
 ON  PRIMARY 
( NAME = N'dbMyNews', FILENAME = N'D:\dbMyNews.mdf' , SIZE = 5120KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'dbMyNews_log', FILENAME = N'D:\dbMyNews_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO
USE [dbMyNews]
GO
/****** Object:  Table [dbo].[tblCategory]    Script Date: 2014/01/14 09:46:04 ب.ظ ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tblCategory](
[tblCategoryId] [int] IDENTITY(1,1) NOT NULL,
[CatName] [nvarchar](50) NOT NULL,
[IsDeleted] [bit] NOT NULL,
 CONSTRAINT [PK_tblCategory] PRIMARY KEY CLUSTERED 
(
[tblCategoryId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
/****** Object:  Table [dbo].[tblNews]    Script Date: 2014/01/14 09:46:04 ب.ظ ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tblNews](
[tblNewsId] [int] IDENTITY(1,1) NOT NULL,
[tblCategoryId] [int] NOT NULL,
[Title] [nvarchar](50) NOT NULL,
[Description] [nvarchar](max) NOT NULL,
[RegDate] [datetime] NOT NULL,
[IsDeleted] [bit] NULL,
 CONSTRAINT [PK_tblNews] PRIMARY KEY CLUSTERED 
(
[tblNewsId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO
ALTER TABLE [dbo].[tblNews]  WITH CHECK ADD  CONSTRAINT [FK_tblNews_tblCategory] FOREIGN KEY([tblCategoryId])
REFERENCES [dbo].[tblCategory] ([tblCategoryId])
GO
ALTER TABLE [dbo].[tblNews] CHECK CONSTRAINT [FK_tblNews_tblCategory]
GO
USE [master]
GO
ALTER DATABASE [dbMyNews] SET  READ_WRITE 
GO

اکنون Visual Studio 2013 را بازکنید سپس روی گزینه New Project کلیک کنید و برابر با نگاره‌ی زیر عمل کنید:

پروژه MyNewsWCFLibrary در راه حل MyNews ساخته می‌شود. این پروژه به صورت پیش‌گزیده دارای یک کلاس به نام Service و یک interface به نام IService است. هر دو را حذف کنید و سپس روی نام پروژه راست‌کلیک کرده، از منوی بازشده گزینه‌ی Add -> New Item را انتخاب کنید. سپس برابر با نگاره‌ی زیر عمل کنید:

در لایه‎‌ی Service Interface کلیه‎ی روال‌های مورد نیاز برای ارتباط با پایگاه داده را می‎سازیم. پیش از آن باید یک Model برای ارتباط با پایگاه داده ساخته باشیم. برای این کار از پنجره Add New Item و از زیرمجموعه Data، گزینه ADO.NET Entity Data Model را انتخاب کنید و به‌سان زیر پیش روید:

در گام پسین روی دکمه New Connection کلیک کنید و رشته‌ی اتصال به پایگاه داده‌ی dbMyNews را بسازید. سپس همانند تنظیمات نگاره‌ی زیر ادامه دهید:

در گام پسین گزینه‌‎ی Entity Framework 6.0 را برگزینید و روی دکمه‎ی Next کلیک کنید.

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

در پایان مدل ما همانند نگاره‌ی زیر خواهد بود.

در بخش پسین درباره‏ی شیوه‏‌ی دست‎کاری کلاس‎های Entity خواهم نوشت.

مطالب
افزودن یک DataType جدید برای نگه‌داری تاریخ خورشیدی - 2
پیش از هرچیز به شما پیش‌نهاد می‌کنم؛ بار دیگر کد سی‌شارپ درس نخست را در پروژه‌ی خود کپی کنید و سپس Publish را بزنید. پس از ارسال آن مطلب، تغییراتی در جهت بهینه‌سازی کد دادم که به نظرم بهتر است شما نیز در پروژه‌ی خود به کار برید.

چرا از این نوع داده استفاده کنیم؟
نخستین پرسشی که ممکن است برای شما پیش بیاید این است که چرا بهتر است از این نوع داده استفاده کنیم. برای پاسخ به این پرسش باید راه‌کارهای گذشته را بررسی کنیم. معمولاً طراحان پایگاه داده‌ها برای استفاده از تاریخ خورشیدی، زمان را به صورت میلادی ثبت می‌کنند؛ سپس با یک scalar-valued function زمان درج شده را به خورشیدی تبدیل می‌کنند. در این صورت می‌توان با یک تابع کوچک دیگر بخش مربوط به ساعت را نیز از همان ستون به دست آورد. در این صورت می‌توانیم از کلیه‌ی متدهای مربوط به DateTime در SQL از جمله افزایش و کاهش و تفاضل دو تاریخ بهره برد. برخی دیگر از طراحان، ستونی از نوع (char(10 در نظر می‌گیرند و تاریخ خورشیدی را به صورت ده‌کاراکتری در آن ذخیره می‌کنند. این روش هرچند نیاز به تبدیل به خورشیدی را ندارد ولی کلیه‌ی مزایایی که در استفاده از DateTime به آن‌ها دسترسی داریم از دست می‌دهیم. افزون بر این جهت نگه‌داری زمان باید یک فیلد دیگر از نوع کاراکتری و یا در نگارش‌های نوین‌تر از نوع time تعریف کنیم. برخی دیگر از هر دو را در کنار هم استفاده می‌کنند و در واقع جهت سرعت بالاتر نمایش و بررسی داده‌ها از طریق محیط SQL Server از فیلد کاراکتری تاریخ خورشیدی و برای مقایسه و بدست آوردن ساعت از فیلد نوع DateTime استفاده می‌کنند.

از نظر فضای اشغال‌شده نوع DataTime، هشت بایت، smalldatetime (در صورت استفاده) 4 بایت و فیلد 10 کاراکتری تاریخ 10 بایت فضا اشغال می‌کند در صورتی که نوع JalaliDate با درنظر گرفتن همه‌ی مزایای انواع داده‌ی استفاده‌شده برای تاریخ، فقط 8 بایت فضا اشغال می‌کند. با استفاده از این نوع به راحتی داده‌ی تاریخ را بر اساس تقویم ایرانی اعتبارسنجی می‌کنید و بخش‌های مختلف زمان از سال تا ثانیه را با یک متد به دست می‌آورید. می‌توانید به راحتی به تاریخ خود زمانی را بیفزایید یا بکاهید و در گزارش‌ها بدون نگرانی از تبدیل درست استفاده کنید. چون کدباز است می‌توانید با کمی حوصله امکانات دیگر مد نظر خود را به آن بیفزایید و از آن در SQL بهره ببرید.

چگونه این نوع داده را حذف کنم!؟
شما می‌توانید به سادگی نوع داده‌ی ایجادشده توسط CLR را در مسیر زیر بیابید و اقدام به حذف آن نمایید:

همان‌طور که مشاهده می‌شود؛ حتی نوع داده‌ی سیستمی hierarchyid که جهت ساختار سلسله‌مراتبی مانند چارت سازمانی یا درخت تجهیزات استفاده می‌شود؛ نیز یک نوع داده‌ی CLR است.

آیا راه دیگری نیز برای افزودن این نوع داده به SQL به جز Publish کردن وجود دارد؟
مانند بسیاری دیگر از گونه‌های پروژه، در اینجا نیز شما یک فایل DLL خواهید داشت. این فایل برپایه‌ی تنظیماتی که شما در قسمت Properties پروژه‌ی خود انجام می‌دهید ساخته می‌شود. پس از تغییر مسیر فایل DLL در دستور زیر توسط یک New Query از Database خود، آن را اجرا کنید:

CREATE ASSEMBLY JalaliDate
FROM 'F:\prgJalaliDate.dll' 
WITH PERMISSION_SET = SAFE;
هم‌چنین در صورت ویرایش‌های دوباره پروژه از دستور زیر استفاده کنید:
ALTER ASSEMBLY JalaliDate
FROM 'F:\prgJalaliDate.dll'
با استفاده از دستورهای زیر می‌توانید از چگونگی درج فایل‌های افزوده شده آگاه شوید:
select * from sys.assemblies
select * from sys.assembly_files
تا اینجا SQL Server، دی‌ال‌ال مربوط به پروژه را شناخته است. برای تعریف نوع داده از دستور زیر بهره ببرید:
CREATE TYPE dbo.JalaliDate 
EXTERNAL NAME JalaliDate.[JalaliDate];
این کار همانند استفاده از گزینه‌ی Publish در Visual Studio است.
هم‌چنین چنان‌چه در SQL Server 2012 از منوی راست‌کلیک پایگاه داده‌ها روی گزینه Tasks و سپس Generate Scripts را انتخاب کنیم، از مشاهده‌ی سند ساخته شده، درخواهیم یافت که حتی دستورهای مربوط به ساخت اسمبلی CLR با تبدیل فایل به کد در Scripts وجود دارد و با اجرای آن در سروری دیگر، انتقال می‌یابد.

GO
/****** Object:  SqlAssembly [prgJalaliDate]    Script Date: 2013/04/30 08:27:00 ب.ظ ******/
CREATE ASSEMBLY [prgJalaliDate]
FROM 0x4D5A90000300000004000000FFFF0000B8000000000000 ..... بقیه‌ی کدها حذف شده
WITH PERMISSION_SET = SAFE

GO
ALTER ASSEMBLY [prgJalaliDate]
ADD FILE FROM 0x4D6963726F736F667420432F432B2B204D534620372E30300D0A1A44530..... بقیه‌ی کدها حذف شده
AS N'prgJalaliDate.pdb'

GO
/****** Object:  UserDefinedType [dbo].[JalaliDate]    Script Date: 2013/04/30 08:27:00 ب.ظ ******/
CREATE TYPE [dbo].[JalaliDate]
EXTERNAL NAME [prgJalaliDate].[JalaliDate]

GO

دنباله دارد ...
مطالب
امکان مفهوم بخشیدن به رشته‌ها در NET 7.
رشته‌ها، یکی از عمومی‌ترین نوع‌های داده‌ها هستند؛ از آن‌ها در تعریف آدرس‌های اینترنتی، عبارات باقاعده و یا حتی زمان‌ها و تاریخ‌ها استفاده می‌کنیم. در دات نت 7 می‌توان با استفاده از ویژگی جدید StringSyntaxAttribute، به این نوع‌های مختلف اندکی معنا بخشید.


معرفی ویژگی جدید StringSyntax

با استفاده از ویژگی StringSyntax جدید می‌توان مقدار مورد انتظار از رشته‌ی درخواستی را معنادار کرد. برای مثال، Visual Studio سال‌هاست که راهنمایی را در حین تعریف عبارات باقاعده ظاهر می‌کند. اما این راهنما صرفا مختص به ویژوال استودیو است و تا پیش از این راهی وجود نداشت تا عنوان کنیم که برای مثال این رشته قرار است تنها یک عبارت باقاعده باشد. اکنون در دات نت 7 با معرفی ویژگی جدید StringSyntax می‌توان یک چنین intellisense ای را در سایر IDEها نیز شاهد بود.
برای نمونه مثال زیر را درنظر بگیرید:
using System.Diagnostics.CodeAnalysis;

namespace CS11Tests;

public class StringSyntaxAttributeTests
{
    public static void Test()
    {
        RegexTest("");
        DateTest("");
    }

    private static void RegexTest([StringSyntax(StringSyntaxAttribute.Regex)] string regex)
    {
    }

    private static void DateTest([StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string dateTime)
    {
    }
}
در اینجا با استفاده از ویژگی StringSyntax، دقیقا مشخص کرده‌ایم که هدف از تعریف پارامترهای رشته‌ای مدنظر چه چیزی بوده‌است. به این ترتیب، برای مثال در Rider، در حین استفاده از این متدها، به intellisense‌های زیر خواهیم رسید:

راهنمای ظاهر شده جهت تعریف ساده‌تر عبارات باقاعده:


و راهنمای ظاهر شده جهت تعریف ساده‌تر یک DateTime:



امکان استفاده از StringSyntax در دات نت‌های پیش از نگارش 7

هرچند StringSyntax در دات نت 7 تعریف شده‌است؛ اما اگر تعریف کلاس زیر را به همراه فضای نام دقیق آن به پروژه‌های قدیمی‌تر هم اضافه کنیم ... برای دات نت‌های پیش از نگارش 7 هم کار می‌کند:
#if !NET7_0_OR_GREATER

namespace System.Diagnostics.CodeAnalysis
{
  /// <summary>Specifies the syntax used in a string.</summary>
  [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
  public sealed class StringSyntaxAttribute : Attribute
  {
    /// <summary>The syntax identifier for strings containing composite formats for string formatting.</summary>
    public const string CompositeFormat = "CompositeFormat";
    /// <summary>The syntax identifier for strings containing date format specifiers.</summary>
    public const string DateOnlyFormat = "DateOnlyFormat";
    /// <summary>The syntax identifier for strings containing date and time format specifiers.</summary>
    public const string DateTimeFormat = "DateTimeFormat";
    /// <summary>The syntax identifier for strings containing <see cref="T:System.Enum" /> format specifiers.</summary>
    public const string EnumFormat = "EnumFormat";
    /// <summary>The syntax identifier for strings containing <see cref="T:System.Guid" /> format specifiers.</summary>
    public const string GuidFormat = "GuidFormat";
    /// <summary>The syntax identifier for strings containing JavaScript Object Notation (JSON).</summary>
    public const string Json = "Json";
    /// <summary>The syntax identifier for strings containing numeric format specifiers.</summary>
    public const string NumericFormat = "NumericFormat";
    /// <summary>The syntax identifier for strings containing regular expressions.</summary>
    public const string Regex = "Regex";
    /// <summary>The syntax identifier for strings containing time format specifiers.</summary>
    public const string TimeOnlyFormat = "TimeOnlyFormat";
    /// <summary>The syntax identifier for strings containing <see cref="T:System.TimeSpan" /> format specifiers.</summary>
    public const string TimeSpanFormat = "TimeSpanFormat";
    /// <summary>The syntax identifier for strings containing URIs.</summary>
    public const string Uri = "Uri";
    /// <summary>The syntax identifier for strings containing XML.</summary>
    public const string Xml = "Xml";

    /// <summary>Initializes the <see cref="T:System.Diagnostics.CodeAnalysis.StringSyntaxAttribute" /> with the identifier of the syntax used.</summary>
    /// <param name="syntax">The syntax identifier.</param>
    public StringSyntaxAttribute(string syntax)
    {
      this.Syntax = syntax;
      this.Arguments = Array.Empty<object>();
    }

    /// <summary>Initializes the <see cref="T:System.Diagnostics.CodeAnalysis.StringSyntaxAttribute" /> with the identifier of the syntax used.</summary>
    /// <param name="syntax">The syntax identifier.</param>
    /// <param name="arguments">Optional arguments associated with the specific syntax employed.</param>
    public StringSyntaxAttribute(string syntax, params object?[] arguments)
    {
      this.Syntax = syntax;
      this.Arguments = arguments;
    }

    /// <summary>Gets the identifier of the syntax used.</summary>
    public string Syntax { get; }

    /// <summary>Gets the optional arguments associated with the specific syntax employed.</summary>
    public object?[] Arguments { get; }
  }
}

#endif
مطالب
طرح پیشنهادی برای بارگذاری پویای ماژول‌های JS
آقای Domenic Denicola در نسخه‌های بعدی، طرح پیشنهادی را مطرح کرده است که مربوط به بارگذاری داینامیک ماژول‌های JS می‌باشد. البته کتابخانه‌ها و روش‌هایی در حال حاضر برای این کار وجود دارند. با هم مثال‌هایی از این قابلیت را بررسی میکنیم. 

در نسخه جدید Javascript قابلیتی برای import کردن ماژول‌ها وجود دارد؛ ولی این قابلیت کاملا استاتیک می‌باشد. کد زیر را مشاهده کنید:
import someModule from './dir/someModule.js';
خوب سوالی که مطرح می‌شود این است که چه نیازی به بارگذاری داینامیک ماژول‌ها داریم؟               
جواب این سوال خیلی مشخص است. در import معمولی و استاتیک JS، ما تمام ماژول‌های مورد نیاز در پروژه را فراخوانی میکنیم. اما شاید خیلی از این ماژول‌ها در طول اجرای پروژه مورد نیاز نباشند و بر حسب رفتارهای کاربر نیاز به این ماژول‌ها داشته باشیم. در این صورت هست که بارگذاری داینامیک ماژول‌ها مطرح می‌شود. این قابلیت در جاوا اسکریپت به صورت built-in وجود ندارد. ولی با کتابخانه‌هایی مانند RequireJS این قابلیت قابل استفاده هست. این Proposal توسط آقای Domenic Denicola مطرح شده است.                  
 کد زیر مثال ساده از این قابلیت می‌باشد:
import('./dir/someModule.js')
    .then(someModule => someModule.foo());
                   
 یا یک مثال عملیاتی؛ فرض کنید با کلیک بر روی دکمه‌ای می‌خواهیم یک Dialog را باز کنیم که منطق و قوائد مخصوص به خود را دارد و به صورت یک ماژول جداگانه نوشته شده‌است. کد زیر را مشاهده کنید:
 button.addEventListener('click', event => {
        import('./dialogBox.js')
        .then(dialogBox => {
            dialogBox.open();
        })
        .catch(error => {
            /* Error handling */
        })
    });
                 
 این قابلیت هم وجود دارد که دو ماژول را که در یک فایل نوشته شده‌اند نیز به صورت جداگانه استفاده کنید.
import('./myModule.js')
    .then(({export1, export2}) => {
        export1.run();
        export2.fire();
    });
             
 شما حتی می‌توانید چند ماژول را باهم بارگذاری کنید و بعد از پایان بارگذاری همه ماژول‌ها، یک عمل خاصی را انجام دهید. کد زیر را مشاهده کنید:
Promise.all([
        import('./module1.js'),
        import('./module2.js'),
        import('./module3.js'),
    ])
    .then(([module1, module2, module3]) => {
        // code 
    });
این موضوع را به کمک Promise با متد all انجام دادیم.                

 حتی شما می‌توانید با قابلیت async و await نیز کدهای تمیزتر و با قابلیت خوانایی بالاتری را بنویسید. مثال زیر را مشاهده کنید:
async function main() {
        const myModule = await import('./myModule.js');
    
        myModule.getInfo();

        const {export1, export2} = await import('./myModule.js');      
        
        export1.run();
        
        export2.fire()
    }
    main();
                    
 خوب خوشبختانه طرافداران NodeJS ماژول مربوط به این قابلیت را قبل از ارائه این قابلیت جدید در JS به صورت Packages در NPM فراهم کرده‌اند. لینک زیر را مشاهده کنید:
                       
 و Developer‌های که از Webpack در پروژه‌های خود استفاده میکنند می‌توانند از ماژول زیر استفاده کنند که توسط تیم Airbnb تهیه شده است:
                   
 و Developer‌هایی که از نسخه ۲ Webpack استفاده میکنند، می‌توانند بحث Code Splitting را در راهنمای زیر مشاهده کنند:
                      

البته آقای Jake Archibald کد جالبی را برای این قابلیت پیشنهاد داده‌است که ترکیبی از import استاتیک ES6 می‌باشد:

function importModule (url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
    script.type = "module";
    script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;

    script.onload = () => {
      resolve(window[tempGlobal]);
      delete window[tempGlobal];
      script.remove();
    };

    script.onerror = () => {
      reject(new Error("Failed to load module script with URL " + url));
      delete window[tempGlobal];
      script.remove();
    };

    document.documentElement.appendChild(script);
  });
}