مطالب
بهبود کارآیی استفاده از JSON در دات نت 6 با معرفی Source generators آن
دات نت 6 به همراه source generator‌های توکاری است که می‌توانند کار serialization و deserialization نوع JSON را با کارآیی بسیار بیشتری انجام دهند؛ با آزمایش‌هایی که این بهبود را در حد 40 درصد سریعتر نسبت به حالت متداول آن نمایش می‌دهند و ... این مساله بسیار مهم است. از این جهت که این روزها، JSON را در همه‌جا مشاهده می‌کنیم؛ در Web APIها، در تنظیمات برنامه‌ها، در ارسال پیام‌ها بین برنامه‌ها و غیره. بنابراین هرگونه بهبودی در زمینه‌ی کارآیی serialization و deserialization آن، تاثیر بسیار قابل ملاحظه‌ای را بر روی کارآیی کلی یک برنامه بجا خواهد گذاشت.


System.Text.Json source generator چیست؟

پا‌یه‌ی تمام اعمال serialization و deserialization در دات نت، استفاده از Reflection است که در زمینه‌ی ارائه‌ی برنامه‌هایی با کارآیی بالا و با مصرف حافظه‌ی پایین، بهینه عمل نمی‌کند. راه‌حل جایگزین استفاده از Reflection که در زمان اجرای برنامه رخ می‌دهد، به همراه دات نت 5 ارائه شد و source generators نام دارد. Source generators امکان تولید فایل‌های #C را در زمان کامپایل برنامه میسر می‌کنند که نسبت به راه‌حل Reflection که در زمان اجرای برنامه فعال می‌شود، کارآیی بسیار بیشتری را ارائه می‌کنند. برای مثال به همراه دات نت 6، علاوه بر روش پیش‌فرض مبتنی بر Reflection ارائه شده‌ی توسط System.Text.Json، راه حل جدید امکان استفاده‌ی از source generators توکار آن نیز پیش بینی شده‌است. کار اصلی آن، انجام تمام مراحلی است که پیشتر توسط Reflection در زمان اجرای برنامه صورت می‌گرفت، اینبار در زمان کامپایل برنامه و ارائه‌ی آن به صورت از پیش آماده شده و مهیا.
مزایای این روش شامل موارد زیر است:
- بالا رفتن سرعت برنامه
- کاهش زمان آغاز اولیه‌ی برنامه
- کاهش میزان حافظه‌ی مورد نیاز برنامه
- عدم نیاز به استفاده‌ی از System.Reflection و System.Reflection.Emit
- ارائه‌ی Trim-compatible serialization که سبب کاهش اندازه‌ی نهایی برنامه می‌شود. برای مثال در برنامه‌های Blazor می‌توان با فعالسازی Trimming، کدهای استفاده نشده را از فایل‌های بایناری نهایی حذف کرد. استفاده از source generators، با این روش سازگاری کاملی دارد.



مثالی از نحوه‌ی کار با JSON در دات نت 6، توسط source generators آن

فرض کنید قصد داریم اعمال serialization و deserialization از نوع JSON را بر روی نمونه‌های کلاس زیر انجام دهیم:
namespace Test
{
    internal class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}
اولین کاری که در این زمینه باید انجام شود، ایجاد یک کلاس خالی، با نامی دلخواه، اما مشتق شده‌ی از JsonSerializerContext است. در این حالت اخطارهایی را در IDE خود مبتنی بر نیاز به پیاده سازی تعدادی از متدهای این کلاس پایه دریافت می‌کنیم. اما ... ما قصد نداریم این متدها را پیاده سازی کنیم؛ Source generator قرار است اینکار را انجام دهد. به همین جهت این کلاس را partial تعریف کرده (تا source generator بتواند آن‌را در فایلی دیگر تکمیل کند) و همچنین آن‌را مزین به ویژگی JsonSerializable از نوع کلاسی که می‌خواهیم آن‌را serialize کنیم، خواهیم کرد تا سبب فعال شدن source generator بر روی این کلاس شویم:
using System.Text.Json.Serialization;

namespace Test
{
    [JsonSerializable(typeof(Person))]
    internal partial class MyJsonContext : JsonSerializerContext
    {
    }
}
و ... همین! کدهای این کلاس partial توسط source generator در زمان کامپایل برنامه به صورت خودکار تولید و تکمیل می‌شوند.
پس از آن فقط کافی است MyJsonContext را به عنوان پارامتر متدهای جدید Serialize و یا Deserialize، به صورت زیر ارسال کنیم تا از آن استفاده شود:
Person person = new() { FirstName = "Jane", LastName = "Doe" };
byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(person, MyJsonContext.Default.Person);
person = JsonSerializer.Deserialize(utf8Json, MyJsonContext.Default.Person);

