مطالب
کار با چندین نوع بانک اطلاعاتی متفاوت در Entity Framework Core
یکی از مزایای کار با ORMها، امکان تعویض نوع بانک اطلاعاتی برنامه، بدون نیازی به تغییری در کدهای برنامه است. برای مثال فرض کنید می‌خواهید با تغییر رشته‌ی اتصالی برنامه، یکبار از بانک اطلاعاتی SQL Server و بار دیگر از بانک اطلاعاتی کاملا متفاوتی مانند SQLite استفاده کنید. در این مطلب نکات استفاده‌ی از چندین نوع بانک اطلاعاتی متفاوت را در برنامه‌های مبتنی بر EF Core بررسی خواهیم کرد.


هر بانک اطلاعاتی باید Migration و Context خاص خودش را داشته باشد

تامین کننده‌ی بانک‌های اطلاعاتی مختلف، عموما تنظیمات خاص خودشان را داشته و همچنین دستورات SQL متفاوتی را نیز تولید می‌کنند. به همین جهت نمی‌توان از یک تک Context، هم برای SQLite و هم SQL Server استفاده کرد. به علاوه قصد داریم اطلاعات Migrations هر کدام را نیز در یک اسمبلی جداگانه قرار دهیم. در یک چنین حالتی EF نمی‌پذیرد که Context تولید کننده‌ی Migration، در اسمبلی دیگری قرار داشته باشد و باید حتما در همان اسمبلی Migration قرار گیرد. بنابراین ساختار پوشه بندی مثال جاری به صورت زیر خواهد بود:


- در پوشه‌ی EFCoreMultipleDb.DataLayer فقط اینترفیس IUnitOfWork را قرار می‌دهیم. از این جهت که وقتی قرار شد در برنامه چندین Context تعریف شوند، لایه‌ی سرویس برنامه قرار نیست بداند در حال حاضر با کدام Context کار می‌کند. به همین جهت است که تغییر بانک اطلاعاتی برنامه، تغییری را در کدهای اصلی آن ایجاد نخواهد کرد.
- در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLite کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQLite را قرار می‌دهیم.
- در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLServer کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQL Server را قرار می‌دهیم.

برای نمونه ابتدای Context مخصوص SQLite چنین شکلی را دارد:
    public class SQLiteDbContext : DbContext, IUnitOfWork
    {
        public SQLiteDbContext(DbContextOptions options) : base(options)
        { }

        public virtual DbSet<User> Users { set; get; }
و IDesignTimeDbContextFactory مخصوص آن که برای Migrations از آن استفاده می‌شود، به صورت زیر تهیه خواهد شد:
namespace EFCoreMultipleDb.DataLayer.SQLite.Context
{
    public class SQLiteDbContextFactory : IDesignTimeDbContextFactory<SQLiteDbContext>
    {
        public SQLiteDbContext CreateDbContext(string[] args)
        {
            var basePath = Directory.GetCurrentDirectory();
            Console.WriteLine($"Using `{basePath}` as the BasePath");
            var configuration = new ConfigurationBuilder()
                                    .SetBasePath(basePath)
                                    .AddJsonFile("appsettings.json")
                                    .Build();
            var builder = new DbContextOptionsBuilder<SQLiteDbContext>();
            var connectionString = configuration.GetConnectionString("SqliteConnection")
                                                .Replace("|DataDirectory|", Path.Combine(basePath, "wwwroot", "app_data"));
            builder.UseSqlite(connectionString);
            return new SQLiteDbContext(builder.Options);
        }
    }
}
هدف از این فایل، ساده سازی کار تولید اطلاعات Migrations برای EF Core است. به این صورت ساخت new SQLiteDbContext توسط ما صورت خواهد گرفت و دیگر EF Core درگیر جزئیات وهله سازی آن نمی‌شود.


تنظیمات رشته‌های اتصالی بانک‌های اطلاعاتی مختلف

در اینجا محتویات فایل appsettings.json را که در آن تنظیمات رشته‌های اتصالی دو بانک SQL Server LocalDB و همچنین SQLite در آن ذکر شده‌اند، مشاهده می‌کنید:
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SqlServerConnection": "Data Source=(LocalDB)\\MSSQLLocalDB;Initial Catalog=ASPNETCoreSqlDB;AttachDbFilename=|DataDirectory|\\ASPNETCoreSqlDB.mdf;Integrated Security=True;MultipleActiveResultSets=True;",
    "SqliteConnection": "Data Source=|DataDirectory|\\ASPNETCoreSqliteDB.sqlite",
    "InUseKey": "SqliteConnection"
  }
}
همین رشته‌ی اتصالی است که در SQLiteDbContextFactory مورد استفاده قرار می‌گیرد.
یک کلید InUseKey را هم در اینجا تعریف کرده‌ایم تا مشخص باشد در ابتدای کار برنامه، کلید کدام رشته‌ی اتصالی مورد استفاده قرار گیرد. برای مثال در اینجا کلید رشته‌ی اتصالی SQLite تنظیم شده‌است.
در این تنظیمات یک DataDirectory را نیز مشاهده می‌کنید. مقدار آن در فایل Startup.cs برنامه به صورت زیر بر اساس پوشه‌ی جاری تعیین می‌شود و در نهایت به wwwroot\app_data اشاره خواهد کرد:
var connectionStringKey = Configuration.GetConnectionString("InUseKey");
var connectionString = Configuration.GetConnectionString(connectionStringKey)
                     .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data"));


دستورات تولید Migrations و به روز رسانی بانک اطلاعاتی

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

برای تولید Migrations، از طریق خط فرمان، به پوشه‌ی اسمبلی مدنظر وارد شده و دستور زیر را اجرا کنید:
For /f "tokens=2-4 delims=/ " %%a in ('date /t') do (set mydate=%%c_%%a_%%b)
For /f "tokens=1-2 delims=/:" %%a in ("%TIME: =0%") do (set mytime=%%a%%b)
dotnet build
dotnet ef migrations --startup-project ../EFCoreMultipleDb.Web/ add V%mydate%_%mytime% --context SQLiteDbContext
در اینجا ذکر startup-project و همچنین context برای پروژه‌هایی که context آن‌ها خارج از startup-project است و همچنین بیش از یک context دارند، ضروری‌است. بدیهی است این دستورات را باید یکبار در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLite و یکبار در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLServer اجرا کنید.
دو سطر اول آن، زمان اجرای دستورات را به عنوان نام فایل‌ها تولید می‌کنند.

پس از تولید Migrations، اکنون نوبت به تولید بانک اطلاعاتی و یا به روز رسانی بانک اطلاعاتی موجود است:
dotnet build
dotnet ef --startup-project ../EFCoreMultipleDb.Web/ database update --context SQLServerDbContext
در این مورد نیز ذکر startup-project و همچنین context مدنظر ضروری است.


بدیهی است این رویه را پس از هربار تغییراتی در موجودیت‌های برنامه و یا تنظیمات آن‌ها در Contextهای متناظر، نیاز است مجددا اجرا کنید. البته اجرای اولین دستور اجباری است؛ اما می‌توان دومین دستور را به صورت زیر نیز اجرا کرد:
namespace EFCoreMultipleDb.Web
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            applyPendingMigrations(app);
// ...
        }

        private static void applyPendingMigrations(IApplicationBuilder app)
        {
            var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
            using (var scope = scopeFactory.CreateScope())
            {
                var uow = scope.ServiceProvider.GetService<IUnitOfWork>();
                uow.Migrate();
            }
        }
    }
}
متد applyPendingMigrations، کار وهله سازی IUnitOfWork را انجام می‌دهد. سپس متد Migrate آن‌را اجرا می‌کند، تا تمام Migrations تولید شده، اما اعمال نشده‌ی به بانک اطلاعاتی، به صورت خودکار به آن اعمال شوند. متد Migrate نیز به صورت زیر تعریف می‌شود:
namespace EFCoreMultipleDb.DataLayer.SQLite.Context
{
    public class SQLiteDbContext : DbContext, IUnitOfWork
    {
    // ... 

        public void Migrate()
        {
            this.Database.Migrate();
        }
    }
}

مرحله‌ی آخر: انتخاب بانک اطلاعاتی در برنامه‌ی آغازین

پس از این تنظیمات، قسمتی که کار تعریف IUnitOfWork و همچنین DbContext جاری برنامه را انجام می‌دهد، به صورت زیر پیاده سازی می‌شود:
namespace EFCoreMultipleDb.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUsersService, UsersService>();

            var connectionStringKey = Configuration.GetConnectionString("InUseKey");
            var connectionString = Configuration.GetConnectionString(connectionStringKey)
                     .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data"));
            switch (connectionStringKey)
            {
                case "SqlServerConnection":
                    services.AddScoped<IUnitOfWork, SQLServerDbContext>();
                    services.AddDbContext<SQLServerDbContext>(options =>
                    {
                        options.UseSqlServer(
                            connectionString,
                            dbOptions =>
                                {
                                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                                    dbOptions.CommandTimeout(minutes);
                                    dbOptions.EnableRetryOnFailure();
                                });
                    });
                    break;
                case "SqliteConnection":
                    services.AddScoped<IUnitOfWork, SQLiteDbContext>();
                    services.AddDbContext<SQLiteDbContext>(options =>
                    {
                        options.UseSqlite(
                            connectionString,
                            dbOptions =>
                                {
                                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                                    dbOptions.CommandTimeout(minutes);
                                });
                    });
                    break;
                default:
                    throw new NotImplementedException($"`{connectionStringKey}` is not defined in `appsettings.json` file.");
            }

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }
در اینجا ابتدا مقدار InUseKey از فایل تنظیمات برنامه دریافت می‌شود. بر اساس مقدار آن، رشته‌ی اتصالی مدنظر دریافت شده و سپس یکی از دو حالت SQLite و یا SQLServer انتخاب می‌شوند. برای مثال اگر Sqlite انتخاب شده باشد، IUnitOfWork به SQLiteDbContext تنظیم می‌شود. به این ترتیب لایه‌ی سرویس برنامه که با IUnitOfWork کار می‌کند، به صورت خودکار وهله‌ای از SQLiteDbContext را دریافت خواهد کرد.


آزمایش برنامه

ابتدا کدهای کامل این مطلب را از اینجا دریافت کنید: EFCoreMultipleDb.zip
سپس آن‌را اجرا نمائید. چنین تصویری را مشاهده خواهید کرد:


اکنون برنامه را بسته و سپس فایل appsettings.json را جهت تغییر مقدار InUseKey به کلید SqlServerConnection ویرایش کنید:
{
  "ConnectionStrings": {
    // …
    "InUseKey": "SqlServerConnection"
  }
}
اینبار اگر مجددا برنامه را اجرا کنید، چنین خروجی قابل مشاهده‌است:


مقدار username، در contextهای هر کدام از این بانک‌های اطلاعاتی، با مقدار متفاوتی به عنوان اطلاعات اولیه‌ی آن ثبت شده‌است. سرویسی هم که اطلاعات آن‌را تامین می‌کند، به صورت زیر تعریف شده‌است:
namespace EFCoreMultipleDb.Services
{
    public interface IUsersService
    {
        Task<User> FindUserAsync(int userId);
    }

    public class UsersService : IUsersService
    {
        private readonly IUnitOfWork _uow;
        private readonly DbSet<User> _users;

        public UsersService(IUnitOfWork uow)
        {
            _uow = uow;
            _users = _uow.Set<User>();
        }