متدهای جدید این API مبتنی بر source generators را در ادامه ملاحظه می‌کنید:
namespace System.Text.Json
{
    public static class JsonSerializer
    {
        public static object? Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerContext context) => ...;
        public static object? Deserialize(ReadOnlySpan<char> json, Type returnType, JsonSerializerContext context) => ...;
        public static object? Deserialize(string json, Type returnType, JsonSerializerContext context) => ...;
        public static object? Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerContext context) => ...;
        public static ValueTask<object?> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static ValueTask<TValue?> DeserializeAsync<TValue>(Stream utf8Json, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static TValue? Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
        public static TValue? Deserialize<TValue>(string json, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
        public static TValue? Deserialize<TValue>(ReadOnlySpan<char> json, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
        public static TValue? Deserialize<TValue>(ref Utf8JsonReader reader, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
        public static string Serialize(object? value, Type inputType, JsonSerializerContext context) => ...;
        public static void Serialize(Utf8JsonWriter writer, object? value, Type inputType, JsonSerializerContext context) { }
        public static Task SerializeAsync(Stream utf8Json, object? value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static byte[] SerializeToUtf8Bytes(object? value, Type inputType, JsonSerializerContext context) => ...;
        public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
        public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonTypeInfo<TValue> jsonTypeInfo) { }
        public static string Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
    }
}


روش معرفی تنظیمات Serializer به Source generator

برای معرفی تنظیمات serialization و deserialization، برای مثال تهیه‌ی خروجی‌های CamelCase، می‌توان از ویژگی JsonSourceGenerationOptions به صورت زیر استفاده کرد:
using System.Text.Json.Serialization;

namespace Test
{
    [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
    [JsonSerializable(typeof(Person))]
    internal partial class MyJsonContext : JsonSerializerContext
    {
    }
}
در این حالت مابقی کدها مانند قبل باقی خواهند ماند:
string json = JsonSerializer.Serialize(person, MyJsonContext.Default.Person);
Person person = JsonSerializer.Deserialize(json, MyJsonContext.Default.Person);


روش استفاده از JSON Source generators در برنامه‌های ASP.NET Core

در این نوع برنامه‌ها، JsonSerializerContext‌ها را می‌توان توسط متد AddContext به صورت زیر به تنظیمات JSON برنامه معرفی کرد:
services.AddControllers().AddJsonOptions(options => options.AddContext<MyJsonContext>());


روش استفاده از JSON Source generators در برنامه‌های Blazor

البته در اینجا بیشتر منظور امکان استفاده‌ی از آن‌ها توسط HttpClient است که به صورت زیر توسط متد GetFromJsonAsync واقع در فضای نام System.Net.Http.Json، میسر شده‌است:
[JsonSerializable(typeof(WeatherForecast[]))]
internal partial class MyJsonContext : JsonSerializerContext { }

@code {
    private WeatherForecast[] forecasts;

    private static JsonSerializerOptions Options = new(JsonSerializerDefaults.Web);
    private static MyJsonContext Context = new MyJsonContext(Options);

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync("sample-data/weather.json", Context.WeatherForecastArray);
    }
}
لیست کامل‌تر این API جدید به صورت زیر است:
namespace System.Net.Http.Json
{
    public static partial class HttpClientJsonExtensions
    {
        public static Task<object?> GetFromJsonAsync(this HttpClient client, string? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static Task<object?> GetFromJsonAsync(this HttpClient client, System.Uri? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, string? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
    }
    public static partial class HttpContentJsonExtensions
    {
        public static Task<object?> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
        public static Task<T?> ReadFromJsonAsync<T>(this HttpContent content, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
    }
}
مطالب
LocalDB FAQ
SQL Server Express LocalDB یا به صورت خلاصه LocalDB، یک بانک اطلاعاتی‌است که به صورت متصل به پروسه‌ی برنامه‌ی جاری اجرا می‌شود؛ برخلاف رویه‌ی متداول بانک‌های اطلاعاتی که به صورت یک سرویس مستقل اجرا می‌شوند. هدف آن، جایگزین کردن نگارش Express نیست و بیشتر حجم کم و سهولت توزیع آن مدنظر بوده‌است. برای مثال نگارش Express به صورت یک سرویس مجزا و مستقل بر روی سیستم نصب می‌شود؛ اما LocalDB به همراه و متصل به برنامه‌ی نوشته شده، اجرا می‌شود:


اگر به تصویر فوق دقت کنید، یک child process جدید به نام sqlservr.exe نیز به همراه برنامه‌ی آزمایشی ما به صورت خودکار اجرا شده‌است. این child process به همراه پارامترهای ذیل است (که توسط NET Framework. مقدار دهی می‌شوند و مدیریت نهایی آن خودکار است):
 "C:\Program Files\Microsoft SQL Server\120\LocalDB\Binn\\sqlservr.exe"   
-c -SMSSQL12E.LOCALDB
-sLOCALDB#5657074F
-d"C:\Users\Vahid\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\MSSQLLocalDB\master.mdf"
-l"C:\Users\Vahid\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\MSSQLLocalDB\mastlog.ldf"
-e"C:\Users\Vahid\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\MSSQLLocalDB\error.log"
بنابراین LocalDB برخلاف SQL Server CE، یک بانک اطلاعاتی in-process نیست و به صورت یک پروسه‌ی مجزا اجرا می‌شود. زمانیکه از SQL Server CE استفاده می‌شود، موتور این بانک اطلاعاتی چند فایل DLL بیشتر نیستند و نهایتا اجرای آن داخل پروسه‌ی برنامه‌ی ما و همانند اجرای سایر DLLهای متصل و مورد استفاده‌ی به آن است.
اما LocalDB یک بانک اطلاعاتی user-mode است و در پروفایل کاربر جاری سیستم اجرا می‌شود. این بانک اطلاعاتی یک بار بر روی سیستم نصب می‌شود و در هر برنامه‌ای که از آن استفاده می‌کنید، یک child process مجزای خاص خودش را (sqlservr.exe) اجرا خواهد کرد. اجرا و خاتمه‌ی این child processها نیز خودکار هستند و نیازی به دخالت مستقیم برنامه ندارند.
البته به نظر توسعه‌ی SQL Server CE متوقف شده‌است و دیگر پشتیبانی نمی‌شود. بنابراین گزینه‌ی ترجیح داده شده‌ی برای کارهایی با حجم‌های بانک اطلاعاتی زیر 10 گیگابایت ، می‌تواند LocalDB باشد. به علاوه اینکه قابلیت‌های T-SQL بیشتری را نیز پشتیبانی می‌کند و همچنین پشتیبانی منظمی نیز از آن وجود دارد. برای مثال پیش نمایش نگارش 2016 آن نیز موجود است.

در ادامه، یک سری پرسش و پاسخ متداول جهت کار با LocalDB را مرور خواهیم کرد.


محل دریافت آخرین نگارش مستقل آن کجاست؟

همانطور که عنوان شد، یکی از مهم‌ترین اهداف LocalDB، سهولت توزیع آن است و عدم نیاز به یک Admin سیستم، برای نصب و نگهداری آن. نگارش 2014 SP1 آن‌را از آدرس ذیل می‌توانید دریافت کنید:
https://www.microsoft.com/en-us/download/details.aspx?id=46697

در اینجا نسخه‌های متعددی وجود دارند. برای مثال اگر سیستم شما 64 بیتی است، تنها نیاز است ENU\x64\SqlLocalDB.msi را دریافت و نصب کنید:



پارامترهای نصب خاموش آن برای توزیع ساده‌ی برنامه کدامند؟

اگر می‌خواهید نصاب LocalDB را به همراه setup برنامه‌ی خود توزیع کنید، می‌توانید روش توزیع خاموش را با ذکر پارامترهای ذیل، مورد استفاده قرار دهید:
 msiexec /i SqlLocalDB.msi /qn IACCEPTSQLLOCALDBLICENSETERMS=YES


رشته‌ی اتصالی مخصوص آن کدام است؟

  <connectionStrings>
    <add name="Sample35Context"
        connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\test.mdf;Integrated Security=True;"
        providerName="System.Data.SqlClient" />
  </connectionStrings>
اگر نگارش 2014 SP1 آن‌را نصب کرده باشید، رشته‌ی اتصالی فوق، تمام آن‌چیزی است که برای شروع به کار با آن، نیاز دارید و دارای دو قسمت مهم است:
الف) ذکر وهله‌ی مدنظر
در اینجا وهله‌ی MSSQLLocalDB ذکر شده‌است؛ اما چه وهله‌هایی بر روی سیستم نصب هستند و چطور می‌توان وهله‌ی دیگری را ایجاد کرد؟ برای این منظور، به پارامترهای sqlservr.exe ابتدای بحث دقت کنید. اکثر آن‌ها به پوشه‌ی ذیل اشاره می‌کنند:
 C:\Users\your_user_name_here\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances
با یک چنین محتوایی

در این پوشه، وهله‌‌های موجود و نصب شده‌ی بر روی سیستم شما نمایش داده می‌شوند که یکی از آن‌ها را می‌توانید در رشته‌ی اتصالی فوق ذکر کنید.
به علاوه، این لیست را توسط برنامه‌ی کمکی SqlLocalDB.exe، به همراه پارامتر info یا i نیز می‌توانید دریافت و بررسی کنید:


برنامه‌ی کمکی SqlLocalDB.exe به همراه نصاب LocalDB، نصب می‌شود و توسط آن می‌توان نگارش‌های مختلف نصب شده‌را با پارامتر v و وهله‌ی مختلف موجود را با پارامتر i مشاهده کرد.
همچنین اگر می‌خواهید وهله‌ی جدیدی را بجز وهله‌ی پیش فرض MSSQLLocalDB ایجاد کنید، می‌توانید از پارامتر create آن به نحو ذیل استفاده نمائید:
For LocalDB SQL EXPRESS 2014
 "C:\Program Files\Microsoft SQL Server\120\Tools\Binn\SqlLocalDB.exe" create "v12.0" 12.0 -s

For LocalDB SQL Express 2012
 "C:\Program Files\Microsoft SQL Server\110\Tools\Binn\SqlLocalDB.exe" create "v11.0" 11.0 -s

ب) ذکر DataDirectory
در رشته‌ی اتصالی فوق، پارامتر DataDirectory نیز ذکر شده‌است تا بتوان مسیر بانک اطلاعاتی را به صورت نسبی و بدون ذکر عبارت دقیق آن که ممکن است در سیستم‌های دیگر متفاوت باشد، پردازش کرد. این پارامتر در برنامه‌های وب به پوشه‌ی استاندارد app_data اشاره می‌کند و نیازی به تنظیم اضافه‌تری ندارد. اما در برنامه‌های دسکتاپ باید به نحو ذیل به صورت دستی، در آغاز برنامه مقدار دهی شود:
 AppDomain.CurrentDomain.SetData("DataDirectory", AppDomain.CurrentDomain.BaseDirectory);
به این ترتیب DataDirectory به محل قرارگیری فایل exe برنامه اشاره می‌کند. بدیهی است در اینجا هر پوشه‌ی دیگری را نیز می‌توانید ذکر کنید:
 AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "db"));
برای نمونه تنظیم فوق به زیر پوشه‌ی db، در کنار فایل exe برنامه اشاره می‌کند.


محل نصب بانک‌های اطلاعاتی پیش فرض آن کدام است؟

ذکر AttachDbFilename در رشته‌ی اتصالی فوق، اختیاری است. در صورت عدم ذکر آن، بانک اطلاعاتی ایجاد شده را در یکی از مسیرهای ذیل می‌توانید جستجو کنید:
 C:\Users\USERNAME\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances
C:\Users\USERNAME\AppData\Local\Microsoft\VisualStudio\SSDT
همچنین در این محل‌ها فایل‌های log متنی خطاهای این بانک اطلاعاتی را نیز می‌توان مشاهده کرد. بنابراین اگر به خطای خاصی برخوردید، بهترین کار، بررسی این فایل‌‌ها است.


آیا می‌توان فایل‌های mdf و ldf آن‌را به نگارش کامل SQL Server متصل (attach) کرد؟

بله. اما باید دقت داشته باشید که SQL Server به محض اتصال یک بانک اطلاعاتی با نگارش پایین‌تر به آن، ابتدا شماره نگارش آن‌‌را به روز می‌کند. یعنی دیگر نخواهید توانست این بانک اطلاعاتی را با نگارش پایین‌تر LocalDB باز کنید و یک چنین پیام خطایی را دریافت خواهید کرد:
 The database xyz cannot be opened because it is version 706. This server supports version 663 and earlier. A downgrade path is not supported.


چگونه محتوای بانک‌های اطلاعاتی LocalDB را با VS.NET مشاهده کنیم؟

از منوی view گزینه‌ی server explorer را انتخاب کنید. بر روی data connections کلیک راست کرده و گزینه‌ی Add connection را انتخاب کنید.


در صفحه‌ی باز شده، گزینه‌ی Microsoft SQL server  را انتخاب کنید. در صفحه‌ی بعد، ذکر server name مطابق data source رشته‌ی اتصالی بحث شده و سپس انتخاب گزینه‌ی attach a database file کفایت می‌کند:


پس از کلیک بر روی ok، امکان کار با اجزای این بانک اطلاعاتی را خواهید داشت:



چگونه از LocalDB با EF استفاده کنیم؟

EF 6.x به صورت پیش فرض از بانک اطلاعاتی LocalDB استفاده می‌کند و تنها داشتن یک چنین تنظیمی در فایل کانفیگ برنامه، برای کار با آن کافی است:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="Sample35Context"
        connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\test.mdf;Integrated Security=True;"
        providerName="System.Data.SqlClient" />
  </connectionStrings>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
</configuration>
یک قسمت آن ذکر رشته‌ی اتصالی است که در مورد آن بحث شد و قسمت دوم آن، ذکر connection factory مخصوص localdb است که به صورت فوق می‌باشد. تنظیم دیگری برای کار با LocalDB و EF 6.x نیازی نیست.
البته باید دقت داشت که اسمبلی EntityFramework.SqlServer نیز به صورت خودکار به همراه بسته‌ی نیوگت EF 6.x به برنامه اضافه می‌شود که استفاده‌ی از connection factory ذکر شده را میسر می‌کند.


استفاده‌ی از LocalDB به همراه برنامه‌های وب چگونه است؟

سه نکته را باید در حین استفاده‌ی از LocalDB، در برنامه‌های وب اجرا شده‌ی بر روی IIS مدنظر داشت:
الف) LocalDB یک بانک اطلاعاتی user-mode است و child process آن تحت مجوز اکانت تنظیم شده‌ی برای آن کار می‌کند.
ب) همانطور که عنوان شد، در رشته‌ی اتصالی ذکر شده، پارامتر DataDirectory به پوشه‌ی استاندارد app_data اشاره می‌کند که فایل‌های قرار گرفته‌ی در آن توسط IIS محافظت می‌شوند و از طریق وب قابل دسترسی و دانلود نیستند.
ج) child process مربوط به LocalDB، نیاز به دسترسی write، برای کار با فایل‌های mdf و ldf خود دارد.