        public Task<User> FindUserAsync(int userId)
        {
            return _users.FindAsync(userId);
        }
    }
}
همانطور که مشاهده می‌کنید، با تغییر context برنامه، هیچ نیازی به تغییر کدهای UsersService نیست؛ چون اساسا این سرویس نمی‌داند که IUnitOfWork چگونه تامین می‌شود.
مطالب
استفاده از چندین بانک اطلاعاتی به صورت همزمان در EF Code First
یکی از روش‌های تهیه‌ی برنامه‌های چند مستاجری، ایجاد بانک‌های اطلاعاتی مستقلی به ازای هر مشتری است؛ یا نمونه‌ی دیگر آن، برنامه‌هایی هستند که اطلاعات هر سال را در یک بانک اطلاعاتی جداگانه نگه‌داری می‌کنند. در ادامه قصد داریم، نحوه‌ی کار با این بانک‌های اطلاعاتی را به صورت همزمان، توسط EF Code first و در حالت استفاده از الگوی واحد کار و تزریق وابستگی‌ها، به همراه فعال سازی خودکار مباحث migrations و به روز رسانی ساختار تمام بانک‌های اطلاعاتی مورد استفاده، بررسی کنیم.


مشخص سازی رشته‌های متفاوت اتصالی

فرض کنید برنامه‌ی جاری شما قرار است از دو بانک اطلاعاتی مشخص استفاده کند که تعاریف رشته‌های اتصالی آن‌ها در وب کانفیگ به صورت ذیل مشخص شده‌اند:
  <connectionStrings>
    <clear />
    <add name="Sample07Context" connectionString="Data Source=(local);Initial Catalog=TestDbIoC;Integrated Security = true" providerName="System.Data.SqlClient" />
    <add name="Database2012" connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true" providerName="System.Data.SqlClient" />
  </connectionStrings>
البته، ذکر این مورد کاملا اختیاری است و می‌توان رشته‌های اتصالی را به صورت پویا نیز در زمان اجرا مشخص و مقدار دهی کرد.


تغییر Context برنامه جهت پذیرش رشته‌های اتصالی پویای قابل تغییر در زمان اجرا

اکنون که قرار است کاربران در حین ورود به برنامه، بانک اطلاعاتی مدنظر خود را انتخاب کنند و یا سیستم قرار است به ازای کاربری خاص، رشته‌ی اتصالی خاص او را به Context ارسال کند، نیاز است Context برنامه را به صورت ذیل تغییر دهیم:
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EF_Sample07.DomainClasses;
 
namespace EF_Sample07.DataLayer.Context
{
    public class Sample07Context : DbContext, IUnitOfWork
    {
        public DbSet<Category> Categories { set; get; }
        public DbSet<Product> Products { set; get; }
 
        /// <summary>
        /// It looks for a connection string named Sample07Context in the web.config file.
        /// </summary>
        public Sample07Context()
            : base("Sample07Context")
        {
        }
 
        /// <summary>
        /// To change the connection string at runtime. See the SmObjectFactory class for more info.
        /// </summary>
        public Sample07Context(string connectionString)
            : base(connectionString)
        {
            //Note: defaultConnectionFactory in the web.config file should be set.
        }
 
 
        public void SetConnectionString(string connectionString)
        {
            this.Database.Connection.ConnectionString = connectionString;
        }
    }
}
در اینجا دو متد سازنده را مشاهده می‌کنید. سازنده‌ی پیش فرض، از رشته‌ای اتصالی با نامی مساوی Sample07Context استفاده می‌کند و سازنده‌ی دوم، امکان پذیرش یک رشته‌ی اتصالی پویا را دارد. مقدار پارامتر ورودی آن می‌تواند نام رشته‌ی اتصالی و یا حتی مقدار کامل رشته‌ی اتصالی باشد. حالت پذیرش نام رشته‌ی اتصالی زمانی مفید است که همانند مثال ابتدای بحث، این نام‌ها را پیشتر در فایل کانفیگ برنامه ثبت کرده باشید و حالت پذیرش مقدار کامل رشته‌ی اتصالی، جهت مقدار دهی پویای آن بدون نیاز به ثبت اطلاعاتی در فایل کانفیگ برنامه مفید است.

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


مدیریت سشن‌های رشته‌ی اتصالی جاری

پس از اینکه کاربر، در حین ورود مشخص کرد که از چه بانک اطلاعاتی قرار است استفاده کند و یا اینکه برنامه بر اساس اطلاعات ثبت شده‌ی او تصمیم‌گیری کرد که باید از کدام رشته‌ی اتصالی استفاده کند، نگهداری این رشته‌ی اتصالی نیاز به سشن دارد تا به ازای هر کاربر متصل به سایت منحصربفرد باشد. در مورد مدیریت سشن‌ها در برنامه‌های وب، از نکات مطرح شده‌ی در مطلب «مدیریت سشن‌ها در برنامه‌های وب به کمک تزریق وابستگی‌ها» استفاده خواهیم کرد:
using System;
using System.Threading;
using System.Web;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.ServiceLayer;
using StructureMap;
using StructureMap.Web;
using StructureMap.Web.Pipeline;
 
namespace EF_Sample07.IoCConfig
{
    public static class SmObjectFactory
    {
        private static readonly Lazy<Container> _containerBuilder =
            new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);
 
        public static IContainer Container
        {
            get { return _containerBuilder.Value; }
        }
 
        public static void HttpContextDisposeAndClearAll()
        {
            HttpContextLifecycle.DisposeAndClearAll();
        }
 
        private static Container defaultContainer()
        {
            return new Container(ioc =>
            {
                // session manager setup
                ioc.For<ISessionProvider>().Use<DefaultWebSessionProvider>();
                ioc.For<HttpSessionStateBase>()
                   .Use(ctx => new HttpSessionStateWrapper(HttpContext.Current.Session));
 
                ioc.For<IUnitOfWork>()
                   .HybridHttpOrThreadLocalScoped()
                   .Use<Sample07Context>()
                    // Remove these 2 lines if you want to use a connection string named Sample07Context, defined in the web.config file.
                   .Ctor<string>("connectionString")
                   .Is(ctx => getCurrentConnectionString(ctx));
 
                ioc.For<ICategoryService>().Use<EfCategoryService>();
                ioc.For<IProductService>().Use<EfProductService>();
 
                ioc.For<ICategoryService>().Use<EfCategoryService>();
                ioc.For<IProductService>().Use<EfProductService>();
 
                ioc.Policies.SetAllProperties(properties =>
                {
                    properties.OfType<IUnitOfWork>();
                    properties.OfType<ICategoryService>();
                    properties.OfType<IProductService>();
                    properties.OfType<ISessionProvider>();
                });
            });
        }
 
        private static string getCurrentConnectionString(IContext ctx)
        {
            if (HttpContext.Current != null)
            {
                // this is a web application
                var sessionProvider = ctx.GetInstance<ISessionProvider>();
                var connectionString = sessionProvider.Get<string>("CurrentConnectionString");
                if (string.IsNullOrWhiteSpace(connectionString))
                {
                    // It's a default connectionString.
                    connectionString = "Database2012";
                    // this session value should be set during the login phase
                    sessionProvider.Store("CurrentConnectionStringName", connectionString);
                }
 
                return connectionString;
            }
            else
            {
                // this is a desktop application, so you can store this value in a global static variable.
                return "Database2012";
            }
        }
    }
}
در اینجا نحوه‌ی پویا سازی تامین رشته‌ی اتصالی را مشاهده می‌کنید. در مورد اینترفیس ISessionProvider و کلاس پایه HttpSessionStateBase پیشتر در مطلب «مدیریت سشن‌ها در برنامه‌های وب به کمک تزریق وابستگی‌ها» بحث شد.
نکته‌ی مهم این تنظیمات، قسمت مقدار دهی سازنده‌ی کلاس Context برنامه به صورت پویا توسط IoC Container جاری است. در اینجا هر زمانیکه قرار است وهله‌ای از Sample07Context ساخته شود، از سازنده‌ی دوم آن که دارای پارامتری به نام connectionString است، استفاده خواهد شد. همچنین مقدار آن به صورت پویا از متد getCurrentConnectionString که در انتهای کلاس تعریف شده‌است، دریافت می‌گردد.
در این متد ابتدا مقدار HttpContext.Current بررسی شده‌است. این مقدار اگر نال باشد، یعنی برنامه‌ی جاری یک برنامه‌ی دسکتاپ است و مدیریت رشته‌ی اتصالی جاری آن‌را توسط یک خاصیت Static یا Singleton تعریف شده‌ی در برنامه نیز می‌توان تامین کرد. از این جهت که در هر زمان، تنها یک کاربر در App Domain جاری برنامه‌ی دسکتاپ می‌تواند وجود داشته باشد و Singleton یا Static تعریف شدن اطلاعات رشته‌ی اتصالی، مشکلی را ایجاد نمی‌کند. اما در برنامه‌های وب، چندین کاربر در یک App Domain به سیستم وارد می‌شوند. به همین جهت است که مشاهده می‌کنید در اینجا از تامین کننده‌ی سشن، برای نگهداری اطلاعات رشته‌ی اتصالی جاری کمک گرفته شده‌است.

کلید این سشن نیز در این مثال مساوی CurrentConnectionStringName تعریف شده‌است. بنابراین در حین لاگین موفقیت آمیز کاربر، دو مرحله‌ی زیر باید طی شوند:
 sessionProvider.Store("CurrentConnectionString", "Sample07Context");
uow.SetConnectionString(WebConfigurationManager.ConnectionStrings[_sessionProvider.Get<string>("CurrentConnectionString")].ConnectionString);
ابتدا باید سشن CurrentConnectionStringName به بانک اطلاعاتی انتخابی کاربر تنظیم شود. برای نمونه در این مثال خاص، از نام رشته‌ی اتصالی مشخص شده‌ی در وب کانفیگ برنامه (مثال ابتدای بحث) به نام Sample07Context استفاده شده‌است.
سپس از متد SetConnectionString برای خواندن مقدار نام مشخص شده در سشن CurrentConnectionStringName کمک گرفته‌ایم. هرچند سازنده‌ی کلاس Context برنامه، هر دو حالت استفاده از نام رشته‌ی اتصالی و یا مقدار کامل رشته‌ی اتصالی را پشتیبانی می‌کند، اما خاصیت this.Database.Connection.ConnectionString تنها رشته‌ی کامل اتصالی را می‌پذیرد (بکار رفته در متد SetConnectionString).

تا اینجا کار پویا سازی انتخاب و استفاده از رشته‌ی اتصالی برنامه به پایان می‌رسد. هر زمانیکه قرار است Context برنامه توسط IoC Container نمونه سازی شود، به متد getCurrentConnectionString رجوع کرده و مقدار رشته‌ی اتصالی را از سشن تنظیم شده‌‌ای به نام CurrentConnectionStringName دریافت می‌کند. سپس از مقدار آن جهت مقدار دهی سازنده‌ی دوم کلاس Context استفاده خواهد کرد.


مدیریت migrations خودکار برنامه در حالت استفاده از چندین بانک اطلاعاتی

یکی از مشکلات کار با برنامه‌های چند دیتابیسی، به روز رسانی ساختار تمام بانک‌های اطلاعاتی مورد استفاده، پس از تغییری در ساختار مدل‌های برنامه است. از این جهت که اگر تمام بانک‌های اطلاعاتی به روز نشوند، کوئری‌های جدید برنامه که از خواص و فیلدهای جدید استفاده می‌کنند، دیگر کار نخواهند کرد. پویا سازی اعمال این تغییرات را می‌توان به صورت ذیل انجام داد:
using System;
using System.Data.Entity;
using System.Web;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.IoCConfig;
 
namespace EF_Sample07.WebFormsAppSample
{
    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            initDatabases();
        }
 
        private static void initDatabases()
        {
            // defined in web.config
            string[] connectionStringNames =
            {
                "Sample07Context",
                "Database2012"
            };
 
            foreach (var connectionStringName in connectionStringNames)
            {
                Database.SetInitializer(
                    new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>(connectionStringName));
 
                using (var ctx = new Sample07Context(connectionStringName))
                {
                    ctx.Database.Initialize(force: true);
                }
            }
        }
 
        void Application_EndRequest(object sender, EventArgs e)
        {
            SmObjectFactory.HttpContextDisposeAndClearAll();
        } 
    }
}
نکته‌ی مهمی که در اینجا بکار گرفته شده‌است، مشخص سازی صریح سازنده‌ی شیء MigrateDatabaseToLatestVersion است. به صورت معمول در اکثر برنامه‌های تک دیتابیسی، نیازی به مشخص سازی پارامتر سازنده‌ی این کلاس نیست و در این حالت از سازنده‌ی بدون پارامتر کلاس Context برنامه استفاده خواهد شد. اما اگر سازنده‌ی آن‌را مشخص کنیم، به صورت خودکار از متد سازنده‌ای در کلاس Context استفاده می‌کند که پارامتر رشته‌ی اتصالی را به صورت پویا می‌پذیرد.
در این مثال خاص، متد initDatabases در حین آغاز برنامه فراخوانی شده‌است. منظور این است که اینکار در طول عمر برنامه تنها کافی است یکبار انجام شود و پس از آن است که EF Code first می‌تواند از رشته‌های اتصالی متفاوتی که به آن ارسال می‌شود، بدون مشکل استفاده کند. زیرا اطلاعات نگاشت کلاس‌های مدل برنامه به جداول بانک اطلاعاتی به این ترتیب است که کش می‌شوند و یا بر اساس کلاس Configuration به صورت خودکار به بانک اطلاعاتی اعمال می‌گردند.


کدهای کامل این مثال را که در حقیقت نمونه‌ی بهبود یافته‌ی مطلب «EF Code First #12» است، از اینجا می‌توانید دریافت کنید:
UoW-Sample
مطالب
آشنایی با آزمایش واحد (unit testing) در دات نت، قسمت 6

ادامه آشنایی با NUnit

فرض کنید یک RSS reader نوشته‌اید که فیدهای فارسی و انگلیسی را دریافت می‌کند. به صورت پیش فرض هم مشخص نیست که کدام فید اطلاعات فارسی را ارائه خواهد داد و کدامیک انگلیسی. تشخیص محتوای فارسی و از راست به چپ نشان دادن خودکار مطالب ‌آن‌ها به عهده‌ی برنامه نویس است. بهترین روش برای تشخیص این نوع الگوها، استفاده از regular expressions است.
برای مثال الگوی تشخیص اینکه آیا متن ما حاوی حروف انگلیسی است یا خیر به صورت زیر است:

[a-zA-Z]

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

[\u0600-\u06FF]
[ا-یءئ]

در مورد اینکه بازه یونیکد فارسی استاندارد از کجا شروع می‌شود می‌توان به مقاله‌ی آقای حاج‌لو مراجعه نمود (به صورت خلاصه، بازه مصوب عربی یونیکد، همان بازه یونیکد فارسی نیز می‌باشد. یا به بیان بهتر، بازه‌ی فارسی، جزئی از بازه‌ای است که عربی نام گرفته است). البته بازه‌ی مصوب دیگری هم در مورد ایران باستان وجود دارد به نام old Persian که مورد استفاده‌ی روزمره‌ای ندارد!

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

using System.Text.RegularExpressions;

namespace sample
{
public static class CDetectFarsi
{
public static bool ContainsFarsiData(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, "[ا-یءئ]");
}

public static bool ContainsFarsi(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, @"[\u0600-\u06FF]");
}
}
}

همانطور که ملاحظه می‌کنید در اینجا از extension methods سی شارپ 3 جهت توسعه کلاس پایه string استفاده شد.
اکنون می‌خواهیم بررسی کنیم آیا این الگوها مقصود ما را برآورده می‌سازند یا خیر.

using NUnit.Framework;
using sample;

namespace TestLibrary
{
[TestFixture]
public class TestFarsiClass
{
/*******************************************************************************/
[Test]
public void TestContainsFarsi1()
{
Assert.IsTrue("وحید".ContainsFarsi());
}

[Test]
public void TestContainsFarsi2()
{
Assert.IsTrue("گردان".ContainsFarsi());
}

[Test]
public void TestContainsFarsi3()
{
Assert.IsTrue("سپیدTest".ContainsFarsi());
}

[Test]
public void TestContainsFarsi4()
{
Assert.IsTrue("123بررسی456".ContainsFarsi());
}

[Test]
public void TestContainsFarsi5()
{
Assert.IsFalse("Book".ContainsFarsi());
}

[Test]
public void TestContainsFarsi6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsi());
}

[Test]
public void TestContainsFarsi7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsi()); //Russian hello!
}