برای مورد الف نیاز است تا به تنظیمات application pool برنامه مراجعه کرده و سپس بر روی آن کلیک راست کرد و گزینه‌ی advanced settings را انتخاب نمود. در اینجا گزینه‌ی load user profile باید true باشد:


تنظیم load user profile ضروری است اما کافی نیست. پس از آن باید setProfileEnvironment را نیز به true تنظیم کرد. تنظیم این مورد در کنسول مدیریتی IIS به صورت زیر است.
ابتدا ریشه‌ی اصلی سرور را انتخاب کنید و سپس به configuration editor آن وارد شوید:


در ادامه از دارپ داون آن، گزینه‌ی system.applicationHost و زیر شاخه‌ی applicationPools آن‌را انتخاب کنید:


در اینجا application pool defaults و سپس در آن processModel را نیز باز کنید:


اکنون امکان ویرایش setProfileEnvironment را به true خواهید داشت:


پس از این تنظیم، ابتدا بر روی دکمه‌ی apply سمت راست صفحه کلیک کرده و سپس نیاز است یکبار IIS را نیز ریست کنید تا تنظیمات اعمال شوند.


در ادامه برای تنظیم دسترسی write (موارد ب و ج)، ابتدا بر روی پوشه‌ی app_data برنامه، کلیک راست کرده و برگه‌ی security آن‌را باز کنید. سپس بر روی دکمه‌ی edit کلیک کرده و در صفحه‌ی باز شده بر روی دکمه‌ی add کلیک کنید تا بتوان به کاربر application pool برنامه دسترسی write داد:


در اینجا iis apppool\TestLocalDB را وارد کرده و بر روی دکمه‌ی check name کلیک کنید.

iis apppool آن که مشخص است. عبارت TestLocalDB نام application pool ایی است که برای برنامه‌ی وب خود ایجاد کرده‌ایم (بهتر است به ازای هر برنامه‌ی وب، یک application pool مجزا تعریف شود).


در اینجا بر روی OK کلیک کرده و به این کاربر جدید اضافه شده، دسترسی full control را بدهید تا برنامه و یوزر آن بتواند فایل‌های mdf و ldf را ایجاد کرده و به روز رسانی کنند.

پس از تنظیم load user profile و همچنین set profile environment و دادن دسترسی write به کاربر application pool برنامه، اکنون child process مربوط به local db را می‌توان ذیل پروسه‌ی IIS مشاهده کرد و برنامه قادر به استفاده‌ی از LocalDB خواهد بود:

مطالب
Lambda Syntax و کارآیی
در این مطلب می‌خواهیم کارآیی event handlers پیاده سازی شده با روش‌های متفاوتی را مورد بررسی قراردهیم.
به مثال زیر توجه کنید:
class EventSource : System.Progress<int>
{
    public async System.Threading.Tasks.Task<int> PerformExpensiveCalculation()
    {
        var sum = 0;
        for (var i = 0; i < 100; i++)
        {
            await System.Threading.Tasks.Task.Delay(100);
            sum += i;
            this.OnReport(sum);
        }
        return sum;
    }
}

static class Program
{
    static void Main(string[] args)
    {
        var source = new EventSource();
        System.EventHandler<int> handler = (_, progress) => System.Console.WriteLine(progress);
        source.ProgressChanged += handler;
        System.Console.WriteLine(source.PerformExpensiveCalculation().Result);
        source.ProgressChanged -= handler;

        source.ProgressChanged += ProgressChangedMethod;
        System.Console.WriteLine(source.PerformExpensiveCalculation().Result);
        source.ProgressChanged -= ProgressChangedMethod;
    }

    private static void ProgressChangedMethod( object sender, int e )
    {
        System.Console.WriteLine(e);
    }
}
در مثال بالا دو نسخه‌ی مختلف از event handler را با دو روش، (روش اول) با استفاده از Lambda syntax و (روش دوم) با استفاده از یک متد، به صورت جدا تعریف شده، پیاده سازی کرده‌ایم.
خوب؛ برای اندازه گیری کارآیی این دو روش باید کمی فکر کنیم که چه چیزی کارآیی این دو روش را تغییر می‌دهد؟
آیا پردازش event با اضافه کردن و حذف کردن event handler؟ و یا پردازش درون event باعث تغییر در کارآیی می‌شود؟
این، سوال مهمی در تست کارآیی این دو روش مختلف است. اگر پردازش درون event باعث ایجاد تفاوت کارآیی می‌شود، با استفاده از این برنامه می‌توان آن را اندازه گیری کرد. با این حال اگر تفاوت کارآیی با اضافه کردن و حذف کردن event handler اتفاق می‌افتد، با این برنامه بعید است بتوان این روش را تست کرد چرا که فقط یکبار این عمل انجام می‌شود.
قبل از شروع به اندازه گیری کارآیی این دو روش‌، اجازه بدهید ابتدا به کد IL آن‌ها نگاهی کنیم. (روش اول با استفاده از Lambda syntax)
IL_0007:  ldsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__0_0'
IL_000c:  dup
IL_000d:  brtrue.s   IL_0026
IL_000f:  pop
IL_0010:  ldsfld     class LambdaPerformance.Program/'<>c' LambdaPerformance.Program/'<>c'::'<>9'
IL_0015:  ldftn      instance void LambdaPerformance.Program/'<>c'::'<Main>b__0_0'(object, int32)
IL_001b:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
IL_0020:  dup
IL_0021:  stsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__0_0'
IL_0026:  stloc.1
IL_0027:  ldloc.0
IL_0028:  ldloc.1
IL_0029:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::add_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
IL_002e:  nop
IL_002f:  ldloc.0
IL_0030:  callvirt   instance class [mscorlib]System.Threading.Tasks.Task`1<int32> LambdaPerformance.EventSource::PerformExpensiveCalculation()
IL_0035:  callvirt   instance !0 class [mscorlib]System.Threading.Tasks.Task`1<int32>::get_Result()
IL_003a:  call       void [mscorlib]System.Console::WriteLine(int32)
IL_003f:  nop
IL_0040:  ldloc.0
IL_0041:  ldloc.1
IL_0042:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::remove_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
در بالا 5 دستورالعمل برای اضافه کردن event handler وجود دارد (از IL_0010 تا IL_0029) و یک دستور برای حذف handler وجود دارد (IL_0042).
قبل از شروع مقایسه، کد IL روش دوم را نیز بررسی می‌کنیم:
IL_004a:  ldftn      void LambdaPerformance.Program::ProgressChangedMethod(object, int32)
IL_0050:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
IL_0055:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::add_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
IL_005a:  nop
IL_005b:  ldloc.0
IL_005c:  callvirt   instance class [mscorlib]System.Threading.Tasks.Task`1<int32> LambdaPerformance.EventSource::PerformExpensiveCalculation()
IL_0061:  callvirt   instance !0 class [mscorlib]System.Threading.Tasks.Task`1<int32>::get_Result()
IL_0066:  call       void [mscorlib]System.Console::WriteLine(int32)
IL_006b:  nop
IL_006c:  ldloc.0
IL_006d:  ldnull
IL_006e:  ldftn      void LambdaPerformance.Program::ProgressChangedMethod(object, int32)
IL_0074:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
IL_0079:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::remove_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
همانطور که مشاهده می‌کنید در روش دوم برای اضافه کردن event handler تنها 3 خط وجود دارند (IL_004a تا IL_0055) و برای حذف کردن آن نیز 3 خط وجود دارند (IL_006e تا IL_0079).