/*******************************************************************************/
[Test]
public void TestContainsFarsiData1()
{
Assert.IsTrue("وحید".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData2()
{
Assert.IsTrue("گردان".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData3()
{
Assert.IsTrue("سپیدTest".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData4()
{
Assert.IsTrue("123بررسی456".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData5()
{
Assert.IsFalse("Book".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsiData()); //Russian hello!
}
}
}

در کلاس فوق هر دو متد را با آزمایش‌های واحد مختلفی بررسی کرده‌ایم، انواع و اقسام حروف فارسی، ترکیبی از فارسی و انگلیسی، ترکیبی از فارسی و اعداد انگلیسی، عبارت کاملا انگلیسی، عدد کاملا فارسی و یک عبارت روسی! (در یک کلاس عمومی با متدهای عمومی بدون آرگومان از نوع void)
کلاس CDetectFarsi در برنامه اصلی قرار دارد و کلاس TestFarsiClass در یک پروژه class library دیگر قرار گرفته است (در این مورد و جدا سازی آزمایش‌ها از پروژه اصلی در قسمت‌های قبل بحث شد)
همچنین به ازای هر عبارت Assert یک متد ایجاد گردید تا شکست یکی، سبب اختلال در بررسی سایر موارد نشود.
نتیجه اجرای این آزمایش واحد با استفاده از امکانات مجتمع افزونه ReSharper به صورت زیر است:



منهای یک مورد، سایر آزمایشات ما با موفقیت انجام شده‌اند. موردی که با شکست مواجه شده، بررسی اعداد کاملا فارسی است که البته در الگوی دوم لحاظ نشده است و انتظار هم نمی‌رود که آن‌را به این شکل پشتیبانی کند.
برای اینکه در حین اجرای آزمایشات بعدی این متد در نظر گرفته نشود، می‌توان ویژگی Test آن‌را به صورت زیر تغییر داد:

[Test,Ignore]

نکته: مرسوم شده است که نام متدهای آزمایش واحد به صورت زیر تعریف شوند (با Test شروع شوند، در ادامه نام متدی که بررسی می‌شود ذکر گردد و در آخر ویژگی مورد بررسی عنوان شود):

Test[MethodToBeTested][SomeAttribute]

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

مطالب
تغییرات رمزنگاری اطلاعات در NET Core.
در NET Core. به ظاهر دیگر خبری از کلاس‌هایی مانند RNGCryptoServiceProvider برای تولید اعداد تصادفی و یا SHA256Managed (و تمام کلاس‌های Managed_) برای هش کردن اطلاعات نیست. در ادامه این موارد را بررسی کرده و با معادل‌های آن‌ها در NET Core. آشنا خواهیم شد.


تغییرات الگوریتم‌های هش کردن اطلاعات

با حذف و تغییرنام کلاس‌هایی مانند SHA256Managed (و تمام کلاس‌های Managed_) در NET Core.، معادل کدهایی مانند:
using (var sha256 = new SHA256Managed()) 
{ 
   // Crypto code here... 
}
به صورت ذیل درآمده‌است:
public static string GetHash(string text)
{
  using (var sha256 = SHA256.Create())
  {
   var hashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
   return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower();
  }
}
البته اگر از یک برنامه‌ی ASP.NET Core استفاده می‌کنید، اسمبلی‌های مرتبط آن به صورت پیش فرض به پروژه الحاق شده‌اند. اما اگر می‌خواهید یک کتابخانه‌ی جدید را ایجاد کنید، نیاز است وابستگی ذیل را نیز به فایل project.json آن اضافه نمائید:
 "dependencies": {
  "System.Security.Cryptography.Algorithms": "4.2.0"
},

به علاوه اگر نیاز به محاسبه‌ی هش حاصل از جمع چندین byte array را دارید، در اینجا می‌توان از الگوریتم‌های IncrementalHash به صورت ذیل استفاده کرد:
using (var md5 = IncrementalHash.CreateHash(HashAlgorithmName.MD5))
{
  md5.AppendData(byteArray1, 0, byteArray1.Length);
  md5.AppendData(byteArray2, 0, byteArray2.Length); 
  var hash = md5.GetHashAndReset();
}
این الگوریتم‌ها شامل MD5، SHA1، SHA256، SHA384 و SHA512 می‌شوند.


تولید اعداد تصادفی Thread safe در NET Core.

روش‌های زیادی برای تولید اعداد تصادفی در برنامه‌های دات نت وجود دارند؛ اما مشکل اکثر آن‌ها این است که thread safe نیستند و نباید از آن‌ها در برنامه‌های چند ریسمانی (مانند برنامه‌های وب)، به نحو متداولی استفاده کرد. در این بین تنها کلاسی که thread safe است، کلاس RNGCryptoServiceProvider می‌باشد؛ آن هم با یک شرط:
   private static readonly RNGCryptoServiceProvider Rand = new RNGCryptoServiceProvider();
از کلاس آن باید تنها یک وهله‌ی static readonly در کل برنامه وجود داشته باشد (مطابق مستندات MSDN).
بنابراین اگر در کدهای خود چنین تعریفی را دارید:
 var rand = new RNGCryptoServiceProvider();
اشتباه است و باید اصلاح شود.
در NET Core. این کلاس به طور کامل حذف شده‌است و معادل جدید آن کلاس RandomNumberGenerator است که به صورت ذیل قابل استفاده است (و در عمل تفاوتی بین کدهای آن با کدهای RNGCryptoServiceProvider نیست):
    public interface IRandomNumberProvider
    {
        int Next();
        int Next(int max);
        int Next(int min, int max);
    }

    public class RandomNumberProvider : IRandomNumberProvider
    {
        private readonly RandomNumberGenerator _rand = RandomNumberGenerator.Create();

        public int Next()
        {
            var randb = new byte[4];
            _rand.GetBytes(randb);
            var value = BitConverter.ToInt32(randb, 0);
            if (value < 0) value = -value;
            return value;
        }

        public int Next(int max)
        {
            var randb = new byte[4];
            _rand.GetBytes(randb);
            var value = BitConverter.ToInt32(randb, 0);
            value = value % (max + 1); // % calculates remainder
            if (value < 0) value = -value;
            return value;
        }

        public int Next(int min, int max)
        {
            var value = Next(max - min) + min;
            return value;
        }
    }
در اینجا نیز یک وهله از کلاس RandomNumberGenerator را ایجاد کرده‌ایم، اما استاتیک نیست. علت اینجا است که چون برنامه‌های ASP.NET Core به همراه یک IoC Container توکار هستند، می‌توان این کلاس را با طول عمر singleton معرفی کرد که همان کار را انجام می‌دهد:
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.TryAddSingleton<IRandomNumberProvider, RandomNumberProvider>();
و پس از این تنظیم، می‌توان سرویس IRandomNumberProvider را در تمام قسمت‌های برنامه، با کمک تزریق وابستگی‌های آن در سازنده‌ی کلاس‌ها، استفاده کرد و نکته‌ی مهم آن thread safe بودن آن جهت کاربردهای چند ریسمانی است. بنابراین دیگر در برنامه‌های وب خود از new Random استفاده نکنید.


نیاز به الگوریتم‌های رمزنگاری متقارن قوی و معادل بهتر آن‌ها در ASP.NET Core

ASP.NET Core به همراه یکسری API جدید است به نام data protection APIs که روش‌هایی را برای پیاده سازی بهتر الگوریتم‌های هش کردن اطلاعات و رمزنگاری اطلاعات، ارائه می‌دهند و برای مثال ASP.NET Core Identity و یا حتی Anti forgery token آن، در پشت صحنه دقیقا از همین API برای انجام کارهای رمزنگاری اطلاعات استفاده می‌کنند.
برای مثال اگر بخواهید کتابخانه‌ای را طراحی کرده و در آن از الگوریتم AES استفاده نمائید، نیاز است تنظیم اضافه‌تری را جهت دریافت کلید عملیات نیز اضافه کنید. اما با استفاده از data protection APIs نیازی به اینکار نیست و مدیریت ایجاد، نگهداری و انقضای این کلید به صورت خودکار توسط سیستم data protection انجام می‌شود. کلیدهای این سیستم موقتی هستند و طول عمری محدود دارند. بنابراین باتوجه به این موضوع، روش مناسبی هستند برای تولید توکن‌های Anti forgery و یا تولید محتوای رمزنگاری شده‌ی کوکی‌ها. بنابراین نباید از آن جهت ذخیره سازی اطلاعات ماندگار در بانک‌های اطلاعاتی استفاده کرد.
فعال سازی این سیستم نیازی به تنظیمات اضافه‌تری در ASP.NET Core ندارد و جزو پیش فرض‌های آن است. در کدهای ذیل، نمونه‌ای از استفاده‌ی از این سیستم را ملاحظه می‌کنید:
    public interface IProtectionProvider
    {
        string Decrypt(string inputText);
        string Encrypt(string inputText);
    }

namespace Providers
{
    public class ProtectionProvider : IProtectionProvider
    {
        private readonly IDataProtector _dataProtector;

        public ProtectionProvider(IDataProtectionProvider dataProtectionProvider)
        {
            _dataProtector = dataProtectionProvider.CreateProtector(typeof(ProtectionProvider).FullName);
        }

        public string Decrypt(string inputText)
        {
                var inputBytes = Convert.FromBase64String(inputText);
                var bytes = _dataProtector.Unprotect(inputBytes);
                return Encoding.UTF8.GetString(bytes);
        }

        public string Encrypt(string inputText)
        {
            var inputBytes = Encoding.UTF8.GetBytes(inputText);
            var bytes = _dataProtector.Protect(inputBytes);
            return Convert.ToBase64String(bytes);
        }
    }
}
کار با تزریق IDataProtectionProvider در سازنده‌ی کلاس شروع می‌شود. سرویس آن به صورت پیش فرض توسط ASP.NET Core در اختیار برنامه قرار می‌گیرد و نیازی به تنظیمات اضافه‌تری ندارد. پس از آن باید یک محافظت کننده‌ی جدید را با فراخوانی متد CreateProtector آن ایجاد کرد و در آخر کار با آن به سادگی فراخوانی متدهای Unprotect و Protect است که ملاحظه می‌کنید.
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.TryAddSingleton<IProtectionProvider, ProtectionProvider>();
پس از طراحی این سرویس جدید نیز می‌توان وابستگی‌های آن‌را به نحو فوق به سیستم معرفی کرد و از سرویس IProtectionProvider آن در تمام قسمت‌های برنامه (جهت کارهای کوتاه مدت رمزنگاری اطلاعات) استفاده نمود.

مستندات مفصل این API را در اینجا می‌توانید مطالعه کنید.


معادل الگوریتم Rijndael در NET Core.

همانطور که عنوان شد، طول عمر کلیدهای data protection API محدود است و به همین جهت برای کارهایی چون تولید توکن‌ها، رمزنگاری کوئری استرینگ‌ها و یا کوکی‌های کوتاه مدت، بسیار مناسب است. اما اگر نیاز به ذخیره سازی طولانی مدت اطلاعات رمزنگاری شده وجود داشته باشد، یکی از الگوریتم‌های مناسب اینکار، الگوریتم AES است.
الگوریتم Rijndael نگارش کامل دات نت، اینبار نام اصلی آن یا AES را در NET Core. پیدا کرده‌است و نمونه‌ای از نحوه‌ی استفاده‌ی از آن، جهت رمزنگاری و رمزگشایی اطلاعات، به صورت ذیل است:
public string Decrypt(string inputText, string key, string salt)
{
    var inputBytes = Convert.FromBase64String(inputText);
    var pdb = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(salt));
 
    using (var ms = new MemoryStream())
    {
        var alg = Aes.Create();
 
        alg.Key = pdb.GetBytes(32);
        alg.IV = pdb.GetBytes(16);
 
        using (var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write))
        {
            cs.Write(inputBytes, 0, inputBytes.Length);
        }
        return Encoding.UTF8.GetString(ms.ToArray());
    }
}
 
public string Encrypt(string inputText, string key, string salt)
{
 
    var inputBytes = Encoding.UTF8.GetBytes(inputText);
    var pdb = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(salt));
    using (var ms = new MemoryStream())
    {
        var alg = Aes.Create();
 
        alg.Key = pdb.GetBytes(32);
        alg.IV = pdb.GetBytes(16);
 
        using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write))
        {
            cs.Write(inputBytes, 0, inputBytes.Length);
        }
        return Convert.ToBase64String(ms.ToArray());
    }
}
همانطور که در کدهای فوق نیز مشخص است، این روش نیاز به قید صریح key و salt را دارد. اما روش استفاده‌ی از data protection APIs مدیریت key و salt را خودکار کرده‌است؛ آن هم با طول عمر کوتاه. در این حالت دیگر نیازی نیست تا جفت کلیدی را که احتمالا هیچگاه در طول عمر برنامه تغییری نمی‌کنند، از مصرف کننده‌ی کتابخانه‌ی خود دریافت کنید و یا حتی نام خودتان را به عنوان کلید در کدها به صورت hard coded قرار دهید!
مطالب
ویژگی های کمتر استفاده شده در NET. - بخش اول

ObsoleteAttribute

ObsoleteAttribute بر روی تمامی عناصر یک برنامه بجز assemblies, modules، پارامترها و مقادیر بازگشتی قابل استفاده است. علامتگذاری یک عنصر به عنوان منسوخ شده، به کاربر استفاده کننده اطلاع می‌دهد که این عنصر در نسخه‌های آینده حذف خواهد شد.

با استفاده از پروپرتی Message آن پیامی را به کاربر استفاده کننده نشان خواهد داد و توصیه می‌شود در این پیام یک راه حل نیز ارائه شود.

پروپرتی IsError در صورتی که مقدار آن به true تعیین شده باشد و کامپایلر در صورتی که عنصری که این خصوصیت بر روی آن تعریف شده است، استفاده شده باشد، در پنجره Error List، پیام مربوط به Obsolete را نشان می‌دهد. برای مثال پس از استفاده از کلاس زیر، OrderDetailTotal به صورت warning و CalculateOrderDetailTotal به صورت Error در پنجره Error List نشان داده می‌شود.

public static class ObsoleteExample
{
    // Mark OrderDetailTotal As Obsolete.
    [ObsoleteAttribute("This property (OrderDetailTotal) is obsolete. Use InvoiceTotal instead.", false)]
    public static decimal OrderDetailTotal
    {
        get  {  return 12m; }
    }

    public static decimal InvoiceTotal
    {
        get  {  return 25m;  }
    }

    // Mark CalculateOrderDetailTotal As Obsolete.
    [ObsoleteAttribute("This method is obsolete. Call CalculateInvoiceTotal instead.", true)]
    public static decimal CalculateOrderDetailTotal()
    {
        return 0m;
    }

    public static decimal CalculateInvoiceTotal()
    {
        return 1m;
    }
}

DefaultValueAttribute

DefaultValueAttribute جهت تعیین مقدار پیش فرض یک پروپرتی استفاده می‌شود. شما می‌توانید یک DefaultValueAttribute را با هر مقداری ایجاد کنید. ایجاد مقدار پیش فرض برای یک پروپرتی باعث نمی‌شود که مقداردهی اولیه‌ای به آن انجام گیرد؛ برای این کار نیاز به کدنویسی می‌باشد.
مثال زیر نحوه استفاده و مقداردهی اولیه پروپرتی‌ها را نشان می‌دهد.
public class DefaultValueAttributeTest
{
    public DefaultValueAttributeTest()
    {
        // Use the DefaultValue propety of each property to actually set it, via reflection.
        foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this))
        {
            var attr = prop.Attributes[typeof(DefaultValueAttribute)] as DefaultValueAttribute;
            if (attr != null)
                prop.SetValue(this, attr.Value);
        }
    }

    [DefaultValue(28)]
    public int Age { get; set; }

    [DefaultValue("Vahid")]
    public string FirstName { get; set; }

    [DefaultValue("Mohammad Taheri")]
    public string LastName { get; set; }

    public override string ToString()
    {
        return $"{this.FirstName} {this.LastName} is {this.Age}.";
    }
}

DebuggerBrowsableAttribute 

در صورت استفاده از DebuggerBrowsableAttribute ، شما می‌توانید نحوه نمایش یک عضو را در پنجره متغیرها، در زمان دیباگ، تعیین کنید.
public class DebuggerBrowsableTest
{
    [DebuggerBrowsable(DebuggerBrowsableState.Never)] // عدم نمایش در زمان دیباگ در پنجره متغیرها
    public string FirstName { get; set; }

    [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] // مقدار پیش فرض
    public string LastName { get; set; }

    [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // عدم نمایش در زمان دیباگ در پنجره متغیرها
    public string FullName => FirstName + " " + LastName;

    [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // تنها در زمانی که یک آرایه یا لیست باشد نمایش داده می‌شود
    public string[] FullNameArray => new string[] { FirstName + " " + LastName };
}

 اگر از کد مثال بالا استفاده کنید و با استفاده از کلید F11 به صورت خط به خط دستورات را اجرا کنید، مشاهده خواهید کرد متغیر FirstName و FullName در پنجره Autos نشان داده نخواهد شد.

 

Operator ??

عملگر ??  در صورتی که عملوند سمت چپ آن تهی (null) نباشد، مقدار آن را باز می‌گرداند و در غیر اینصورت مقدار عملوند سمت راست خود را باز می‌گرداند. نوع‌های تهی پذیر (nullable) می‌توانند دارای مقدار و یا به صورت تعریف نشده باشند. عملگر ?? وقتی که یک نوع تهی پذیر به یک نوع غیرتهی پذیر انتساب داده می‌شود، مقدار پیش فرض آن را باز می‌گرداند.

int? x = null;
int y = x ?? -1;
Console.WriteLine("y now equals -1 because x was null => {0}", y);
int i = DefaultValueOperatorTest.GetNullableInt() ?? default(int);
Console.WriteLine("i equals now 0 because GetNullableInt() returned null => {0}", i);
string s = DefaultValueOperatorTest.GetStringValue();
Console.WriteLine("Returns 'Unspecified' because s is null => {0}", s ?? "Unspecified");
مطالب
بررسی واژه کلیدی static

تفاوت بین یک کلاس استاتیک، متدی استاتیک و یا متغیر عضو استاتیک چیست؟ چه زمانی باید از آن‌ها‌ استفاده کرد و لزوم بودن آن‌ها‌ چیست؟
برای پاسخ دادن به این سؤالات باید از نحوه‌ی تقسیم بندی حافظه شروع کرد.
RAM برای هر نوع پروسه‌ای که در آن بارگذاری می‌شود به سه قسمت تقسیم می‌گردد: Stack ، Heap و Static (استاتیک در دات نت در حقیقت قسمتی از Heap است که به آن High Frequency Heap نیز گفته می‌شود).
این قسمت استاتیک حافظه، محل نگهداری متدها و متغیرهای استاتیک است. آن متدها و یا متغیرهایی که نیاز به وهله‌ای از کلاس برای ایجاد ندارند، به صورت استاتیک ایجاد می‌گردند. در سی شارپ از واژه کلیدی static برای معرفی آن‌ها کمک گرفته می‌شود. برای مثال:

class MyClass
{
public static int a;
public static void DoSomething();
}
در این مثال برای فراخوانی متد DoSomething نیازی به ایجاد یک وهله جدید از کلاس MyClass نمی‌باشد و تنها کافی است بنویسیم:

MyClass.DoSomething(); // and not -> new MyClass().DoSomething();
نکته‌ی مهمی که در اینجا وجود دارد این است که متدهای استاتیک تنها قادر به استفاده از متغیرهای استاتیک تعریف شده در سطح کلاس هستند. علت چیست؟
به مثال زیر دقت نمائید:

class MyClass
{
// non-static instance member variable
private int a;
//static member variable
private static int b;
//static method
public static void DoSomething()
{
//this will result in compilation error as “a” has no memory
a = a + 1;
//this works fine since “b” is static
b = b + 1;
}
}
در این مثال اگر متد DoSomething را فراخوانی کنیم، تنها متغیر b تعریف شده، در حافظه حضور داشته (به دلیل استاتیک معرفی شدن) و چون با روش فراخوانی MyClass.DoSomething هنوز وهله‌ای از کلاس مذکور ایجاد نشده، به متغیر a نیز حافظه‌ای اختصاص داده نشده است و نامعین می‌باشد.
بر این اساس کامپایلر نیز از کامپایل شدن این کد جلوگیری کرده و خطای لازم را گوشزد خواهد کرد.

اکنون تعریف یک کلاس به صورت استاتیک چه اثری را خواهد داشت؟
با تعریف یک کلاس به صورت استاتیک مشخص خواهیم کرد که این کلاس تنها حاوی متدها و متغیرهای استاتیک می‌باشد. امکان ایجاد یک وهله از آن‌ها وجود نداشته و نیازی نیز به این امر ندارند. این کلاس‌ها امکان داشتن instance variables را نداشته و به صورت پیش فرض از نوع sealed به حساب خواهند آمد و امکان ارث بری از آن‌ها نیز وجود ندارد. علت این امر هم این است که یک کلاس static هیچ نوع رفتاری را تعریف نمی‌کند.

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

با کلاس‌های استاتیک نیز همانند سایر کلاس‌های یک برنامه توسط JIT compiler رفتار می‌شود، اما با یک تفاوت. کلاس‌های استاتیک فقط یکبار هنگام اولین دسترسی به آن‌ها ساخته شده و در قسمت High Frequency Heap حافظه قرار می‌گیرند. این قسمت از حافظه تا پایان کار برنامه از دست garbage collector‌ در امان است (بر خلاف garbage-collected heap‌ یا object heap که جهت instance classes مورد استفاده قرار می‌گیرد)


نکته:
در برنامه‌های ASP.Net از بکارگیری متغیرهای عمومی استاتیک برحذر باشید (از static fields و نه static methods). این متغیرها بین تمامی کاربران همزمان یک برنامه به اشتراک گذاشته شده و همچنین باید مباحث قفل‌گذاری و امثال آن‌را در محیط‌های چند ریسمانی هنگام کار با آن‌ها رعایت کرد (thread safe نیستند).

مطالب
تهیه گزارشات Crosstab به کمک LINQ - قسمت دوم

اگر به قسمت اول «تهیه گزارشات Crosstab به کمک LINQ» دقت کرده باشید، یک مشکل کوچک دارد و آن هم لزوم مشخص سازی دقیق ستون‌هایی است که می‌خواهیم در گزارش ظاهر شوند. مثلا دقیقا مشخص کنیم که نام واحد چیست یا دقیقا روز را مشخص کنیم. این مورد برای گزارش‌های کوچک مشکلی ندارد؛ ولی اگر همان مثال دوم را در نظر گرفته و بازه را کمی بیشتر کنیم، مثلا یک ماه، آن وقت باید حداقل 30 بار بنویسیم Day1IsPresent تا ... Day30IsPresent و یا اگر بازه‌ی گزارشگیری به اختیار کاربر باشد آن وقت چه باید کرد؟ مثلا یکبار 7 روز پایان ماه را انتخاب کند، یکبار 14 روز را، شاید یک بار هم مثلا 90 روز را مد نظر داشته باشد (تعداد ستون‌ها متغیر باشد یا به عبارتی Dynamic Crosstab نیاز است ایجاد شود).
برای حل این مساله، می‌توان از متد الحاقی زیر از سایت extensionmethod.net کمک گرفت:

using System;
using System.Collections.Generic;
using System.Linq;

namespace PivotExtensions
{
public static class Ext
{
public static Dictionary<TKey1, Dictionary<TKey2, TValue>>
Pivot<TSource, TKey1, TKey2, TValue>
(
this IEnumerable<TSource> source,
Func<TSource, TKey1> key1Selector,
Func<TSource, TKey2> key2Selector,
Func<IEnumerable<TSource>, TValue> aggregate
)
{
return source.GroupBy(key1Selector)
.Select(
key1Group => new
{
Key = key1Group.Key,
Value = key1Group.GroupBy(key2Selector)
.Select(
key2Group => new
{
K = key2Group.Key,
V = aggregate(key2Group)
})
.ToDictionary(e => e.K, o => o.V)
})
.ToDictionary(e => e.Key, o => o.Value);
}
}
}

در این متد:
key1Selector مشخص کننده ستون‌های ثابت و مشخص سمت راست یا چپ (بر اساس جهت صفحه) گزارش است. در سیستم‌های مختلف این ستون‌ها نام‌هایی مانند keyColumn ، leftColumn و Row Heading ممکن است داشته باشند.
key2Selector ستون‌های پویای گزارش را تشکیل می‌دهد. در سایر سیستم‌ها این پارامتر، pivotNameColumn ،VariableColumn ، topField و یا Column Heading هم نامیده می‌شود.
Aggregate در اینجا مشخص می‌کند که مقادیر ستون‌های پویای یاد شده چگونه باید محاسبه شوند.

با توجه به این متد، برای نمونه جهت حل مثال اول قسمت قبل خواهیم داشت:

var list = ExpenseDataSource.ExpensesDataSource();
var pivotList = list.Pivot(
x =>
new
{
x.Date.Year,
x.Date.Month
},
x1 => x1.Department,
x2 => x2.Sum(x => x.Expenses));

با خروجی


فایل LINQPad آن از اینجا قابل دریافت است.


و برای حل مثال دوم قسمت قبل می‌توان نوشت:

var list2 = StudentsStatDataSource.CreateWeeklyReportDataSource();
var lst = list2.Pivot(
x =>
new
{
x.Id,
x.Name
},
x1 => "Day " + x1.Date.Day,
x2 => x2.First().IsPresent);

با خروجی


فایل LINQPad آن از اینجا قابل دریافت است.

مطالب
C# 7 - Local Functions
توابع محلی، امکان تعریف یک تابع را درون یک متد، فراهم می‌کنند. هدف آن‌ها تدارک توابعی کمکی است که به سایر قسمت‌های کلاس مرتبط نمی‌شوند. برای مثال اگر متدی نیاز به کار با یک private method دیگر را دارد و این متد خصوصی در جای دیگری استفاده نمی‌شود، می‌توان جهت بالابردن خوانایی برنامه و سهولت یافتن متد مرتبط، این متد خصوصی را تبدیل به یک تابع محلی، درون همان متد کرد.
static void Main(string[] args)
{
    int Add(int a, int b)
    {
        return a + b;
    }
 
    Console.WriteLine(Add(3, 4)); 
}


بازنویسی کدهای C# 6 با توابع محلی C# 7

کلاس زیر را که بر اساس امکانات C# 6 تهیه شده‌است، در نظر بگیرید:
public class PersonWithPrivateMethod
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        string ageSuffix = GenerateAgeSuffix(Age);
        return $"{Name} is {Age} year{ageSuffix} old";
    }

    private string GenerateAgeSuffix(int age)
    {
        return age > 1 ? "s" : "";
    }
}
متد خصوصی همین کلاس را توسط Func delegates می‌توان به صورت ذیل خلاصه کرد (باز هم بر اساس امکانات C# 6):
public class PersonWithLocalFuncDelegate
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        Func<int, string> generateAgeSuffix = age => age > 1 ? "s" : "";
        return $"{Name} is {Age} year{generateAgeSuffix(Age)} old";
    }
}
به این ترتیب نیاز به تعریف یک متد private دیگر کمتر خواهد شد.
اکنون در C# 7 می‌توان این Func delegate را به نحو ذیل تبدیل به یک local function کرد:
public class PersonWithLocalFunction
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"{Name} is {Age} year{GenerateAgeSuffix(Age)} old";
        // Define a local function:
        string GenerateAgeSuffix(int age)
        {
            return age > 1 ? "s" : "";
        }
    }
}


مزیت کار با local functions نسبت به Func delegates محلی

در قطعه کد فوق، کار انجام شده صرفا استفاده‌ی از یک Syntax جدید نیست؛ بلکه از لحاظ کارآیی نیز سربار کمتری را به همراه دارد. زمانیکه Func Delegates تعریف می‌شوند، کار ایجاد یک anonymous type، وهله سازی و فراخوانی آن‌ها توسط کامپایلر صورت می‌گیرد. اما حین کار با توابع محلی، کامپایلر با یک متد استاندارد سروکار دارد و هیچکدام از مراحل یاد شده و سربارهای آن‌ها رخ نمی‌دهند (هیچگونه GC allocation ایی نخواهیم داشت). به علاوه اینبار کامپایلر فرصت in-line تعریف کردن متد را به نحو بهتری یافته و به این ترتیب کار سوئیچ بین متدهای مختلف کاهش پیدا می‌کند که در نهایت سرعت برنامه را افزایش می‌دهند.


میدان دید توابع محلی

البته با توجه به اینکه متد مثال فوق محلی است، به تمام متغیرها و پارامترهای متد دربرگیرنده‌ی آن نیز دسترسی دارد. بنابراین می‌توان پارامتر int age آن‌را نیز حذف کرد:
public class PersonWithLocalFunctionEnclosing
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"{Name} is {Age} year{GenerateAgeSuffix()} old";
        // Define a local function:
        string GenerateAgeSuffix()
        {
            return Age > 1 ? "s" : "";
        }
    }
}
به همین جهت نمی‌توانید داخل یک تابع محلی، متغیری را تعریف کنید که هم‌نام یکی از متغیرها یا پارامترهای متد دربرگیرنده‌ی آن باشد.


خلاصه نویسی توابع محلی به کمک expression bodies