برای اندازه گیری دقیق، برنامه‌ی بالا را کمی تغییر می‌دهیم. ما میزان اضافه و حذف شدن event handler را می‌خواهیم اندازه‌گیری کنیم و کاری به زمان اجرای یک عملیات نداریم. بنابراین فراخوانی ()PerformExpensiveCalculation را comment کرده و به صورت خیلی ساده فقط handler را اضافه و حذف می‌کنیم.
static class Program
{
    static void Main(string[] args)
    {
        for (var repeats = 10; repeats <= 1000000; repeats *= 10)
        {
            VersionOne(repeats);
            VersionTwo(repeats);
        }
    }

    private static void VersionOne(int repeats)
    {
        var timer = new System.Diagnostics.Stopwatch();
        timer.Start();

        var source = new EventSource();
        for (var i = 0; i < repeats; i++)
        {
            System.EventHandler<int> handler = (_, progress) => System.Console.WriteLine(progress);
            source.ProgressChanged += handler;
            // Console.WriteLine(source.PerformExpensiveCalculation().Result);
            source.ProgressChanged -= handler;
        }

        timer.Stop();

        System.Console.WriteLine($"Version one: {repeats} add/remove takes {timer.ElapsedMilliseconds}ms");
    }

    private static void VersionTwo(int repeats)
    {
        var timer = new System.Diagnostics.Stopwatch();
        timer.Start();

        var source = new EventSource();
        for (var i = 0; i < repeats; i++)
        {
            source.ProgressChanged += ProgressChangedMethod;
            // Console.WriteLine(source.PerformExpensiveCalculation().Result);
            source.ProgressChanged -= ProgressChangedMethod;
        }

        timer.Stop();

        System.Console.WriteLine($"Version two: {repeats} add/remove takes {timer.ElapsedMilliseconds}ms");
    }

    private static void ProgressChangedMethod(object sender, int e)
    {
        System.Console.WriteLine(e);
    }
}
و چنین خروجی را تولید می‌کند (البته نسبت به سرعت CPU این زمان‌ها متفاوت خواهد بود)
Version one: 10 add/remove takes 0ms
Version two: 10 add/remove takes 0ms
Version one: 100 add/remove takes 0ms
Version two: 100 add/remove takes 0ms
Version one: 1000 add/remove takes 0ms
Version two: 1000 add/remove takes 0ms
Version one: 10000 add/remove takes 0ms
Version two: 10000 add/remove takes 1ms
Version one: 100000 add/remove takes 8ms
Version two: 100000 add/remove takes 13ms
Version one: 1000000 add/remove takes 93ms
Version two: 1000000 add/remove takes 121ms
خوب؛ اگر در یک اجرای برنامه، شما یک میلیون بار event handler را اضافه و حذف کنید، 28ms می‌توانید صرفه جویی کنید (در روش اول).

توجه:
اگر در برنامه‌ی شما یک میلیون بار event handler اضافه و حذف می‌شوند، نیاز به بازنگری مجدد در طراحی کلی برنامه تان دارد.

یک اشتباه بزرگ

با ایجاد یک تغییر در روش اول (Lambda syntax)، ممکن است تاثیر بسیار زیادی را در عملکرد برنامه مشاهده کنید:
private static void VersionOne(int repeats)
{
    var timer = new System.Diagnostics.Stopwatch();
    timer.Start();

    var source = new EventSource();
    for (var i = 0; i < repeats; i++)
    {
        // System.EventHandler<int> handler = (_, progress) => System.Console.WriteLine(progress);
        source.ProgressChanged += (_, progress) => System.Console.WriteLine(progress);
        // Console.WriteLine(source.PerformExpensiveCalculation().Result);
        source.ProgressChanged -= (_, progress) => System.Console.WriteLine(progress);
    }

    timer.Stop();

    System.Console.WriteLine($"Version one: {repeats} add/remove takes {timer.ElapsedMilliseconds}ms");
}
به جای تعریف یک متغیر محلی برای عبارت Lambda، دستور اضافه و حذف کردن event handler را به صورت inline استفاده کردیم. خروجی این روش به صورت زیر می‌شود:
Version one: 10 add/remove takes 0ms
Version two: 10 add/remove takes 0ms
Version one: 100 add/remove takes 1ms
Version two: 100 add/remove takes 0ms
Version one: 1000 add/remove takes 102ms
Version two: 1000 add/remove takes 0ms
Version one: 10000 add/remove takes 10509ms
Version two: 10000 add/remove takes 1ms
Version one: 100000 add/remove takes 1039014ms
Version two: 100000 add/remove takes 11ms
همانطور که مشاهده می‌کنید، روش اول خیلی خیلی آهسته است. توجه کنید من بعد از یکصد هزار بار اضافه و حذف کردن handler، به دلیل طولانی شدن، عملیات را قطع کردم. خب دلیل این همه کندی چیست؟ بیایید نگاهی به کد IL درون حلقه‌ی روش اول بیاندازیم.
  IL_0018:  nop
  IL_0019:  ldloc.1
  IL_001a:  ldsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__1_0'
  IL_001f:  dup
  IL_0020:  brtrue.s   IL_0039
  IL_0022:  pop
  IL_0023:  ldsfld     class LambdaPerformance.Program/'<>c' LambdaPerformance.Program/'<>c'::'<>9'
  IL_0028:  ldftn      instance void LambdaPerformance.Program/'<>c'::'<VersionOne>b__1_0'(object, int32)
  IL_002e:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
  IL_0033:  dup
  IL_0034:  stsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__1_0'
  IL_0039:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::add_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
  IL_003e:  nop
  IL_003f:  ldloc.1
  IL_0040:  ldsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__1_1'
  IL_0045:  dup
  IL_0046:  brtrue.s   IL_005f
  IL_0048:  pop
  IL_0049:  ldsfld     class LambdaPerformance.Program/'<>c' LambdaPerformance.Program/'<>c'::'<>9'
  IL_004e:  ldftn      instance void LambdaPerformance.Program/'<>c'::'<VersionOne>b__1_1'(object, int32)
  IL_0054:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
  IL_0059:  dup
  IL_005a:  stsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__1_1'
  IL_005f:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::remove_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
  IL_0064:  nop
  IL_0065:  nop
  IL_0066:  ldloc.2
  IL_0067:  stloc.3
  IL_0068:  ldloc.3
  IL_0069:  ldc.i4.1
  IL_006a:  add
  IL_006b:  stloc.2
  IL_006c:  ldloc.2
  IL_006d:  ldarg.0
  IL_006e:  clt
  IL_0070:  stloc.s    V_4
  IL_0072:  ldloc.s    V_4
  IL_0074:  brtrue.s   IL_0018
به خطهای ( IL_0028 و IL_0034 و IL_004e و IL_005a ) در کد بالا دقت کنید. توجه داشته باشید که event handler اضافه شده با event handler حذف شده، با هم متفاوت هستند. حذف کردن event handler ای که به جایی متصل نیست باعث ایجاد خطا نمی‌شود ولی کاری هم انجام نمی‌دهد. بنابراین اتفاقی که در روش اول درون حلقه می‌افتد این است که بیش از یک میلیون بار event handler به delegate اضافه می‌شود. همه‌ی آنها یکسان هستند؛ اما همچنان CPU و حافظه مصرف می‌کنند.

ممکن است شما به این نتیجه رسیده باشید که استفاده از Lambda syntax برای اضافه و حذف کردن event handler آهسته‌تر از، استفاده از متد جدا است، این یک اشتباه بزرگ است. در صورتی که شما اضافه و حذف کردن event handler را با استفاده از Lambda syntax به شکل صحیح انجام ندهید، به سرعت، در معیارهای کارآیی خود را نشان می‌دهد.

دانلود برنامه بالا
مطالب
Functional Programming - قسمت چهارم - برخورد با Exception ها
چنانچه قسمت‌های قبلی سری آموزش برنامه نویسی تابعی Functional Programming را مطالعه نکرده‌اید، پیشنهاد میکنم قبلا آن‌ها را  (+  و  +  و  +) قبل از شروع بخوانید. در این قسمت قرار است تاثیر استثناءها (exception) را بر روی کدها بررسی کرده و راهکاری را از جنس functional برایش ارائه کنیم. 



Exception و خوانایی کد

تکه کد زیر را در نظر بگیرید: یک Action معمولی در Asp.Net MVC که یک نام را دریافت کرده و یک کارمندرا ایجاد میکند:

public ActionResult CreateEmployee(string name) { 
    try { 
        ValidateName(name);
        // ادامه کد‌ها return View("با موفقیت ثبت شد");
        }
    catch (ValidationException ex) 
    { 
        return View("خطا", ex.Message);
    }
}

private void ValidateName(string name) { 
    if (string.IsNullOrWhiteSpace(name)) 
        throw new ValidationException("نام نمی‌تواند خالی باشد");

    if (name.Length > 100) 
        throw new ValidationException("نام نمی‌تواند طولانی باشد");
}

در این قطعه کد، در متد ValidateName، در صورت معتبر نبودن ورودی، یک Exception رخ میدهد و بلاک کد try/catch، این exception را دریافت کرده و خطای مناسبی را به کاربر نشان خواهد داد. تا اینجا ظاهرا همه چیز مرتب است و مشکلی ندارد! احتمالا کد‌های مشابه به این کد را زیاد دیده‌اید. در اینجا متد ValidateName، صادق نیست. در قسمت اول، در مورد Honesty صحبت کردیم. به عبارت ساده‌تر شما از امضای این متد نمی‌توانید به نوع خروجی و کاری که قرار است انجام دهد، پی ببرید. در واقع شما همیشه باید پیاده سازی متد را گوشه‌ای، در ذهن خود داشته باشید و برای اطمینان از کاری که متد انجام میدهد، همیشه باید به بدنه‌ی متد برگردیم. اگر به‌خاطر داشته باشید، توابع برنامه نویسی را به توابع ریاضی تشبیه کردیم. پس میتوانیم بگوییم: 

به عبارت دیگر وقتی از exception‌ها برای کنترل flow برنامه استفاده میکنید، مشابه کاری را انجام می‌دهید که دستور GOTO انجام می‌داد. این دستور در روش‌های قبل از برنامه نویسی ساخت یافته وجود داشت و توسط یک دانشمند هلندی به نام آقای دیکسترا حذف شد. وقتی از دستور GOTO یا JUMP استفاده میکنیم، فهمیدن flow برنامه پیچیدگی‌های زیادی را خواهد داشت. چراکه فراخوانی قطعه‌های کد و متد‌ها، وابستگی شدیدی خواهند داشت و البته میتوان گفت استفاده از exception‌ها برای کنترل جریان برنامه، می‌توانند از GOTO هم بد‌تر باشند؛ چرا که exception میتواند از لایه‌های مختلف کد نیز عبور کند.

امیدوارم تا اینجا به یک عقیده‌ی مشترک رسیده باشیم. خوب راهکار چیست؟ تصور کنید که تکه کد بالا را به صورت زیر تبدیل کنیم: 

public ActionResult CreateEmployee(string name) { 
    string error = ValidateName(name);

 if (error != string.Empty) 
        return View("خطا", error);
    // ادامه کد‌ها return View("با موفقیت ثبت شد");
}

private string ValidateName(string name) { 

    if (string.IsNullOrWhiteSpace(name)) 
        return "نام نمی‌تواند خالی باشد";

    if (name.Length > 100) 
        return "طول نام نمی‌تواند بیشتر از 100 کاراکتر باشد";

    return string.Empty;
}

با refactor ای که انجام دادیم، متد ValidateName را به یک تابع ریاضی تبدیل کردیم. به این معنا که هر آنچه را که از امضای متد، مشخص است، انجام می‌دهد و در این حالت چیزی مخفی نیست. توجه داشته باشید که این راهکار نهایی ما نیست و لطفا مقاله را تا انتها بخوانید!  



موارد استفاده Exception

با همه‌ی بدی‌هایی که از Exception‌ها گفتیم، با این حساب پس چه زمانی از آن استفاده کنیم؟

  1. Exception‌ها واقعا برای موارد استثنائی هستند.
  2. Exception‌ها برای شرایطی هستند که به معنای واقعی یک باگ باشند.
  3. منتظر رخ دادن Exception نباشیم! 

در توضیح مورد سوم، در اعتبار سنجی داده‌های کاربر (Validation) انتظار داده‌ی نادرستی را می‌توان داشت، پس نمی‌توانیم آن را یک حالت استثنایی بدانیم. معماری زیر را در نظر بگیرید


دیتایی که به API ما ارسال خواهد شد، همیشه شامل عملیات Filter یا به عبارتی Validation است و از آنجایی که می‌توان انتظار استفاده‌ی نادرست یا دیتای نادرست را داشت، نمیتوانیم این را حالتی از استثنائات در نظر بگیریم؛ ولی بر خلاف آن، وقتی در دامین پروژه و ارتباط بین دامین‌های مختلف، دیتایی رد و بدل می‌شود که معتبر نیست، میتوانیم آن را جزء استثناء‌ها در نظر بگیریم. به مثال زیر دقت کنید:

public ActionResult UpdateEmployee(int employeeId, string name) { 
    string error = ValidateName(name);
    
    if (error != string.Empty) 
        return View("Error", error);
    
    Employee employee = GetEmployee(employeeId); 
    employee.UpdateName(name);
}

public class Employee { 

    public void UpdateName(string name){

        if (name == null) 
            throw new ArgumentNullException();
        
        // ادامه کد‌ها }
}

در قطعه کد بالا تصور این است که کلاس Employee و متد UpdateName خارج از دامین می‌باشند. همانطورکه مشاهده میکنید، ما در action controller، از خالی نبودن نام اطمینان حاصل کردیم و سپس آن را به متد UpdateName ارجاع دادیم. ولی اگه به بدنه‌ی متد UpdateName دقت کنید، می‌بینید که مجددا از خالی نبودن نام اطمینان حاصل کرده‌ایم و در صورت خالی بودن، یک Exception را صادر میکنیم! به این مدل چک کردن‌ها در دامین‌های مختلف، معمولا guard clause گفته می‌شود و یک نوع قرارداد بین برنامه نویس هاست. اگر طبق تعریفی که بالاتر ارائه کردیم هم چک کنیم، میتوانیم حدس بزنیم که خالی بودن نام، نشان یک باگ در نرم افزار است! 



مفهوم fail fast

تا اینجا متوجه شدیم که از exception‌ها باید در شرایط استثنائی استفاده کنیم. خوب با توجه به این مساله، چه طور میتوانیم آن‌ها را Handle کنیم؟ این سؤال ما را به مفهومی به نام fail fast می‌رساند. این مفهوم به ما میگوید:

  • کار جاری را به محض یک اتفاق استثنائی باید متوقف کنیم.
  • رعایت این نکته در نهایت ما را به یک نرم افزار پایدار خواهد رساند.


برای درک هر چه بهتر این موضوع، بیایید به عکس این حالت نگاه کنیم؛ اصطلاحا Fail Silently.

متد زیر را ببینید: 

public void ProcessItems(List<Item> items) { 

    foreach (Item item in items) { 
        try { 
            Process(item);
 } 
        catch (Exception ex) 
        { 
            Logger.Log(ex);
 }
 }
 }

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

به صور خلاصه مهم‌ترین مزیت Fail Fast را میتوانیم به صورت زیر خلاصه کنیم:

  • مسیر رسیدن به خطا‌ها سر راست‌تر می‌شود.
  • نرم افزار به پایداری مناسبی خواهد رسید.
  • از اعتبار دیتای ذخیره شده اطمینان خواهیم داشت.


کجا exception‌ها را به دام بیندازیم؟

در یکی از حالت‌های زیر:

  • لاگ کردن
  • متوقف کردن عملیات
  • هیچ گاه در بلاک catch هیچ منطقی را پیاده نکنید.


حالت دیگر در استفاه از کتابخانه‌های دیگران (3rd parties) است. به طور مثال در استفاده از EF ممکن است به دلیل عدم برقراری ارتباط با دیتابیس، خطایی را دریافت کنید. در این حالت با توجه به نکات فوق، با این استثنائات برخورد کنید:

  • جلوی این نوع استثنائات را در پایین‌ترین حد ممکن در کد خود بگیرید.
  • Exception هایی را catch کنید که میدانید در حالت استثناء، چه کاری را می‌توانید انجام دهید.


این به این معنی میباشد که به صورت کلی همه نوع Exception ای را به صورت کلی نگیرید و نوع Exception اختصاصی را در بلاک catch قرار دهید. الان که قرار شد در بعضی از حالت‌ها جلوی استثنائات را بگیریم، خوب است ببینیم چطور باید اینکار را انجام بدیم.

قطعه کد زیر را در نظر بگیرید:

public void CreateCustomer(string name) { 
    Customer customer = new Customer(name); 
    bool result = SaveCustomer(customer);
    if (!result) { 
        MessageBox.Show("Error connecting to the database. Please try again later.");
    }
}

private bool SaveCustomer(Customer customer) { 
    try { 
        using (MyContext context = new MyContext()) { 
            context.Customers.Add(customer);
         context.SaveChanges();
        } 
        return true;
    }
    catch (DbUpdateException ex) { 
        if (ex.Message == "Unable to open the DB connection") 
            return false; 
        else 
            throw;
    }
}

همانطور که مشاهده میکنید، در حالتیکه خطایی از نوع DbUpdateException رخ میدهد، مقدار بازگشتی متد را برابر با false میکنیم. اما مشکلی که وجود دارد این است که این‌کار به اندازه‌ی کافی خوانا نیست. همچنین honest بودن متد را نقض کرده‌ایم. به علاوه مشکل بزرگتر دیگر این است که ما با بازگرداندن یک مقدار bool، میتوانیم به متد بالاتر اطلاع بدهیم که کار مورد نظر انجام شده یا نه، اما در مورد دلیل انجام نشدن آن، هیچ کاری نمیتوانیم بکنیم. پیشنهاد من برای مقدار بازگشتی متد‌هایی که احتمال انجام نشدن کاری در آن‌ها می‌رود، استفاده از یک نوع اختصاصی می‌باشد.

در اینجا من این نوع را با نام کلاس Result معرفی میکنم. انتظاری که از این نوع اختصاصی داریم:

  • Honest بودن متد را نگه دارد.
  • خروجی متد را به همراه وضعیت اجرا شدن برگرداند.
  • شکل یکسانی را برای خطا‌ها داشته باشد.
  • فقط جلوی خطا‌های غیر منتظره را بگیرد.


برای مثال کد بالا را به شکل زیر refactor می‌کنیم:

private Result SaveCustomer(Customer customer) { 
    try { 

        using (var context = new MyContext()) { 

            context.Customers.Add(customer); 
            context.SaveChanges();
 } 

        return Result.Ok();
    } 
    catch (DbUpdateException ex) { 
        if (ex.Message == "Unable to open the DB connection") 
            Result.Fail(ErrorType.DatabaseIsOffline);

        if (ex.Message.Contains("IX_Customer_Name")) 
            return Result.Fail(ErrorType.CustomerAlreadyExists);

        throw;
    }
}

به عبارتی با این روش میتوانیم از انجام شدن/نشدن عملیات اطمینان حاصل کنیم و خروجی/دلیل انجام نشدن را نیز میتوانیم برگردانیم.

اگر به امضای متد‌های زیر نگاه کنیم، می‌توانیم آن‌ها را طبق الگوی CQS دسته‌بندی کنیم: 

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

مطالب
چگونه برنامه‌های دات نت را خارج از ویژوال استودیو دیباگ کنیم؟
مشکل: نگارش  1.0.808.0 برنامه‌ی DNTProfiler بر روی سایر سیستم‌ها، هنوز به مرحله‌ی نمایش نرسیده، کرش می‌کند. علت چیست؟

این نگارش بر روی سیستم من مشکلی نداشت ولی پس از چند گزارش عدم امکان اجرای آن بر روی سایر سیستم‌ها، یک ماشین مجازی ویندوز 8.1 را تهیه و برنامه را بر روی آن اجرا کردم. بله ... برنامه هنوز به مرحله‌ی نمایش نرسیده، محو می‌شد. در این مرحله‌ی ابتدایی امکان تهیه‌ی لاگ استثنای حاصل توسط برنامه وجود نداشت و تنها این خطا در event viewer ویندوز (Computer management -> event viewer -> windows logs -> application) قابل مشاهده بود:
 Application: DNTProfiler.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Windows.Markup.XamlParseException
Stack:
at System.Windows.Markup.WpfXamlLoader.Load
متاسفانه از این استثنای لاگ شده‌ی در لاگ‌های ویندوز، اطلاعات مفیدی را نمی‌توان دریافت کرد. عنوان کرده XamlParse برنامه را نمی‌تواند آغاز کند.
روش حل این نوع مشکلات و بررسی آن‌ها در خارج از VS.NET، با استفاده از برنامه‌ی معروف WinDBG مایکروسافت میسر است.
دو نگارش 32 بیتی و 64 بیتی آن‌را از اینجا می‌توانید دریافت کنید:
http://codemachine.com/downloads.html

پس از دریافت، بهتر است نسخه‌ی 32 بیتی آن‌را اجرا کنید، زیرا برای دیباگ برنامه‌های دات نت این نسخه است که امکان بارگذاری فایل C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll را دارد.

تا اینجا فرض بر این است که نسخه‌ی 32 بیتی windbg را دریافت و اجرا کرده‌اید. در ادامه از منوی File آن گزینه‌ی Open executable را انتخاب کرده و فایل exe برنامه را به آن معرفی کنید.
 CommandLine: C:\Tests\DNTProfiler.1.0.808.0\DNTProfiler.exe
Symbol search path is: srv*
Executable search path is:
ModLoad: 00150000 00176000 DNTProfiler.exe
ModLoad: 76f30000 77098000 ntdll.dll
ModLoad: 72ee0000 72f36000 C:\Windows\SysWOW64\MSCOREE.DLL
ModLoad: 74be0000 74d20000 C:\Windows\SysWOW64\KERNEL32.dll
ModLoad: 756e0000 757af000 C:\Windows\SysWOW64\KERNELBASE.dll
(684.764): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=37af0000 edx=00000000 esi=7eb8f000 edi=00000000
eip=76fe2d15 esp=002ff604 ebp=002ff630 iopl=0 nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b efl=00000246
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntdll.dll -
ntdll!LdrResolveDelayLoadsFromDll+0xa89:
76fe2d15 cc   int   3
برنامه داخل این دیباگر اجرا شده و در همینجا نیز به پایان می‌رسد. در ادامه‌ی کار در textbox ذیل این گزارش، دستور g را صادر کرده و enter کنید.
 0:000> g
ModLoad: 757b0000 75827000 C:\Windows\SysWOW64\ADVAPI32.dll
ModLoad: 05080000 05102000 Newtonsoft.Json.dll
(684.764): C++ EH exception - code e06d7363 (first chance)
(684.764): C++ EH exception - code e06d7363 (first chance)
(684.764): C++ EH exception - code e06d7363 (first chance)
(684.764): CLR exception - code e0434352 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=002fd0a0 ebx=00000005 ecx=00000005 edx=00000000 esi=002fd164 edi=00000001
eip=756f3d67 esp=002fd0a0 ebp=002fd0f8 iopl=0 nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b efl=00200216
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\SysWOW64\KERNELBASE.dll -
KERNELBASE!RaiseException+0x48:
756f3d67 8b4c2454   mov   ecx,dword ptr [esp+54h] ss:002b:002fd0f4=3fce1188
فرمان g سبب می‌شود تا کار دیباگ برنامه ادامه پیدا کند و به اولین CLR exception رسیده و متوقف شود. در ادامه نیاز است تا با صدور فرمان sxe clr، دیباگ clr را فعال کرده و سپس مجددا دستور g را برای ادامه‌ی دیباگ صادر کنیم.
 0:000> sxe clr
0:000> g
(684.764): C++ EH exception - code e06d7363 (first chance)
(684.764): CLR exception - code e0434352 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=002fe2a0 ebx=00000005 ecx=00000005 edx=00000000 esi=002fe368 edi=00000001
eip=756f3d67 esp=002fe2a0 ebp=002fe2fc iopl=0 nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b efl=00200216
KERNELBASE!RaiseException+0x48:
756f3d67 8b4c2454   mov   ecx,dword ptr [esp+54h] ss:002b:002fe2f4=3fce2388
بنابراین ابتدا دستور sxe clr را صادر و enter و سپس g و enter. مجددا برنامه پس از رسیدن به یک CLR exception متوقف می‌شود.
اکنون می‌خواهیم پیام استثنای واقعی دات نت را مشاهده کنیم. به همین جهت ابتدا دستور loadby sos clr! و سپس دستور CLRStack! را صادر می‌کنیم:
 0:000> !loadby sos clr
0:000> !CLRStack
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll -
PDB symbol for clr.dll not loaded
OS Thread Id: 0x764 (0)
Child SP IP Call Site
002fe3f4 756f3d67 [GCFrame: 002fe3f4]
002fe430 756f3d67 [GCFrame: 002fe430]
002fe528 756f3d67 [GCFrame: 002fe528]
002fe544 756f3d67 [HelperMethodFrame_2OBJ: 002fe544] System.RuntimeTypeHandle.CreateInstance(System.RuntimeType, Boolean, Boolean, Boolean ByRef, System.RuntimeMethodHandleInternal ByRef, Boolean ByRef)
DNTProfiler.App.Main()
002ff190 720e2652 [GCFrame: 002ff190]
در اینجا کار بررسی stack trace برنامه پایان می‌یابد. اکنون با صدور دستور PrintException! می‌توان علت اصلی استثناء را مشاهده کرد:
 0:000> !PrintException
Exception object: 021e6f0c
Exception type: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
InnerException: System.IO.FileNotFoundException, Use !PrintException 021e6950 to see more.
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80131604
0:000> !PrintException 021e6950
Exception object: 021e6950
Exception type: System.IO.FileNotFoundException
Message: Could not load file or assembly 'System.Net.Http.Formatting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.
InnerException: <none>
StackTrace (generated):
SP IP Function
00000000 00000001 DNTProfiler!DNTProfiler.Services.SelfHostConfig.getHostConfiguration(System.String, Boolean)+0x2
002FE298 006B4E54 DNTProfiler!DNTProfiler.Services.SelfHostConfig.OpenWait(System.String, Boolean)+0x14
002FE2B0 006B4A3F DNTProfiler!DNTProfiler.ViewModels.MainWindowViewModel.doStart(System.String)+0x1f
002FE2C0 006B368D DNTProfiler!DNTProfiler.ViewModels.MainWindowViewModel..ctor(DNTProfiler.Common.Mvvm.ICommonDialogsService, DNTProfiler.Common.Controls.DialogManagement.Contracts.IDialogManager)+0xcd
002FE2D8 006B04C9 DNTProfiler!DNTProfiler.MainWindow..ctor()+0x91

StackTraceString: <none>
HResult: 80070002
همانطور که مشاهده می‌کنید، عنوان کرده‌است که فایل System.Net.Http.Formatting قابل بارگذاری نیست. بله ... این فایل جزو بسته‌ی نگارش اول برنامه نبود و به نظر علت عدم کرش آن، نصب این فایل در GAC سیستم توسط نصاب‌های ASP.NET MVC بوده‌است (نگارش‌های 3 و 4 آن). بنابراین اگر کسی این فایل را در GAC سیستم خود نداشته باشد، قادر به اجرای برنامه نخواهد بود.
روش حل این مشکل، مراجعه به خواص پروژه، یافتن اسمبلی System.Net.Http.Formatting در لیست ارجاعات برنامه و سپس true کردن خاصیت copy to local آن است. به این ترتیب این اسمبلی در کنار فایل اجرایی برنامه کپی خواهد شد.

بنابراین خلاصه‌ی عملیات یافتن علت اصلی کرش برنامه در خارج از ویژوال استودیو به صورت ذیل است:
الف) اجرای نسخه‌ی 32 بیتی برنامه‌ی windbg
ب) انتخاب منوی File آن و سپس باز کردن فایل اجرایی برنامه توسط گزینه‌ی Open executable
ج) اجرای دستورات ذیل به ترتیب:
 0:000> g