می‌توان این متد محلی را به صورت یک expression body ارائه شده‌ی در C# 6 نیز بیان کرد:
public class PersonWithLocalFunctionExpressionBodied
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"{Name} is {Age} year{GenerateAgeSuffix(Age)} old";
        // Define a local function:
        string GenerateAgeSuffix(int age) => age > 1 ? "s" : "";
    }
}


روش ارسال یک local function به متدی دیگر

امکان ارسال یک تابع محلی به صورت یک Func delegate به متدی دیگر نیز وجود دارد:
public class LocalFunctionsTest
{
    public void PassAnonFunctionToMethod()
    {
        var p = new SimplePerson
        {
            Name = "Name1",
            Age = 42
        };
        OutputSimplePerson(p, GenerateAgeSuffix);
        string GenerateAgeSuffix(int age) => age > 1 ? "s" : "";
    }
 
    private void OutputSimplePerson(SimplePerson person, Func<int, string> suffixFunction)
    {
        Output.WriteLine(
        $"{person.Name} is {person.Age} year{suffixFunction(person.Age)} old");
    }
}
در این مثال GenerateAgeSuffix یک Local function است که به صورت expression body نیز بیان شده‌است. برای ارسال آن به متد OutputSimplePerson، پارامتر دریافتی آن باید به صورت Func تعریف شود.
مطالب
ردیابی تغییرات در Entity Framework، بخش اول
همان طور که می‌دانید، Entity Framework  تغییراتی را که بر روی اشیا انجام می‌دهید، ردیابی می‌کند. بدیهی است که EF از طریق ردیابی این تغییرات است که می‌تواند تغییرات انجام شده را شناسایی کند و آن‌ها را در مواقع مورد نیاز مانند ذخیره‌ی تغییرات  (DbContext.SaveChanges)، بر روی پایگاه داده اعمال  کند. شما می‌توانید به اطلاعات این ردیاب تغییر و اعمال مرتبط به آن از طریق ویژگی  DbContext.ChangeTracker دسترسی پیدا کنید. 
در این مقاله بیشتر سعی به بررسی مفاهیم ردیابی و روش هایی که EF برای ردیابی تغییرات استفاده می‌کند، بسنده می‌کنم و بررسی  API‌های مختلف آن را به مقاله ای دیگر موکول می‌کنم.
به طور کلی EF از دو روش برای ردیابی تغییرات رخ داده شده در اشیا استفاده می‌کند:
1) ردیابی تغییر عکس فوری! (Snapshot change tracking) 
2) پروکسی‌های ردیابی تغییر  (Change tracking proxies)

 ردیابی تغییر عکس فوری

به نظر من، اسم مناسبی برای این روش انتخاب کرده اند و دقیقا بیان گر کاری است که  EF انجام می‌دهد. در حالت عادی کلاس‌های دامین ما یا همان کلاس‌های POCO، هیچ منطق و کدی را برای مطلع ساختن EF از تغییراتی که در آن‌ها رخ می‌دهد پیاده سازی نکرده اند. چون هیچ راهی برای EF، برای مطلع شدن از تغییرات رخ داده وجود ندارد، EF راه جالبی را بر می‌گزیند. EF هر گاه شیئی را می‌بیند از مقادیر ویژگی‌های آن یک عکس فوری می‌گیرد! و آن‌ها را در حافظه ذخیره می‌کند.این عمل هنگامی که یک شی از پرس و جو (query) حاصل می‌شود، و یا شیئی را به DbSet اضافه می‌کنیم رخ می‌دهد.
زمانی که EF می‌خواهد بفهمد که چه تغییراتی رخ داده است، مقادیر کنونی موجود در کلیه اشیا را اسکن می‌کند و با مقادیری که در عکس فوری ذخیره کرده است مقایسه می‌کند و متوجه تغییرات رخ داده می‌شود. این فرآیند اسکن کردن کلیه اشیا زمانی رخ می‌دهد که متد DetectChanges ویژگی DbSet.ChangeTracker صدا زده شود.

 پروکسی‌های ردیابی تغییر

پروکسی‌های ردیابی تغییر، مکانیزم دیگری برای ردیابی تغییرات EF است و به EF این اجازه را می‌دهد تا از تغییرات رخ داده، مطلع شود.
اگر به یاد داشته باشید در مباحث Lazy loading نیز از واژه پروکسی‌های پویا استفاده شد. پروکسی‌های ردیابی تغییر نیز با استفاده از همان مکانیزم کار می‌کنند و علاوه بر فراهم کردن Lazy loading ،این امکان را می‌دهند تا تغییرات را به Context انتقال دهند.
برای استفاده از پروکسی‌های ردیابی تغییر، شما باید ساختار کلاس‌های خود را به گونه ای تغییر دهید، تا EF بتواند در زمان اجرا، نوع پویایی را که هریک، از کلاس‌های POCO شما مشتق می‌شوند ایجاد کند، و تک تک ویژگی‌های آن‌ها را تحریف (override) کند.
این نوع پویا که به عنوان پروکسی پویا نیز شناخته می‌شود، منطقی را در ویژگی‌های تحریف شده شامل می‌شود، تا EF را از تغییرات صورت گرفته در ویژگی هایش مطلع سازد.
 برای بیان ادامه‌ی مطلب، من مدل یک دفترچه تلفن ساده را به شرح زیر در نظر گرفتم که روابط مهم و اساسی در آن در نظر گرفته شده است. 
namespace EntitySample1.DomainClasses
{
    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
        public virtual PersonInfo PersonInfo { get; set; }
        public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
        public virtual ICollection<Address> Addresses { get; set; }
    }
}

namespace EntitySample1.DomainClasses
{
    public class PersonInfo
    {
        public int Id { get; set; }
        public string Note { get; set; }
        public string Major { get; set; }
    }
}

namespace EntitySample1.DomainClasses
{
    public enum PhoneType
    {
        Home,
        Mobile,
        Work
    }

    public class PhoneNumber
    {
        public int Id { get; set; }
        public string Number { get; set; }
        public PhoneType PhoneType { get; set; }
        public virtual Person Person { get; set; }
    }
}

namespace EntitySample1.DomainClasses
{
    public class Address
    {
        public int Id { get; set; }
        public string City { get; set; }
        public string Street { get; set; }
        public virtual ICollection<Person> Persons { get; set; }
    }
}
طبق کلاس‌های فوق ، ما تعدادی شخص ، اطلاعات شخص ، شماره تلفن و آدرس داریم. رابطه‌ی بین شخص و اطلاعات آن شخص یک به یک، شخص و آدرس  چند به چند  و شخص با شماره تلفن یک به چند است. همچنین به این نکته توجه داشته باشید که  کلیه کلاس‌های فوق به صورت public تعریف، و کلیه خواص راهبری (navigation properties) به صورت virtual تعریف شده اند. دلیل این کار هم این است که این دو مورد، جز الزامات، برای فعال سازی Lazy loading هستند. 
تعریف کلاس context نیز به شکل زیر است:

namespace EntitySample1.DataLayer
{
    public class PhoneBookDbContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }
        public DbSet<PhoneNumber> PhoneNumbers { get; set; }
        public DbSet<Address> Addresses { get; set; }

    }
}

استفاده از ردیابی تغییر عکس فوری

ردیابی تغییر عکس فوری، وابسته به این است که EF بفهمد، چه زمانی تغییرات رخ داده است. رفتار پیش فرض DbContext API ، این هست که به صورت خودکار بازرسی لازم را در نتیجه‌ی رخداد‌های DbContext انجام دهد. DetectChanges تنها اطلاعات مدیریت حالت context، که وظیفه‌ی انعکاس تغییرات صورت گرفته به پایگاه داده را دارد، به روز نمی‌کند، بلکه اصلاح رابطه(ralationship) ترکیبی از خواص راهبری مرجع ، مجموعه ای  و کلید‌های خارجی را انجام می‌دهد. این خیلی مهم خواهد بود که درک روشنی داشته باشیم از این که چگونه و چه زمانی تغییرات تشخیص داده می‌شوند،چه چیزی باید از آن انتظار داشته باشیم و چگونه کنترلش کنیم.

چه زمانی تشخیص خودکار تغییرات اجرا می‌شود؟

متد DetectChanges کلاس ObjectContext، از EF نسخه‌ی 4 به عنوان بخشی از الگوی ردیابی تغییر عکس فوری اشیای POCO ،در دسترس بوده است. تفاوتی که در مورد DataContext.ChangeTracker.DetectChanges( در حقیقت ObjectContext.DetectChanges فراخوانی می‌شود) وجود دارد این است که، رویداد‌های خیلی بیشتری وجود دارند که به صورت خودکار DetectChanges را فراخوانی می‌کنند.
لیستی از متدهایی که باعث انجام عمل تشخیص تغییرات (DetectChanges)، می‌شوند را  در ادامه مشاهده می‌کنید:
• DbSet.Add
• DbSet.Find
• DbSet.Remove
• DbSet.Local
• DbSet.SaveChanges
• فراخوانی Linq Query از DbSet
• DbSet.Attach
• DbContext.GetValidationErrors
• DbContext.Entry
• DbChangeTracker.Entries

 
کنترل زمان فراخوانی DetectChanges

بیشترین زمانی که EF احتیاج به فهمیدن تغییرات دارد، در زمان SaveChanges است، اما حالت‌های زیاد دیگری نیز هست. برای مثال، اگر ما از ردیاب تغییرات، درخواست وضعیت فعلی یک شی را بکنیم،EF احتیاج به اسکن کردن و بررسی تغییرات رخ داده را دارد. همچنین وضعیتی را در نظر بگیرید که شما از پایگاه داده یک شماره تلفن را واکشی می‌کنید و سپس آن را به مجموعه شماره تلفن‌های یک شخص جدید اضافه می‌کنید.آن شماره تلفن اکنون تغییر کرده است، چرا که انتساب آن به یک شخص جدید،خاصیت PersonId آن را تغییر داده است. ولی EF برای اینکه بفهمد تغییر رخ داده است(یا حتی نداده است) ، احتیاج به اسکن کردن همه‌ی اشیا Person دارد.
بیشتر عملیاتی که بر روی DbContext API انجام می‌دهید، موجب فراخوانی DetectChanges می‌شود. در بیشتر موارد DetectChanges به اندازه کافی سریع هست تا باعث ایجاد مشکل کارایی نشود. با این حال ممکن است ، شما تعداد خیلی زیادی اشیا در حافظه داشته باشید، و یا تعداد زیادی عملیات در DbContext ، در مدت خیلی کوتاهی انجام دهید، رفتار تشخیص خودکار تغییرات ممکن است، باعث نگرانی‌های کارایی شود. خوشبختانه گزینه ای برای خاموش کردن رفتار تشخیص خودکار تغییرات وجود دارد و هر زمانی که می‌دانید لازم است، می‌توانید آن را به صورت دستی فراخوانی کنید.
 EF بر مبنای این فرض ساخته شده است که شما ، در صورتی که در فراخوانی آخرین API، موجودیتی تغییر پیدا کرده است، قبل از فراخوانی API جدید، باید DetectChanges صدا زده شود. این شامل فراخوانی DetectChanges، قبل از اجرای هر query نیز می‌شود.اگر این عمل ناموفق یا نابجا انجام شود،ممکن است عواقب غیر منتظره ای در بر داشته باشد. DbContext انجام این وظیفه را بر عهده گرفته است و به همین دلیل به طور پیش فرض تشخیص تغییرات خودکار آن فعال است.

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