0:000> sxe clr
0:000> g
0:000> !loadby sos clr
0:000> !CLRStack
0:000> !PrintException

چند نمونه‌ی مشابه برای مطالعه‌ی بیشتر
Finding CLR exceptions without visual studio
Power of WinDBG: The story of mysterious crash of WPF application
مطالب
ارسال یک مخزن کد از پیش موجود به GitHub‌ از طریق VSCode
در مطلب «کار با یک مخزن کد GitHub از طریق VSCode»، نحوه‌ی Clone یک مخزن کد از پیش موجود در GitHub را بررسی کردیم. گردش کاری دیگری را که می‌توان درنظر گرفت، داشتن یک مخزن کد محلی و سپس ارسال آن به یک مخزن کد جدید در GitHub است.


ایجاد یک مخزن کد محلی جدید توسط VSCode

فرض کنید پوشه‌ای را با ساختار ذیل داریم:


وجود فایل gitignore. را در حین کار با Git و ارسال پروژه به مخازن کد فراموش نکنید. این فایل سبب خواهد شد تا بسیاری از پوشه‌هایی که نباید ارسال شوند (مانند پوشه‌های bin یا packages و امثال آن)، به صورت خودکار ندید گرفته شوند.

در ادامه برای افزودن این پوشه به یک مخزن کد محلی تنها کافی است به برگه‌ی Git آن مراجعه کرده و بر روی دکمه‌ی Initialize repository کلیک کنیم:


البته این دستور در منوی ctrl+shift+p هم با جستجوی git ظاهر می‌شود:


پس از آغازن مخزن کد محلی، توضیحاتی را نوشته و سپس بر روی دکمه‌ی commit کلیک می‌کنیم تا این تغییرات با آن هماهنگ شوند:



ارسال مخزن کد محلی به GitHub از طریق VSCode

در ادامه می‌خواهیم این مخزن کد محلی را به یک مخزن کد جدید در GitHub ارسال کنیم. به همین منظور یک مخزن کد جدید را در GitHub آغاز کرده و گزینه‌ی «Initialize this repository with a README » را انتخاب نمی‌کنیم:


در صفحه‌ی بعدی که ظاهر می‌شود، دو دستور آن مهم هستند:
 …or push an existing repository from the command line
git remote add origin https://github.com/VahidN/test-vscode.git
git push -u origin master

در VSCode، با فشردن دکمه‌های Ctrl+back-tick، کنسول خط فرمان را گشوده و دو دستور فوق را به ترتیب اجرا کنید. این دستورات سبب خواهند شد تا مخزن کد محلی، به مخزن کد GitHub متصل شده و همچنین تغییرات آن به سمت سرور ارسال و با آن هماهنگ شوند.
اکنون اگر به مخزن کد GitHub مراجعه کنیم، می‌توان این هماهنگی و ارسال فایل‌ها را مشاهده کرد:



یک گردش کاری دیگر: هم مخزن کد محلی و هم مخزن کد GitHub دارای فایل هستند

فرض کنید مخزن کد GitHub شما هم اکنون دارای تعدادی فایل است و مانند مثال فوق، از ابتدا و بدون افزودن فایلی به آن ایجاد نشده‌است. همچنین مخزن کد محلی نیز دارای تعدادی فایل است (Initialize repository شده‌است) و نمی‌خواهیم از روش Clone مطلب «کار با یک مخزن کد GitHub از طریق VSCode» استفاده کنیم.
در اینجا نیز با فشردن دکمه‌های Ctrl+back-tick، کنسول خط فرمان را گشوده و همان سطر اول git remote add origin را اجرا می‌کنیم:
 git remote add origin https://github.com/VahidN/test-vscode.git
اما باید دقت داشت که اینبار دستور دوم رال که push است، نمی‌توانیم اجرا کنیم (چون سرور ریموت دارای فایل است). در اینجا برای هماهنگی با سرور ابتدا باید دستور pull را صادر کنیم:
 git pull origin master --allow-unrelated-histories
به این ترتیب فایل‌های سرور دریافت شده و به پروژه‌ی جاری اضافه می‌شوند.
همچنین برای هماهنگی تغییرات محلی بعدی با سرور (عملیات push) باید ابتدا branch را تنظیم کرد:
 git branch --set-upstream-to=origin/master master
مطالب
امکان استفاده از یک هارد SSD بجای RAM در SQL Server 2014
Buffer Pool یکی از مصرف کنندگان اصلی حافظه در SQL Server است. برای مثال زمانیکه اطلاعاتی را از بانک اطلاعاتی دریافت می‌کنید، این داده‌ها در Buffer Pool کش می‌شوند. همچنین SQL Server اطلاعات کلیه Execution Plans را نیز در Plan Cache که جزئی از Buffer Pool است، برای استفاده‌ی مجدد نگهداری می‌کند. هر چقدر حافظه‌ی فیزیکی سرور شما بیشتر باشد، مقدار Buffer Pool نیز به همین میزان افزایش خواهد یافت که البته حداکثر آن‌را می‌توان در تنظیمات حافظه‌ی سرور محدود کرد (Max Server Memory setting).
در دنیای واقعی میزان حافظه‌ی فیزیکی سرورها محدود است. در SQL Server 2014 راه حلی برای این مشکل تحت عنوان Buffer Pool Extensions ارائه شده‌است که محل قرارگیری آن‌را در تصویر ذیل مشاهده می‌کنید:


Buffer Pool Extensions از یک فایل ساده که به آن Extension File نیز گفته می‌شود، تشکیل شده‌است و امکان ذخیره سازی آن بر روی هاردهای سریعی مانند SSD Driveها میسر است. این فایل، ساختاری را همانند page file، در سیستم عامل ویندوز دارد. در این حالت بجای اضافه کردن RAM بیشتر به سرور، یک Extension File را می‌توان بکار گرفت. هر زمان که Buffer Pool اصلی تحت فشار قرار گیرد (به میزان حافظه‌ای بیش از حافظه‌ی فیزیکی سرور نیاز باشد)، از این افزونه‌ی فایلی استفاده خواهد شد.
اطلاعات جزئیات Buffer Pool را توسط کوئری ذیل می‌توان بدست آورد:
 Select * from sys.dm_os_buffer_descriptors