برای خاموش یا روشن کردن تشخیص خودکار تغییرات، باید متغیر بولین DbContext.Configuration.AutoDetectChangesEnabled را تنظیم کنید.
در مثال زیر، ما در متد ManualDetectChanges، تشخیص خودکار تغییرات را خاموش کرده ایم و تاثیرات آن را بررسی کرده ایم.

        private static void ManualDetectChanges()
        {
            using (var context = new PhoneBookDbContext())
            {
                context.Configuration.AutoDetectChangesEnabled = false; // turn off Auto Detect Changes

                var p1 = context.Persons.Single(p => p.FirstName == "joe");

                p1.LastName = "Brown";

                Console.WriteLine("Before DetectChanges: {0}", context.Entry(p1).State);

                context.ChangeTracker.DetectChanges(); // call detect changes manually

                Console.WriteLine("After DetectChanges: {0}", context.Entry(p1).State);
            }
        }

در کدهای بالا ابتدا تشخیص خودکار تغییرات را خاموش کرده ایم و سپس یک شخص با نام joe را از دیتابیس فراخواندیم و سپس نام خانوادگی آن را به Brown تغییر دادیم. سپس در خط بعد، وضعیت فعلی موجودیت p1 را از context جاری پرسیدیم. در خط بعدی، DetectChanges را به صورت دستی صدا زده ایم و دوباره همان پروسه را برای به دست آوردن وضیعت شی p1، انجام داده ایم. همان طور که می‌بینید ، برای به دست آوردن وضعیت فعلی شی مورد نظر از متد Entry متعلق به ChangeTracker API استفاده می‌کنیم، که در آینده مفصل در مورد آن بحث خواهد شد. اگر شما متد Main را با صدا زدن ManualDetectChanges ویرایش کنید ، خروجی زیر را مشاهده خواهید کرد:

Before DetectChanges: Unchanged
After DetectChanges: Modified

همان طور که انتظار می‌رفت، به دلیل خاموش کردن تشخیص خودکار تغییرات، context قادر به تشخیص تغییرات صورت گرفته در شی p1 نیست، تا زمانی که متد DetectChanges را به صورت دستی صدا بزنیم. دلیل این که در دفعه اول، ما نتیجه‌ی غلطی مشاهده می‌کنیم، این است که ما قانون را نقض کرده ایم و قبل از صدا زدن هر API ، متد DetectChanges را صدا نزده ایم. خوشبختانه چون ما در اینجا وضعیت یک شی را بررسی کردیم، با عوارض جانبی آن روبرو نشدیم.

نکته: به این نکته توجه داشته باشید که متد Entry به صورت خودکار، DetectChanges را فراخوانی می‌کند. برای اینکه دانسته بخواهیم این رفتار را غیر فعال کنیم، باید AutoDetectChangesEnabled را غیر فعال کنیم.
در مثال فوق ،خاموش کردن تشخیص خودکار تغییرات، برای ما مزیتی به همراه نداشت و حتی ممکن بود برای ما دردسر ساز شود. ولی حالتی را  در نظر بگیرید که ما یک سری API را فراخوانی می‌کنیم ،بدون این که در این بین ،در حالت اشیا تغییری ایجاد کنیم.در نتیجه می‌توانیم از فراخوانی‌های بی جهت DetectChanges جلوگیری کنیم.
 
در متد AddMultiplePersons مثال بعدی، این کار را نشان داده ام:

        private static void AddMultiplePerson()
        {
            using (var context = new PhoneBookDbContext())
            {
                context.Configuration.AutoDetectChangesEnabled = false;

                context.Persons.Add(new Person
                    {
                        FirstName = "brad",
                        LastName = "watson",
                        BirthDate = new DateTime(1990, 6, 8)
                    });

                context.Persons.Add(new Person
                {
                    FirstName = "david",
                    LastName = "brown",
                    BirthDate = new DateTime(1990, 6, 8)
                });

                context.Persons.Add(new Person
                {
                    FirstName = "will",
                    LastName = "smith",
                    BirthDate = new DateTime(1990, 6, 8)
                });

                context.SaveChanges();

            }
        }
در مثال بالا ما از فراخوانی چهار DetectChanges غیر ضروری که شامل DbSet.Add و SaveChanges می‌شود، جلوگیری کرده ایم.

استفاده از DetectChanges برای فراخوانی اصلاح رابطه


DetectChanges همچنین مسئولیت انجام اصلاح رابطه ، برای هر رابطه ای که تشخیص دهد تغییر کرده است را دارد.اگر شما بعضی از روابط را تغییر دادید و مایل بودید تا همه‌ی خواص راهبری و خواص کلید خارجی را منطبق کنید، DetectChanges این کار را برای شما انجام می‌دهد. این قابلیت می‌تواند برای سناریوهای data-binding که در آن ممکن است در رابط کاربری(UI) یکی از خواص راهبری (یا حتی یک کلید خارجی) تغییر کند، و شما بخواهید که خواص دیگری این رابطه به روز شوند و تغییرات را نشان دهند، مفید واقع شود.
متد DetectRelationshipChanges در مثال زیر از DetectChanges برای انجام اصلاح رابطه استفاده می‌کند.
 
        private static void DetectRelationshipChanges()
        {
            using (var context = new PhoneBookDbContext())
            {

                var phone1 = context.PhoneNumbers.Single(x => x.Number == "09351234567");

                var person1 = context.Persons.Single(x => x.FirstName == "will");

                person1.PhoneNumbers.Add(phone1);

                Console.WriteLine("Before DetectChanges: {0}", phone1.Person.FirstName);

                context.ChangeTracker.DetectChanges(); // ralationships fix-up

                Console.WriteLine("After DetectChanges: {0}", phone1.Person.FirstName);
            }
        }

در اینجا ابتدا ما شماره تلفنی را از دیتابیس لود می‌کنیم. سپس شخص دیگری را نیز با نام will از دیتابیس می‌خوانیم. قصد داریم شماره تلفن خوانده شده را به این شخص نسبت دهیم و مجموعه شماره تلفن‌های وی اضافه کنیم و ما این کار را با افزودن phone1 به مجموعه شماره تلفن‌های person1 انجام داده ایم. چون ما از اشیای POCO استفاده کرده ایم،EF  نمی‌فهمد که ما این تغییر را ایجاد کرده ایم و در نتیجه کلید خارجی PersonId شی phone1 را اصلاح نمی‌کند. ما می‌توانیم تا زمانی صبر کنیم تا متدی مثل SaveChanges، متد DetectChanges را فراخوانی کند،ولی اگر بخواهیم این عمل در همان لحظه انجام شود، می‌توانیم DetectChanges را دستی صدا بزنیم.

اگر ما متد Main را با اضافه کردن فرخوانی DetectRealtionShipsChanges تغییر بدهیم و آن را اجرا کنیم، نتیجه زیر را مشاهده می‌کنید:

Before DetectChanges: david
After DetectChanges: will

تا قبل از فراخوانی تشخیص تغییرات(DetectChanegs)، هنوز phone1 منتسب به شخص قدیمی(david) بوده، ولی پس از فراخوانی DetectChanges ، اصلاح رابطه رخ داده و همه  چیز با یکدیگر منطبق می‌شوند.

فعال سازی و کار با پروکسی‌های ردیابی تغییر

اگر پروفایلر کارایی شما، فراخوانی‌های بیش از اندازه DetectChnages را به عنوان یک مشکل شناسایی کند، و یا شما ترجیح می‌دهید که اصلاح رابطه به صورت بلادرنگ صورت گیرد ، ردیابی تغییر پروکسی‌های پویا، به عنوان گزینه ای دیگر مطرح می‌شود.فقط با چند تغییر کوچک در کلاس‌های POCO، EF قادر به ساخت پروکسی‌های پویا خواهد بود.پروکسی‌های ردیابی تغییر به EF اجازه ردیابی تغییرات در همان لحظه ای که ما تغییری در اشیای خود می‌دهیم را می‌دهند و همچنین امکان انجام اصلاح رابطه را در هر زمانی که تغییرات روابط را تشخیص دهد، دارد.
برای اینکه پروکسی ردیابی تغییر بتواند ساخته شود، باید قوانین زیر رعایت شود:
 
• کلاس باید public باشد و seald نباشد.
• همه‌ی خواص(properties) باید virtual تعریف شوند.
• همه‌ی خواص باید getter و setter با سطح دسترسی public داشته باشند.
• همه‌ی خواص راهبری مجموعه ای باید نوعشان، از نوع ICollection<T> تعریف شوند.


کلاس Person مثال خود را به گونه ای بازنویسی کرده ایم که تمام قوانین فوق را پیاده سازی کرده باشد.

نکته: توجه داشته باشید که ما دیگر در داخل سازنده کلاس ،کدی نمی‌نویسیم و منطقی  که باعث نمونه سازی اولیه خواص راهبری می‌شدند، را پیاده سازی نمی‌کنیم. این پروکسی ردیاب تغییر، همه‌ی خواص راهبری مجموعه ای را تحریف کرده و ار نوع مجموعه ای مخصوص خود(EntityCollection<T>) استفاده می‌کند. این نوع مجموعه ای، هر تغییری که در این مجموعه صورت می‌گیرد را زیر نظر گرفته و به ردیاب تغییر گزارش می‌دهد. اگر تلاش کنید تا نوع دیگری مانند List<T> که معمولا در سازنده کلاس از آن استفاده می‌کردیم را به آن انتساب دهیم، پروکسی، استثنایی را پرتاب می‌کند.

namespace EntitySample1.DomainClasses
{
    public class Person
    {
        public virtual int Id { get; set; }
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual DateTime BirthDate { get; set; }
        public virtual PersonInfo PersonInfo { get; set; }
        public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
        public virtual ICollection<Address> Addresses { get; set; }
    }
}
همان طور که در مباحث مربوط به Lazy loading نیز مشاهده کردید،EF زمانی پروکسی‌های پویا را برای یک کلاس ایجاد می‌کند که یک یا چند خاصیت راهبری آن با virtual علامت گذاری شده باشند.آن پروکسی‌ها که از کلاس مورد نظر، مشتق شده اند، به خواص راهبری virtual امکان می‌دهند تا به صورت lazy لود شوند.پروکسی‌های ردیابی تغییر نیز به همان شکل در زمان اجرا ایجاد می‌شوند، با این تفاوت که این پروکسی ها، امکانات بیشتری دارند.
با این که احتیاجات رسیدن به پروکسی‌های ردیابی تغییر خیلی ساده هستند، اما ساده‌تر از آن ها، فراموش کردن یکی از آن هاست.حتی از این هم ساده‌تر می‌شود که در آینده تغییری در آن کلاس‌ها ایجاد کنید و ناخواسته یکی از آن قوانین را نقض کنید.به این خاطر، فکر خوبیست که یک آزمون واحد نیز اضافه کنیم تا مطمئن شویم که EF توانسته، پروکسی ردیابی تغییر را ایجاد کند یا نه.
در مثال زیر یک متد نوشته شده که این مورد را مورد آزمایش قرار می‌دهد. همچنین فراموش نکنید که فضای نام System.Data.Object.DataClasses را به using‌های خود اضافه کنید.
 
        private static void TestForChangeTrackingProxy()
        {
            using (var context = new PhoneBookDbContext())
            {
                var person = context.Persons.First();
                var isProxy = person is IEntityWithChangeTracker;
                Console.WriteLine("person is a proxy: {0}", isProxy);
            }
        }
زمانی که EF ، پروکسی پویا برای ردیابی تغییر ایجاد می‌کند، اینترفیس IentityWithChangeTrackerرا پیاده سازی  خواهد کرد.متد تست در مثال بالا، نمونه ای از Person را با دریافت آن از دیتابیس ایجاد می‌کند و سپس آن را با اینترفیس ذکر شده چک می‌کند تا مطمئن شود که Person ، توسط پروکسی ردیابی تغییر احاطه شده است. این نکته را نیز به یاد داشته باشید که چک کردن این که EF ، کلاس پروکسی ای که از کلاس ما مشتق شده است ایجاد کرده است یا نه،کفایت نمی‌کند، چرا که پروکسی‌های Lazy Loading نیز چنین کاری انجام می‌دهند. در حقیقت آن چیزی که سبب می‌شود EF به تغییرات صورت گرفته به صورت بلادرنگ گوش دهد،حضور IEntityWithChangeTracker است.
اکنون متد ManualDetectChanges را که کمی بالاتر بررسی کرده ایم را در نظر بگیرید و کد context.ChangeTracker.DetectChanges آن را حذف کنید و بار دیگر آن را فرا بخوانید  و نتیجه را مشاهده کنید:
 
Before DetectChanges: Modified
After DetectChanges: Modified
این دفعه،EF از تغییرات صورت گرفته آگاه است،حال چه DetectChanges فراخوانده شود یا نشود.

اکنون متد DetectRelationshipChanges را ویرایش کرده و برنامه را اجرا کنید:

Before DetectChanges: will
After DetectChanges: will
این بار می‌بینیم که EF، تغییر رابطه را تشخیص داده و اصلاح رابطه را بدون فراخوانی DetectChanges انجام داده است.

نکته: زمانی که شما از پروکسی‌های ردیابی تغییر استفاده می‌کنید،احتیاجی به غیرفعال کردن تشخیص خودکار تغییرات نیست. DetectChanges برای همه اشیایی که تغییرات را به صورت بلادرنگ گزارش می‌دهند،فرآیند تشخیص تغییرات را انجام نمی‌دهد. بنابراین فعال سازی پروکسی‌های ردیابی تغییر،برای رسیدن به مزایای کارایی بالا در هنگام عدم استفاده از DetectChanges کافی است. در حقیقت زمانی که EF، یک پروکسی ردیابی پیدا می‌کند، از مقادیر خاصیت ها، عکس فوری نمی‌گیرد. همچنین DetectChanges این را نیز می‌داند که نباید تغییرات موجودیت هایی که عکسی از مقادیر اصلی آنها ندارد را اسکن کند.

تذکر: اگر شما موجودیت هایی داشته باشید که شامل انواع پیچیده(Complex Types) می‌شوند،EF هنوز هم از ردیابی تغییر عکس فوری، برای خواص موجود در نوع پیچیده استفاده می‌کند، و از این جهت لازم است کهEF، برای نمونه‌ی نوع پیچیده، پروکسی ایجاد نمی‌کند.شما هنوز هم، تشخیص خودکار تغییرات خواصی که مستقیما درون آن موجودیت(Entity) تعریف شده اند را دارید، ولی تغییرات رخ داده درون نوع پیچیده، فقط از طریق DetectChanges قابل تشخیص است.

چگونگی اطمینان از اینکه نمونه‌های جدید ، پروکسی‌ها را دریافت خواهند کرد

EF به صورت خودکار برای نتایج حاصل از کوئری هایی که شما اجرا می‌کنید، پروکسی‌ها را ایجاد می‌کند. با این حال اگر شما فقط از سازنده‌ی کلاس POCO خود برای ایجاد نمونه‌ی جدید استفاده کنید،دیگر پروکسی‌ها ایجاد نخواهند شد.بدین منظور برای دریافت پروکسی ها، شما باید از متد DbSet.Create برای دریافت نمونه‌های جدید آن موجودیت استفاده کنید.
نکته: اگر شما، پروکسی‌های ردیابی تغییر را برای موجودیتی از مدلتان فعال کرده باشید،هنوز هم می‌توانید،نمونه‌های فاقد پروکسی آن موجودیت را ایجاد و بیافزایید.خوشبختانه EF با موجودیت‌های پروکسی و غیر پروکسی در همان مجموعه(set) کار می‌کند.شما باید آگاه باشید که ردیابی خودکار تغییرات و یا اصلاح رابطه، برای نمونه هایی که پروکسی هایی ردیابی تغییر نیستند، قابل استفاده نیستند.داشتن مخلوطی از نمونه‌های پروکسی و غیر پروکسی در همان مجموعه، می‌تواند گیج کننده باشد.بنابر این عموما توصیه می‌شود که برای ایجاد نمونه‌های جدید از DbSet.Create استفاده کنید، تا همه‌ی موجودیت‌های موجود در مجموعه، پروکسی‌های ردیابی تغییر باشند.
متد CreateNewProxies را به برنامه‌ی خود اضافه کرده و آن را اجرا کنید.

        private static void CreateNewProxies()
        {
            using (var context = new PhoneBookDbContext())
            {
                var phoneNumber = new PhoneNumber { Number = "987" };

                var davidPersonProxy = context.Persons.Create();
                davidPersonProxy.FirstName = "david";
                davidPersonProxy.PhoneNumbers.Add(phoneNumber);

                Console.WriteLine(phoneNumber.Person.FirstName);
            }
        }

خروجی مثال فوق david خواهد بود.همان طور که می‌بینید با استفاده از context.Persons.Create، نمونه‌ی ساخته شده، دیگر شی POCO نیست، بلکه davidPersonProxy، از جنس پروکسی ردیابی تغییر است و تغییرات آن به طور خودکار ردیابی شده و رابطه آن نیز به صورت خودکار اصلاح  می‌شود.در اینجا نیز با افزودن phoneNumber به شماره تلفن‌های davidPersonProxy، به طور خودکار رابطه‌ی بین phoneNumber و davidPersonPeroxy بر قرار شده است. همان طور که می‌دانید این عملیات بدون استفاده از پروکسی‌های ردیابی تغییرات امکان پذیر نیست و موجب بروز خطا می‌شود.


ایجاد نمونه‌های پروکسی برای انواع مشتق شده

اورلود جنریک دیگری برای DbSet.Create وجود  دارد که برای نمونه سازی کلاس‌های مشتق شده در مجموعه ما استفاده می‌شود .برای مثال، فراخوانی Create بر روی مجموعه‌ی Persons ،نمونه ای از کلاس Person را  بر می‌گرداند.ولی ممکن است کلاس هایی در مجموعه‌ی Persons وجود داشته باشند، که از آن مشتق شده باشند، مانند Student. برای دریافت نمونه‌ی پروکسی Student، از اورلود جنریک Create استفاده می‌کنیم.

var newStudent = context.Persons.Create<Student>();
واکشی موجودیت‌ها بدون ردیابی تغییرات

تا به این جای کار باید متوجه شده باشید که ردیابی تغییرات، فرآیندی ساده و بدیهی نیست و مقداری سربار در کار است. در بعضی از بخش‌های برنامه تان، احتمالا داده‌ها را به صورت فقط خواندنی در اختیار کاربران قرار می‌دهید و چون اطلاعات هیچ وقت تغییر نمی‌کنند، شما می‌خواهید که سربار ناشی از ردیابی تغییرات را حذف کنید.
خوشبختانه EF شامل متد AsNoTracking است که می‌توان از آن برای اجرای کوئری‌های بدون ردیابی استفاده کرد.یک کوئری بدون ردیابی، یک کوئری ساده هست که نتایج آن توسط context برای تشخیص تغییرات ردیابی نخواهد شد.

متد PrintPersonsWithoutChangeTracking را به برنامه اضافه کنید و آن را اجرا کنید: 
 
        private static void PrintPersonsWithoutChangeTracking()
        {
            using (var context = new PhoneBookDbContext())
            {
                var persons = context.Persons.AsNoTracking().ToList();

                foreach (var person in persons)
                {
                    Console.WriteLine(person.FirstName);
                }

            }
        }
در مثال بالا از متد AsNoTracking برای گرفتن کوئری فاقد ردیابی استفاده کردیم تا محتویات مجموعه Persons را دریافت کنیم. در نهایت با یک حلقه‌ی foreach، نتایج را بر روی کنسول به نمایش در آوردیم.به دلیل اینکه، این یک کوئری بدون ردیابی هست، context دیگر تغییراتی که روی Persons رخ می‌دهد را ردیابی نمی‌کند.در نتیجه اگر شما یکی از خواص یکی از Persons را تغییردهید و SaveChanges را صدا بزنید، تغییرات به دیتابیس ارسال نمی‌شوند.

نکته: واکشی داده‌ها بدون ردیابی تغییرات،معمولا وقتی باعث افزایش قابل توجه کارایی می‌شود که بخواهیم تعداد خیلی زیادی داده  را به صورت فقط خواندنی نمایش دهیم. اگر برنامه‌ی شما داده ای را تغییر می‌دهد و می‌خواهد آن را ذخیره کند، باید از AsNoTracking استفاده نکنید.

AsNoTracking یک متد الحاقی است، که در <IQueryable<T تعریف شده است، در نتیجه شما می‌توانید از آن، در کوئری‌های LINQ نیز استفاده کنید. شما می‌توانید از AsNoTracking، در انتهای DbSet ،در خط from کوئری استفاده کنید.

var query = from p in context.Persons.AsNoTracking()
                            where p.FirstName == "joe"
                            select p;

شما همچنین از AsNoTracking می‌توانید برای تبدیل یک کوئری LINQ موجود، به یک کوئری فاقد ردیابی استفاده کنید. این نکته را به یاد داشته باشید که فقط AsNoTracking بر روی کوئری، فرانخوانده شده است، بلکه متغیر query را با نتیجه‌ی حاصل از فراخوانی AsNoTracking بازنویسی(override)  کرده است و این، از این جهت لازم است که AsNoTracking ،تغییری در کوئری ای که بر روی آن فراخوانده شده نمی‌دهد، بلکه یک کوئری جدید بر می‌گرداند. 

 
                var query = from p in context.Persons
                            where p.FirstName == "joe"
                            select p;
                query = query.AsNoTracking();
نکته: به دلیل اینکه AsNoTracking یک متد الحاقی است، شما احتیاج به افزودن فضای نام System.Data.Entity به فضا‌های نام خود دارید.

منبع: ترجمه ای آزاد از کتاب Programming Entity Framework: DbContext
مطالب
Bundle کردن فایلهای LESS در MVC
چنانچه قبلاً با فایلهای Less کار کرده باشید، متوجه خواهید شد که به صورت پیش فرض و همانند فایلهای .css و .js  قابلیت افزوده شدن به Bundle.config را دارا نمی‌باشند. برای انجام این کار باید مراحلی کوتاه را طی نمایید:

1- به منوی project و بخش Manage NuGet Packages... رفته و dotless را جستجو و نصب نمایید.
2- کلاسی به نام "LessTransForm" ایجاد کنید که از "IBundleTransform" ارث بری کند، کدهای آن را به صورت ذیل تغییر دهید:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Optimization;

namespace LessBundling
{
   
    public class LessTransform:IBundleTransform
    {
        public void Process(BundleContext context, BundleResponse response)
        {
            response.Content = dotless.Core.Less.Parse(response.Content);
            response.ContentType = "text/css";
        }
    }
}
3- فولدری برای فایلهای .Less خود ایجاد کنید:

4- به App_Start/BundleConfig.cs رفته و کدهای ذیل در آن اضافه کنید:

  var lessBundle = new Bundle("~/Content/Less").IncludeDirectory("~/Content/MyLess", "*.less");
            lessBundle.Transforms.Add(new LessTransform());
            lessBundle.Transforms.Add(new CssMinify());

            bundles.Add(lessBundle);
Bundling به اتمام رسید! حال می‌توانید از کد ذیل در viewها و یا در Layout خود، همانند فایلهای css و js  استفاده کنید:
        @Styles.Render("~/Content/Less")

منبع