نحوه‌ی فعال سازی و تنظیم Buffer Pool Extensions

قبل از هر کاری بهتر است وضعیت افزونه‌ی Buffer pool را بررسی کرد:
 select * from sys.dm_os_buffer_pool_extension_configuration


همانطور که ملاحظه می‌کنید، در حالت پیش فرض غیرفعال است.
سپس یک فایل یک گیگابایتی را به عنوان افزونه‌ی Buffer pool ایجاد می‌کنیم.
 ALTER SERVER CONFIGURATION
SET BUFFER POOL EXTENSION ON
 (FILENAME = 'd:\BufferPoolExt.BPE', SIZE = 1GB);
توصیه شده‌است که این فایل را در یک درایور پر سرعت SSD قرار دهید؛ ولی محدودیتی از لحاظ محل قرارگیری ندارد (هر چند به نظر فقط در حالتیکه از SSD Drive استفاده شود واقعا کار می‌کند).
اینبار اگر کوئری اول را اجرا کنیم، چنین خروجی قابل مشاهده است:


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


تغییر اندازه‌ی افزونه‌ی Buffer pool

اگر سعی کنیم، یک گیگابایت را مثلا به 10 گیگابایت افزایش دهیم:
 ALTER SERVER CONFIGURATION
SET BUFFER POOL EXTENSION ON
 (FILENAME = 'd:\BufferPoolExt.BPE', SIZE = 10GB);
با خطای ذیل مواجه خواهیم شد:
 Could not change the value of the 'BPoolExtensionPath' property
برای رفع این مشکل، ابتدا باید افزونه‌ی Buffer pool را غیرفعال کرد:
 ALTER SERVER CONFIGURATION
SET BUFFER POOL EXTENSION OFF
سپس می‌توان مجددا اندازه و یا مسیر دیگری را مشخص کرد. بهتر است اندازه‌ی این فایل را حدود 16 برابر حداکثر میزان حافظه‌ی سرور (Max Server Memory) تعیین کنید.
همچنین توصیه شده‌است که پس از غیرفعال کردن این افزونه، بهتر است یکبار instance جاری را ری استارت کنید.


چه زمانی بهتر است از افزونه‌ی Buffer pool استفاده شود؟
در محیط‌های read-heavy OLTP، استفاده از یک چنین افزونه‌ای می‌تواند میزان کارآیی و پاسخگویی سیستم را به شدت افزایش دهد (تا 50 درصد).


سؤال: آیا غیرفعال کردن افزونه‌ی Buffer pool سبب از دست رفتن اطلاعات می‌شود؟
خیر. BPE، تنها clean pages را در خود ذخیره می‌کند؛ یعنی تنها اطلاعاتی که Commit شده‌اند در آن حضور خواهند داشت و در این حالت حذف آن یا ری استارت کردن سرور، سبب از دست رفتن اطلاعات نخواهند شد.


برای مطالعه بیشتر

Buffer Pool Extension
SQL Server 2014 Buffer Pool Extensions
Do you require a SSD to use the Buffer Pool Extension feature in SQL Server 2014
Buffer Pool Extensions in SQL Server 2014
SQL Server 2014 – Buffer Pool Extension
مطالب
سرورهای متصل شده‌ی SQL Server و مبحث تراکنش‌ها

یکی از قابلیت‌های جالب SQL Server در یک شبکه محلی امکان link و اتصال آن‌ها به یکدیگر است. به این صورت امکان کوئری گرفتن (و یا اعمال متداول SQL ایی) از دو یا چند سرور مختلف با دستورات T-SQL میسر می‌شود؛ به نحوی که حس یکپارچگی دیتابیس‌های این سرورها را حین کوئری نوشتن خواهیم داشت.
برای مثال فرض کنید دو سرور SQL1 و SQL2 را در شبکه داریم. می‌خواهیم در سرور SQL1 اتصالی را به سرور SQL2 ایجاد کنیم.

USE master

EXEC sp_addlinkedserver
'SQL2',
N'SQL Server'

sp_addlinkedsrvlogin @useself='false ', @rmtsrvname = 'SQL2',
@rmtuser = 'sa',
@rmtpassword = 'pass#'

دستورات T-SQL فوق کار ثبت یک liked server جدید و اعمال مشخصات کاربری که توسط آن قرار است به سرور SQL2 دسترسی داشت، انجام می‌دهند.
اکنون جهت بررسی این اتصال در سرور SQL1 کوئری زیر را اجرا می‌کنیم:

select * from sql2.faxManager.dbo.tblErja

که نحوه‌ی فراخوانی جدول مورد نظر باید به صورت Server.DatabaseName.dbo.TableName در آن رعایت شود.
تا اینجا همه چیز خوب است. مشکل از زمانی شروع می‌شود که بخواهیم تراکنش‌ها را نیز دخالت دهیم و اصولی کار کنیم. برای مثال:

begin distributed tran
select * from sql2.faxManager.dbo.tblErja
commit tran

خطایی که در ویندوز سرور 2003 با آخرین به روز رسانی‌ها ظاهر می‌شود به صورت زیر است:

The operation could not be performed because OLE DB provider for linked server was unable to begin a distributed transaction.
OLE DB provider for linked server returned message "The partner transaction manager has disabled its support for remote/network transactions.".


به صورت پیش فرض این نوع تراکنش‌های توزیع شده غیرفعال هستند مگر اینکه فعال شوند و روش حل مشکل نیز به صورت زیر می‌باشد:
قبل از هر کاری به کنسول سرویس‌های ویندوز مراجعه کرده و از در حال اجرا بودن سرویس Distribute Transaction Coordinator اطمینان حاصل کنید.
سپس به قسمت زیر مراجعه نمائید:
Control Panel > Administrative Tools > Component Services


نود مربوط به Component Service را گشوده و سپس بر روی My Computer کلیک راست کرده و گزینه‌ی خواص را انتخاب کنید.
در صفحه‌ی بازه شده به برگه‌ی MSTDC مراجعه کرده و بر روی دکمه‌ی Security Configuration کلیک نمائید.
اکنون تنظیمات آن‌را مطابق شکل زیر تغییر دهید.


این تنظیم باید بر روی هر دو سرور SQL1 و SQL2 انجام شود.

پس از این تغییرات که شامل راه اندازی مجدد سرویس Distribute Transaction Coordinator نیز خواهد شد، مشکل خطای فوق برطرف شده و امکان استفاده از تراکنش‌ها در linked servers نیز میسر می‌شود.

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

Unable to start a nested transaction for OLE DB provider for linked server . A nested transaction was required because the XACT_ABORT option was set to OFF.
OLE DB provider for linked server returned message "Cannot start more transactions on this session.".


برای حل این مشکل یک سطر زیر را باید به ابتدای کوئری خود اضافه کرد که جزو الزامات تراکنش‌های توزیع شده است و به این صورت از rollback کامل تمامی دستورات موجود فراخوانی شده T-SQL در صورت بروز کوچکترین خطایی اطمینان حاصل می‌کند:
SET XACT_ABORT ON


برای مطالعه بیشتر:
MSDTC Troubleshooting

نظرات اشتراک‌ها
ایجاد، اتصال و انتشار مخزن کد در Visual Studio 2012 با استفاده از Git
در صفحه دانلود آپدیت ورژن 2  http://www.microsoft.com/en-us/download/details.aspx?id=36508   چندین فایل وجود داره که اینطور توضیح داده :
Visual Studio:   vsupdate_KB2707250.exe (1.2 MB )
Team Foundation Server:  ISO Image: VS2012_Q1_TFS_ENU.iso
Team Foundation Server Express: ISO Image: VS2012_Q1_TFS_EXP_ENU.iso
Agents: VS2012_Q1_AGTS_enu.iso

من فقط فایل مرتبط با Visual Studio رو نصب کردم و تونستم یک مخزن لوکال git  راه بندازم.
قابلیت‌های که با نصب همون دو فایل برای من وجود داره :
commit ، undo , history ,view history,compare , Branche ,... 
اشتراک‌ها
سری آموزش مقدماتی Git و GitHub

Git and GitHub Tutorials for Beginners!

#1 - What is Git and GitHub?
#2 - Creating Repositories and Commits
#3 - What are Git Branches?
#4 - Creating Forks and Pull Requests
#5 - Understanding GitHub Issues
#6 - Installing Git and Cloning our Repository
#7 - Staging Changes and Making you First Commit!
 

سری آموزش مقدماتی Git و GitHub