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

اما این روش محدودیت هایی نیز دارد :
1. همه قسمت‌های Partial Class باید در یک اسمبلی باشند.
2. پراپرتی‌های درون Partial Class در دیتابیس ذخیره نمی‌شوند.
3. ونیز این پراپرتی‌ها در عبارات Linq قابل استفاده نیستند.چون عبارت Linq در نهایت به یک رشته SQL تبدیل شده و در دیتابیس اجرا می‌شود.البته با فرض این که دیتاپرووایدر، SQL  باشد.
 
مطالب
اشیاء تغییر ناپذیر (Immutable Object)
کلمه‌ی mutable به معنای تغییر پذیر و کلمه‌ی immutable به معنای تغیر ناپذیر در زبان انگلیسی تعریف شده‌اند. در دنیای IT این دو واژه نیز همین معنا را دارند. بطور مثال: یک رشته‌ی mutable، یعنی رشته‌ای که بتوان آن را تغییر داد و یک رشته‌ی immutable یعنی رشته‌ای که غیر قابل تغییر است.



در حین مطالعه‌ی منابع مختلف درباره‌ی موضوع این مطلب، جمله‌ای را با این مضمون دیدم: برای ساخت کوزه، گل را تا مرطوب هست باید شکل داد. زمانیکه گل خشک شود، دیگر نمی‌توان کوزه را تغییر شکل داد. اشیاء تغییر ناپذیر هم به همین شکل هستند. بعد از ایجاد این اشیاء، دیگر نمی‌توان به هیچ وجه آنها را تغییر داد.

تعریف اشیاء تغییر ناپذیر (Immutable Objects) :
این اشیاء، اشیائی هستند که بعد از بارگزاری در حافظه، به هیچ وجه نمی‌توانند اصلاح و یا تغییر کنند. نه از طریق خارجی (کاربران External) و نه از طریق داخلی (اعضای کلاس Internal).

چه زمانی از این اشیاء استفاده می‌کنیم؟
اشیاء تغییر ناپذیر برای داده‌های استاتیک استفاده می‌شوند و نمونه‌هایی از آن در بخش زیر لیست شده‌اند:

 • داده‌های اصلی (Master Data): یکی از بیشترین کاربرد‌های اشیاء تغییر ناپذیر، برای بارگذاری داده‌های اصلی است (کشور‌ها، واحد‌های پولی، استان ها) و داده‌هایی که به ندرت تغییر می‌کنند. این داده‌های اصلی بعد از بارگزاری در حافظه، دیگر تغییر نخواهند کرد.

 • اطلاعات پیکره‌بندی (Configuration Data): همه‌ی برنامه‌ها نیاز به اطلاعات پیکره‌بندی دارند. در دنیای برنامه‌های مایکروسافت، عموما این اطلاعات پیکره بندی را در فایل‌های web.config و App.config ذخیره می‌کنیم. این نوع اطلاعات بصورت یک شیء در حافظه بارگذاری می‌شوند و بعدا تغییر نخواهند کرد.

 • اشیاء Singleton: اشیاء singleton اشیائی هستند که تنها یک نمونه از آنها را می‌توان ایجاد کرد. در برنامه‌ها از این اشیاء برای اشتراک گذاشتن اطلاعات استاتیک استفاده می‌کنند. اگر این اطلاعات تغییر نکنند، یکی از گزینه‌ها، استفاده از اشیاء تغییر ناپذیر هستند.

چگونه می‌توان در سی شارپ اشیاء تغییر ناپذیر را ایجاد کنیم؟

اشیاء تغییر ناپذیر (immutable objects) تنها توسط کلاس‌های تغییر ناپذیر (immutable classes) می‌توانند ایجاد شوند.
برای ایجاد کلاسی تغییر ناپذیر، سه مرحله باید طی شود:
 1- حذف بلاک Set: همانطور که گفته شد، بخش Set از property ‌ها باید حذف شود. با حذف این بخش بعد از بارگزاری شیء در حافظه، دیگر نمی‌توان آن را تغییر داد:
public class Currency
{
  private string _currencyName;
  private string _countryName;

  public string CurrencyName
  {
     get { return _currencyName; }
  }

  public string CountryName
  {
     get { return _countryName; }
  }
}

 2- مهیا کردن پارامتر‌ها از طریق سازنده‌ی کلاس: با حذف بلاک set، راهی برای بارگزاری اطلاعات، در کلاس وجود ندارد. از این رو می‌توان از طریق پارامتر‌های سازنده‌ی کلاس، اطلاعات را به شیء ارسال کرد.
  private string _currencyName;
  private string _countryName;
  public Currency(string paramCurrencyName,string paramCountryName)
  {
     _currencyName= paramCurrencyName;
     _countryName = paramCountryName;
  }

 3- تعریف متغیر‌های کلاس به صورت فقط خواندنی READONLY
در تعریف اولیه گفته شد که اشیاء immutable نه از طریق خارجی (کاربر) و «کمی فراتر» نه از طریق داخلی (اعضای کلاس) قابل تغییر نیستند. اما کلاس ایجاد شده را می‌توان بعد از ایجاد نمونه‌ای از آن، مجددا تغییر داد. کافی است یک متد به شکل زیر در آن تعریف کنیم و به‌راحتی وضعیت شیء را از طریق آن تغییر دهیم.
  public void DoSomthing()
  {
     _countryName = "somthing else";
  }

راه حل ارائه شده‌ی برای حل این موضوع، معرفی متغیر‌ها به صورت readonly می‌باشد. متغیرهایی که بصورت فقط خواندنی تعریف می‌شوند، تنها از طریق سازنده‌ی شیء می‌توانند مقداردهی اولیه شوند.
باز طراحی نهایی کلاس Currency  به صورت زیر است:
 public class Currency
{
  private readonly string _currencyName;
  private readonly string _countryName;
  public string CurrencyName
  {
    get { return _currencyName; }
  }

  public string CountryName
  {
    get { return _countryName; }
  }
  public Currency(string paramCurrencyName,string paramCountryName)
  {
     _currencyName= paramCurrencyName;
     _countryName = paramCountryName;
  }
}
مطالب
تنظیمات JSON در ASP.NET Web API
ASP.NET Web API در سمت سرور، برای مدیریت ApiControllerها و در سمت کلاینت‌های دات نتی آن، برای مدیریت HttpClient، به صورت پیش فرض از JSON.NET استفاده می‌کند. در ادامه نگاهی خواهیم داشت به تنظیمات JSON در سرور و کلاینت‌های ASP.NET Web API.


آماده سازی یک مثال Self host

برای اینکه خروجی‌های JSON را بهتر و بدون نیاز به ابزار خاصی مشاهده کنیم، می‌توان یک پروژه‌ی کنسول جدید را آغاز کرده و سپس آن‌را تبدیل به Host مخصوص Web API کرد. برای اینکار تنها کافی است در کنسول پاور شل نیوگت دستور ذیل را صادر کنید:
 PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
سپس کنترلر Web API ما از کدهای ذیل تشکیل خواهد شد که در آن در متد Post، قصد داریم اصل محتوای دریافتی از کاربر را نمایش دهیم. توسط متد GetAll آن، خروجی نهایی JSON آن در سمت کاربر بررسی خواهد شد.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;

namespace WebApiSelfHostTests
{
    public class UsersController : ApiController
    {
        public IEnumerable<User> GetAllUsers()
        {
            return new[]
            {
                new User{ Id = 1, Name = "User 1", Type = UserType.Admin },
                new User{ Id = 2, Name = "User 2", Type = UserType.User }
            };
        }

        public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
        {
            var jsonContent = await request.Content.ReadAsStringAsync();
            Console.WriteLine("JsonContent (Server Side): {0}", jsonContent);
            return new HttpResponseMessage(HttpStatusCode.Created);
        }
    }
}
که در آن شیء کاربر چنین ساختاری را دارد:
namespace WebApiSelfHostTests
{
    public enum UserType
    {
        User,
        Admin,
        Writer
    }

    public class User
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public UserType Type { set; get; }
    }
}
برای اعمال تنظیمات self host ابتدا نیاز است یک کلاس Startup مخصوص Owin را تهیه کرد:
using System.Web.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Owin;

namespace WebApiSelfHostTests
{
    /// <summary>
    /// PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
    /// </summary>
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var config = new HttpConfiguration();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
                );

            appBuilder.UseWebApi(config);
        }
    }
}
که سپس با فراخوانی چند سطر ذیل، سبب راه اندازی سرور Web API، بدون نیاز به IIS خواهد شد:
 var server = WebApp.Start<Startup>(url: BaseAddress);


Console.WriteLine("Press Enter to quit.");
Console.ReadLine();
server.Dispose();
در ادامه اگر در سمت کلاینت، دستورات ذیل را برای دریافت لیست کاربران صادر کنیم:
 using (var client = new HttpClient())
{
   var response = client.GetAsync(BaseAddress + "api/users").Result;
   Console.WriteLine("Response: {0}", response);
   Console.WriteLine("JsonContent (Client Side): {0}", response.Content.ReadAsStringAsync().Result);
}
به این خروجی خواهیم رسید:
 JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":1},{"Id":2,"Name":"User 2","Type":0}]
همانطور که ملاحظه می‌کنید، مقدار Type مساوی صفر است. در اینجا چون Type را به صورت enum تعریف کرده‌ایم، به صورت پیش فرض مقدار عددی عضو انتخابی در JSON نهایی درج می‌گردد.


تنظیمات JSON سمت سرور Web API

برای تغییر این خروجی، در سمت سرور تنها کافی است به کلاس Startup مراجعه و HttpConfiguration را به صورت ذیل تنظیم کنیم:
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var config = new HttpConfiguration();
            config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings
            {
                Converters = { new StringEnumConverter() }
            };
در اینجا با انتخاب StringEnumConverter، سبب خواهیم شد تا کلیه مقادیر enum، دقیقا مساوی همان مقدار اصلی رشته‌ای آن‌ها در JSON نهایی درج شوند.
اینبار اگر برنامه را اجرا کنیم، چنین خروجی حاصل می‌گردد و در آن دیگر Type مساوی صفر نیست:
 JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":"Admin"},{"Id":2,"Name":"User 2","Type":"User"}]


تنظیمات JSON سمت کلاینت Web API

اکنون در سمت کلاینت قصد داریم اطلاعات یک کاربر را با فرمت JSON به سمت سرور ارسال کنیم. روش متداول آن توسط کتابخانه‌ی HttpClient، استفاده از متد PostAsJsonAsync است:
var user = new User
{
   Id = 1,
   Name = "User 1",
   Type = UserType.Writer
};

var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

var response = client.PostAsJsonAsync(BaseAddress + "api/users", user).Result;
Console.WriteLine("Response: {0}", response);
با این خروجی سمت سرور
 JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":2}
در اینجا نیز Type به صورت عددی ارسال شده‌است. برای تغییر آن نیاز است به متدی با سطح پایین‌تر از PostAsJsonAsync مراجعه کنیم تا در آن بتوان JsonMediaTypeFormatter را مقدار دهی کرد:
            var jsonMediaTypeFormatter = new JsonMediaTypeFormatter
            {
                SerializerSettings = new JsonSerializerSettings
                {
                    Converters = { new StringEnumConverter() }
                }
            };
            var response = client.PostAsync(BaseAddress + "api/users", user, jsonMediaTypeFormatter).Result;
            Console.WriteLine("Response: {0}", response);
خاصیت SerializerSettings کلاس JsonMediaTypeFormatter برای اعمال تنظیمات JSON.NET پیش بینی شده‌است.
اینبار مقدار دریافتی در سمت سرور به صورت ذیل است و در آن، Type دیگر عددی نیست:
 JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":"Writer"}

مثال کامل این بحث را از اینجا می‌توانید دریافت کنید:
UsersController.zip
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
ارتقاء به ASP.NET Core 3.0 و تغییرات نقطه‌ی آغازین برنامه

ASP.NET Core 3.0 از Generic Host بجای Web Host قبلی استفاده می‌کند. در این حالت فایل Program.cs آن از:
public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}
در نگارش 2.2، به حالت زیر در نگارش 3 تغییر یافته‌است:
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}
البته باید درنظر داشت که روش قبلی 2.2، هنوز هم در نگارش 3 کار می‌کند، اما به صورت deprecated و منسوخ شده معرفی خواهد شد و در نگارش‌های بعدی حذف می‌گردد.
در این حالت هاستینگ برنامه دیگر به Kestrel و یا حتی خود ASP.NET Core وابسته نخواهد بود. یعنی می‌توان هاستی را ایجاد کرد که به همراه راه اندازی وب سرور Kestrel نباشد. علت این جنریک شدن چیست؟
در نگارش سوم، هاست‌های دیگری هم معرفی شده‌اند؛ مانند امکان اجرای یک worker service بدون راه اندازی یک وب سرور و یا Blazor از روش هاستینگ متفاوتی درون یک web assembly استفاده می‌کند: «ارتقاء به NET Core 3.0.: پشتیبانی از ایجاد سرویس‌های پس‌زمینه»

یک نکته: جزئیات متد CreateDefaultBuilder و سرویس‌هایی را که به صورت خودکار اضافه می‌کند، در اینجا در فایل src/DefaultBuilder/src/WebHost.cs می‌توانید مشاهده کنید.
نظرات مطالب
ایجاد ویژگی‌های اعتبارسنجی سفارشی در ASP.NET Core 3.1 به همراه اعتبارسنجی سمت کلاینت آن‌ها
کتابخانه FluentValidation ،  این قابلیت رو داره در سمت کلاینت اعتبار سنجی انجام بده و شما میتونید از تمامی شروطی که برای مدل خود در نظر گرفتید به همراه بخش کلاینتش رو باهم استفاده کنید. به عنوان مثال میخواهید کد ملی رو اعتبار سنجی کنید؛ مانند خالی نبودن فیلد. ابتدا از کلاس ClientValidationBase ارث بری می‌کنید
public class NationalCodeClientValidator : ClientValidatorBase
    {
        #region Fields

        private NationalCodeValidator NationalCodeValidator => (NationalCodeValidator)Validator;

        #endregion Fields

        #region Methods

        #region Constructors

        public NationalCodeClientValidator(PropertyRule rule, IPropertyValidator validator) : base(rule, validator)
        {
        }

        #endregion Constructors

        #region Override

        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-nationalcode", GetErrorMessage());
        }

        #endregion Override

        #region Utility

        private string GetErrorMessage()
        {
            var formatter = ValidatorOptions.MessageFormatterFactory().AppendPropertyName(Rule.GetDisplayName());
            string messageTemplate;
            try
            {
                messageTemplate = Validator.Options.ErrorMessageSource.GetString(null);
            }
            catch (FluentValidationMessageFormatException)
            {
                messageTemplate = ValidatorOptions.LanguageManager.GetStringForValidator<NotEmptyValidator>();
            }
            var message = formatter.BuildMessage(messageTemplate);
            return message;
        }

        #endregion Utility

        #endregion Methods
    }

سپس کلاسی دیگر تعریف کرده و از PropertyValidator ارث بری می‌کنید:
public class NationalCodeValidator : PropertyValidator
    {
        #region Methods

        #region Constructors

        public NationalCodeValidator() : base(new LanguageStringSource(nameof(NationalCodeValidator)))
        {
        }

        #endregion Constructors

        #region Override

        protected override bool IsValid(PropertyValidatorContext context)
        {
            return true;
        }

        #endregion Override

        #endregion Methods
    }

سپس باید کلاس‌های تعریف شده رو به FluentValidation معرفی کرد:
.AddFluentValidation(option =>
            {
                option.RegisterValidatorsFromAssemblyContaining<CreateBankValidation>();
                option.ConfigureClientsideValidation(AddFluentValidationClientModelValidatorProvider());
            })

 private static Action<FluentValidationClientModelValidatorProvider> AddFluentValidationClientModelValidatorProvider()
        {
            return clientSideValidation =>
            {
                clientSideValidation.Add(typeof(NationalCodeValidator), (context, rule, validator) => new NationalCodeClientValidator(rule, validator));
            };
        }

حال فقط مانده استفاده ساختار Validation در سمت کلاینت:
// بررسی تایید کد ملی

function setCustomValidator() {
    $.validator.unobtrusive.adapters.add('nationalcode', [], function (options) {
        options.rules['nationalcode'] = {};
        options.messages['nationalcode'] = options.message;
    });
    $.validator.addMethod('nationalcode', function (value, element, parameters) {
        if (isValidIranianNationalCode(value)) return true;
        return false;
    });
}
مطالب دوره‌ها
انتقال خودکار Data Annotations از مدل‌ها به ViewModelهای ASP.NET MVC به کمک AutoMapper
عموما مدل‌های ASP.NET MVC یک چنین شکلی را دارند:
public class UserModel
{
    public int Id { get; set; }
 
    [Required(ErrorMessage = "(*)")]
    [Display(Name = "نام")]
    [StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "نام باید حداقل 3 و حداکثر 10 حرف باشد")]
    public string FirstName { get; set; }
 
    [Required(ErrorMessage = "(*)")]
    [Display(Name = "نام خانوادگی")]
    [StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "نام خانوادگی باید حداقل 3 و حداکثر 10 حرف باشد")]
    public string LastName { get; set; }
}
 و ViewModel مورد استفاده برای نمونه چنین ساختاری را دارد:
public class UserViewModel
{
      public string FirstName { get; set; }
      public string LastName { get; set; }
}
مشکلی که در اینجا وجود دارد، نیاز به کپی و تکرار تک تک ویژگی‌های (Data Annotations/Attributes) خاصیت‌های مدل، به خواص مشابه آن‌ها در ViewModel است؛ از این جهت که می‌خواهیم برچسب خواص ViewModel، از ویژگی Display دریافت شوند و همچنین اعتبارسنجی‌های فیلدهای اجباری و بررسی حداقل و حداکثر طول فیلدها نیز حتما اعمال شوند (هم در سمت کاربر و هم در سمت سرور).
در ادامه قصد داریم راه حلی را به کمک جایگزین سازی Provider‌های توکار ASP.NET MVC با نمونه‌ی سازگار با AutoMapper، ارائه دهیم، به نحوی که دیگر نیازی نباشد تا این ویژگی‌ها را در ViewModelها تکرار کرد.


قسمت‌هایی از ASP.NET MVC که باید جهت انتقال خودکار ویژگی‌ها تعویض شوند

ASP.NET MVC به صورت توکار دارای یک ModelMetadataProviders.Current است که از آن جهت دریافت ویژگی‌های هر خاصیت استفاده می‌کند. می‌توان این تامین کننده‌ی ویژگی‌ها را به نحو ذیل سفارشی سازی نمود.
در اینجا IConfigurationProvider همان Mapper.Engine.ConfigurationProvider مربوط به AutoMapper است. از آن جهت استخراج اطلاعات نگاشت‌های AutoMapper استفاده می‌کنیم. برای مثال کدام خاصیت Model به کدام خاصیت ViewModel نگاشت شده‌است. این‌کارها توسط متد الحاقی GetMappedAttributes انجام می‌شوند که در ادامه‌ی مطلب معرفی خواهد شد.
public class MappedMetadataProvider : DataAnnotationsModelMetadataProvider
{
    private readonly IConfigurationProvider _mapper;
 
    public MappedMetadataProvider(IConfigurationProvider mapper)
    {
        _mapper = mapper;
    }
 
    protected override ModelMetadata CreateMetadata(
        IEnumerable<Attribute> attributes,
        Type containerType,
        Func<object> modelAccessor,
        Type modelType,
        string propertyName)
    {
        var mappedAttributes =
            containerType == null ?
            attributes :
            _mapper.GetMappedAttributes(containerType, propertyName, attributes.ToList());
        return base.CreateMetadata(mappedAttributes, containerType, modelAccessor, modelType, propertyName);
    }
}

شبیه به همین کار را باید برای ModelValidatorProviders.Providers نیز انجام داد. در اینجا یکی از تامین کننده‌های ModelValidator، از نوع DataAnnotationsModelValidatorProvider است که حتما نیاز است این مورد را نیز به نحو ذیل سفارشی سازی نمود. در غیراینصورت error messages موجود در ویژگی‌های تعریف شده، به صورت خودکار منتقل نخواهند شد.
public class MappedValidatorProvider : DataAnnotationsModelValidatorProvider
{
    private readonly IConfigurationProvider _mapper;
 
    public MappedValidatorProvider(IConfigurationProvider mapper)
    {
        _mapper = mapper;
    }
 
    protected override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata,
        ControllerContext context,
        IEnumerable<Attribute> attributes)
    {
 
        var mappedAttributes =
            metadata.ContainerType == null ?
            attributes :
            _mapper.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes.ToList());
        return base.GetValidators(metadata, context, mappedAttributes);
    }
}

و در اینجا پیاده سازی متد GetMappedAttributes را ملاحظه می‌کنید.
ASP.NET MVC هر زمانیکه قرار است توسط متدهای توکار خود مانند Html.TextBoxFor, Html.ValidationMessageFor، اطلاعات خاصیت‌ها را تبدیل به المان‌های HTML کند، از تامین کننده‌های فوق جهت دریافت اطلاعات ویژگی‌های مرتبط با هر خاصیت استفاده می‌کند. در اینجا فرصت داریم تا ویژگی‌های مدل را از تنظیمات AutoMapper دریافت کرده و سپس بجای ویژگی‌های خاصیت معادل ViewModel درخواست شده، بازگشت دهیم. به این ترتیب ASP.NET MVC تصور خواهد کرد که ViewModel ما نیز دقیقا دارای همان ویژگی‌های Model است.
public static class AutoMapperExtensions
{
    public static IEnumerable<Attribute> GetMappedAttributes(
        this IConfigurationProvider mapper,
        Type viewModelType,
        string viewModelPropertyName,
        IList<Attribute> existingAttributes)
    {
        if (viewModelType != null)
        {
            foreach (var typeMap in mapper.GetAllTypeMaps().Where(i => i.DestinationType == viewModelType))
            {
                var propertyMaps = typeMap.GetPropertyMaps()
                    .Where(propertyMap => !propertyMap.IsIgnored() && propertyMap.SourceMember != null)
                    .Where(propertyMap => propertyMap.DestinationProperty.Name == viewModelPropertyName);
 
                foreach (var propertyMap in propertyMaps)
                {
                    foreach (Attribute attribute in propertyMap.SourceMember.GetCustomAttributes(true))
                    {
                        if (existingAttributes.All(i => i.GetType() != attribute.GetType()))
                        {
                            yield return attribute;
                        }
                    }
                }
            }
        }
 
        if (existingAttributes == null)
        {
            yield break;
        }
 
        foreach (var attribute in existingAttributes)
        {
            yield return attribute;
        }
    }
}


ثبت تامین کننده‌های سفارشی سازی شده توسط AutoMapper

پس از تهیه‌ی تامین کننده‌های انتقال ویژگی‌ها، اکنون نیاز است آن‌ها را به ASP.NET MVC معرفی کنیم:
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes); 
 
    Mappings.RegisterMappings();
    ModelMetadataProviders.Current = new MappedMetadataProvider(Mapper.Engine.ConfigurationProvider);
 
    var modelValidatorProvider = ModelValidatorProviders.Providers
        .Single(provider => provider is DataAnnotationsModelValidatorProvider);
    ModelValidatorProviders.Providers.Remove(modelValidatorProvider);
    ModelValidatorProviders.Providers.Add(new MappedValidatorProvider(Mapper.Engine.ConfigurationProvider));
}
در اینجا ModelMetadataProviders.Current با MappedMetadataProvider جایگزین شده‌است.
در قسمت کار با ModelValidatorProviders.Providers، ابتدا صرفا همان تامین کننده‌ی از نوع DataAnnotationsModelValidatorProvider پیش فرض، یافت شده و حذف می‌شود. سپس تامین کننده‌ی سفارشی سازی شده‌ی خود را معرفی می‌کنیم تا جایگزین آن شود.


مثالی جهت آزمایش انتقال خودکار ویژگی‌های مدل به ViewModel

کنترلر مثال برنامه به شرح زیر است. در اینجا از متد Mapper.Map جهت تبدیل خودکار مدل کاربر به ViewModel آن استفاده شده‌است:
public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new UserModel { FirstName = "و", Id = 1, LastName = "ن" };
        var viewModel = Mapper.Map<UserViewModel>(model);
        return View(viewModel);
    }
 
    [HttpPost]
    public ActionResult Index(UserViewModel data)
    {
        return View(data);
    }
}
با این View که جهت ثبت اطلاعات مورد استفاده قرار می‌گیرد. این View، اطلاعات مدل خود را از ViewModel معرفی شده‌ی در ابتدای بحث دریافت می‌کند:
@model Sample12.ViewModels.UserViewModel
 
@using (Html.BeginForm("Index", "Home", FormMethod.Post, htmlAttributes: new { @class = "form-horizontal", role = "form" }))
{
    <div class="row">
        <div class="form-group">
            @Html.LabelFor(d => d.FirstName, htmlAttributes: new { @class = "col-md-2 control-label" })
            <div class="col-md-10">
                @Html.TextBoxFor(d => d.FirstName)
                @Html.ValidationMessageFor(d => d.FirstName)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(d => d.LastName, htmlAttributes: new { @class = "col-md-2 control-label" })
            <div class="col-md-10">
                @Html.TextBoxFor(d => d.LastName)
                @Html.ValidationMessageFor(d => d.LastName)
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="ارسال" class="btn btn-default" />
            </div>
        </div>
    </div>
}
در این حالت اگر برنامه را اجرا کنیم به شکل زیر خواهیم رسید:


در این شکل هر چند نوع مدل View مورد استفاده از ViewModel ایی تامین شده‌است که دارای هیچ ویژگی و Data Annotations/Attributes نیست، اما برچسب هر فیلد از ویژگی Display دریافت شده‌‌است. همچنین اعتبارسنجی سمت کاربر فعال بوده و برچسب‌های آن‌ها نیز به درستی دریافت شده‌اند.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
بهبودهای کار با Lambdas در C# 9.0
C# 9.0 به همراه دو بهبود جزئی درباره‌ی کار با Lambdas است:
- امکان عدم ذکر بعضی از پارامترهای Lambdas
- امکان تعریف متدهای static anonymous


امکان عدم ذکر بعضی از پارامترهای Lambdas در C# 9.0

مثال زیر را در نظر بگیرید:
Action<object, EventArgs> handler1 = (object obj, EventArgs args) => ShowDialog();
در عبارت lambda تعریف شده، از پارامترهای obj و args استفاده نشده‌است. به همین جهت برای کاهش اخطارهای نمایش داده شده‌ی توسط کامپایلر در C# 9.0 می‌توان آن‌ها را به صورت discard parameters توسط _ معرفی کرد؛ به یکی از دو روش زیر:
Action<object, EventArgs> handler2 = (object _, EventArgs _) => ShowDialog();
// OR
Action<object, EventArgs> handler3 = (_, _) => ShowDialog();
که در یکی، نوع‌ها به همراه discard parameters ذکر شده‌اند و در دومی فقط discard parameters را داریم.

نمونه‌ی دیگر آن در حین تعریف رخ‌دادگردان‌ها است:
button1.Click += (s, e) => ShowDialog();
که اینبار پارامترهای استفاده نشده به صورت زیر قابل بیان هستند:
 button1.Click += (_, _) => ShowDialog();


امکان تعریف Static anonymous functions در C# 9.0

برای کاهش میزان تخصیص حافظه‌ی در حین کار با عبارات lambda ای که از متغیرهای محلی استفاده می‌کنند، اینبار در C# 9.0 می‌توان این عبارات را static تعریف کرد. برای نمونه مثال زیر را درنظر بگیرید:
namespace CS9Features
{
    public class LambdasTests
    {
        public void Test()
        {
            string text = "{0} is a good user !";
            PrintItems(item => string.Format(text, item));
        }

        private void PrintItems(Func<string, string> formatFunc)
        {
            foreach (var item in new[] { "User 1", "User 2" })
            {
                Console.WriteLine(formatFunc(item));
            }
        }
    }
}
در اینجا نحوه‌ی فرمت نهایی اطلاعات نمایش داده شده، توسط یک عبارت lambda تامین می‌شود. اتفاقی که در اینجا رخ می‌دهد، اصطلاحا capture شدن یک متغیر (text در اینجا) توسط یک anonymous function است (همان عبارت lambda نوشته شده). حاصل این capture شدن، ایجاد یک شیء موقتی برای مدیریت آن است که تخصیص حافظه و همچنین سربار عملیاتی اضافه‌ای را به همراه دارد. برای حذف این سربارها در C# 9.0 می‌توان متغیر استفاده شده را const تعریف کرد و سپس عبارت lambda تعریف شده را به صورت static نوشت:
const string text = "{0} is a good user !";
PrintItems(static item => string.Format(text, item));
با تعریف شدن یک عبارت lambda و یا یک anonymous method به صورت static که از تخصیص حافظه‌ی اضافی ایجاد شیء موقتی مدیریت کننده‌ی دسترسی به متغیر text جلوگیری می‌کند، اتفاقات زیر نیز رخ می‌دهند:
- این متد به عنوان static anonymous function شناخته می‌شود.
- دیگر نمی‌تواند دسترسی به حالت scope جاری را داشته باشد. بنابراین دیگر دسترسی به متغیرها، پارامترها و حتی شیء this را هم نخواهد داشت.
- می‌تواند با سایر اعضای استاتیک scope جاری کار کند.
- می‌تواند به تعاریف const مربوط به scope جاری، دسترسی داشته باشد.
مطالب
استفاده از SignalR در اندروید
همانطور که مطلع هستید، بخش سورس باز مایکروسافت برای برنامه‌نویس‌های جاوا نیز SDKی جهت استفاده از SignalR ارائه کرده است. در اینجا می‌توانید مخزن کد آن را در گیت‌هاب مشاهده کنید. هنوز مستنداتی برای این SDK به صورت قدم به قدم ارائه نشده است. لازم به ذکر است که مراجعه به قسمت‌های نوشته شده در اینجا نیز می‌تواند منبع خوبی برای شروع باشد. در ادامه نحوه استفاده از این SDK را با هم بررسی خواهیم کرد.
ابتدا در سمت سرور یک Hub ساده را به صورت زیر تعریف می‌کنیم:
public class ChatHub : Hub
{
        public void Send(string name, string message)
        {
            Clients.All.messageReceived(name, message);
        }
}
برای سمت کلاینت نیز یک پروژه Android Application داخل Eclipse به صورت زیر ایجاد می‌کنیم:

خوب، برای استفاده از SignalR در پروژه‌ی ایجاد شده باید کتابخانه‌های زیر را به درون پوشه libs اضافه کنیم، همچنین باید ارجاعی به کتابخانه Gson نیز داشته باشیم.

قدم بعدی افزودن کدهای سمت کلاینت برای SignalR می‌باشد. دقت داشته باشید که کدهایی که در ادامه مشاهده خواهید کرد دقیقاً مطابق دستورالمعل‌هایی است که قبلاً مشاهده کرده‌اید. برای اینکار داخل کلاس MainActivity.java کدهای زیر را اضافه کنید:

Platform.loadPlatformComponent( new AndroidPlatformComponent() );
HubConnection connection = new HubConnection(DEFAULT_SERVER_URL);
HubProxy hub = connection.createHubProxy("ChatHub");
connection.error(new ErrorCallback() {
    
    @Override
    public void onError(final Throwable error) {
        runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
        
    }
});
hub.subscribe(new Object() {
    @SuppressWarnings("unused")
    public void messageReceived(final String name, final String message) {
        
        runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(getApplicationContext(), name + ": " + message, Toast.LENGTH_LONG).show();
            }
        });
    }
});
connection.start()
.done(new Action<Void>() {
    
    @Override
    public void run(Void obj) throws Exception {
        runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(getApplicationContext(), "Done Connecting!", Toast.LENGTH_LONG).show();
            }
        });
    }
});
connection.received(new MessageReceivedHandler() {
    @Override
    public void onMessageReceived(final JsonElement json) {
        runOnUiThread(new Runnable() {
            public void run() {
                JsonObject jsonObject = json.getAsJsonObject();
                JsonArray jsonArray =jsonObject.getAsJsonArray("A");
                Toast.makeText(getApplicationContext(), jsonArray.get(0).getAsString() + ": " + jsonArray.get(1).getAsString(), Toast.LENGTH_LONG).show();
            }
        });
    }
});

همانطور که مشاهده می‌کنید توسط قطعه کد زیر SKD مربوطه در نسخه‌های قدیمی اندروید نیز بدون مشکل کار خواهد کرد:

Platform.loadPlatformComponent( new AndroidPlatformComponent() );

در ادامه توسط متد createHubProxy ارجاعی به هابی که در سمت سرور ایجاد کردیم، داده‌ایم:

HubProxy hub = connection.createHubProxy("ChatHub");

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

connection.error(new ErrorCallback() {
    
    @Override
    public void onError(final Throwable error) {
        runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
        
    }
});
در ادامه نیز توسط کد زیر متد پویایی که در سمت سرور ایجاد کرده بودیم را جهت برقراری ارتباط با سرور اضافه کرده‌ایم:
hub.subscribe(new Object() {
    @SuppressWarnings("unused")
    public void messageReceived(final String name, final String message) {
        
        runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(getApplicationContext(), name + ": " + message, Toast.LENGTH_LONG).show();
            }
        });
    }
});
برای برقراری ارتباط نیز کدهای زیر را اضافه کرده‌ایم. یعنی به محض اینکه با موفقیت اتصال با سرور برقرار شد پیامی بر روی صفحه‌نمایش ظاهر می‌شود:
connection.start()
.done(new Action<Void>() {
    
    @Override
    public void run(Void obj) throws Exception {
        runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(getApplicationContext(), "Done Connecting!", Toast.LENGTH_LONG).show();
            }
        });
    }
});
در نهایت نیز برای نمایش اطلاعات دریافت شده کد زیر را نوشته‌ایم:
connection.received(new MessageReceivedHandler() {
    @Override
    public void onMessageReceived(final JsonElement json) {
        runOnUiThread(new Runnable() {
            public void run() {
                JsonObject jsonObject = json.getAsJsonObject();
                JsonArray jsonArray =jsonObject.getAsJsonArray("A");
                Toast.makeText(getApplicationContext(), jsonArray.get(0).getAsString() + ": " + jsonArray.get(1).getAsString(), Toast.LENGTH_LONG).show();
            }
        });
    }
});
همانطور که عنوان شد کدهای فوق دقیقاً براساس قواعد و دستورالعمل استفاده از SignalR در سمت کلاینت می‌باشد.

مطالب
انتخاب پویای فیلد ها در LINQ

LINQ یک DLS  بر مبنای .NET  می باشد که برای پرس و جو در منابع داده ای مانند پایگاه‌های داده ، فایل‌های XML و یا لیستی از اشیاء درون حافظه کاربرد دارد.

یکی از بزرگترین مزیت‌های آن Syntax  آسان و خوانا آن می‌باشد.

LINQ  از 2 نوع نمادگذاری پشتیبانی می‌کند:

  • Inline LINQ یا query expressions : 
var result = 
    from product in dbContext.Products
    where product.Category.Name == "Toys"
    where product.Price >= 2.50
    select product.Name;
  • Fluent Syntax : 
var result = dbContext.Products
    .Where(p => p.Category.Name == "Toys" && p.Price >= 250)
    .Select(p => p.Name);

در پرس و چو‌های بالا فیلد‌های مورد نیاز در قسمت Select در زمان Compile شناخته شده هستند . اما گاهی ممکن است فیلد‌های مورد نیاز در زمان اجرا مشخص شوند.

به عنوان مثال یک گزارش ساز پویا که کاربر مشخص می‌کند چه ستون هایی در خروجی نمایش داده شوند یا یک جستجوی پیشرفته که ستون‌های خروجی به اختیار کاربر در زمان اجرا مشخص می‌شوند. 

این مدل را در نظر داشته باشید :

    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }


        public static IEnumerable<Student> GetStudentSource()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return new Student
                                 {
                                     Id = i,
                                     Name = "Name " + i,
                                     Field1 = "Field1 " + i,
                                     Field2 = "Field2 " + i,
                                     Field3 = "Field3 " + i
                                 };
            }
        }
    }

ستون‌های کلاس Student  را در رابط کاربری برنامه جهت انتخاب به کاربر نمایش می‌دهیم. سپس کاربر یک یا چند ستون را انتخاب می‌کند که قسمت Select  کوئری برنامه باید  بر اساس فیلد‌های مورد نظر کاربر مشخص شود.

یکی از روش هایی که می‌توان از آن بهره برد استفاده از کتاب خانه Dynamic LINQ معرفی شده در اینجا می باشد.

این کتابخانه جهت سهولت در نصب به کمک NuGet در این آدرس قرار دارد.

فرض بر این است که فیلد‌های انتخاب شده توسط کاربر با "," از یکدیگر جدا شده اند. 

    public class Program
    {
        private static void Main(string[] args)
        {
            System.Console.WriteLine("Specify the desired fields : ");
            string fields = System.Console.ReadLine();
            IEnumerable<Student> students = Student.GetStudentSource();
            IQueryable output = students.AsQueryable().Select(string.Format("new({0})", fields));
            foreach (object item in output)
            {
                System.Console.WriteLine(item);
            }
          
            System.Console.ReadKey();
        }
  
    }

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

این روش مزایا و معایب خودش را دارد ، به عنوان مثال خروجی یک لیست از شیء Student  نیست یا این Select  فقط برای روی یک شیء IQueryable  قابل انجام است.

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

    public  class SelectBuilder <T>
    {
        public static Func<T, T> CreateNewStatement(string fields)
        {
            // input parameter "o"
            var xParameter = Expression.Parameter(typeof(T), "o");


            // new statement "new T()"
            var xNew = Expression.New(typeof(T));

            // create initializers
            var bindings = fields.Split(',').Select(o => o.Trim())
                .Select(o =>
                {

                    // property "Field1"
                    var property = typeof(T).GetProperty(o);

                    // original value "o.Field1"
                    var xOriginal = Expression.Property(xParameter, property);

                    // set value "Field1 = o.Field1"
                    return Expression.Bind(property, xOriginal);
                }
            ).ToList();

            // initialization "new T { Field1 = o.Field1, Field2 = o.Field2 }"
            var xInit = Expression.MemberInit(xNew, bindings);

            // expression "o => new T { Field1 = o.Field1, Field2 = o.Field2 }"
            var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);

            // compile to Func<T, T>
            return lambda.Compile();
        }
    }
برای استفاده از متد CreateNewStatement باید اینگونه عمل کرد :  
       IEnumerable<Student> result = students.Select(SelectBuilder<Student>.CreateNewStatement("Field1, Field2")).ToList();

            foreach (Student student in result)
            {
                System.Console.WriteLine(student.Field1);
            }
خروجی یک لیست از Student  می باشد.
 نحوه‌ی کارکرد CreateNewStatement :

ابتدا فیلد‌های انتخابی کاربر که با "," جدا شده اند به ورودی پاس داده می‌شود سپس یک statement  خالی ایجاد می‌شود

o=>new Student()
فیلد‌های ورودی از یکدیگر تفکیک می‌شوند و به کمک Reflection پراپرتی معادل فیلد رشته ای در کلاس Student پیدا می‌شود :  
var property = typeof(T).GetProperty(o);
سپس عبارت Select و تولید شیء جدید بر اساس فیلد‌های ورودی تولید می‌شود و برای استفاده Compile  به Func می‌شود. در نهایت Func  تولید شده به Select پاس داده می‌شود و لیستی از Student  بر مبنای فیلد‌های انتخابی تولید می‌شود. 

دریافت مثال : DynamicSelect.zip 
مطالب
شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها
پس از بررسی نحوه‌ی انجام تنظیمات اولیه‌ی کار با EF Core و همچنین آشنایی با مهاجرت‌های آن، مرحله‌ی بعد، مرحله‌ی مدلسازی داده‌ها است و اولین مرحله‌ی آن، نحوه‌ی تعیین کلید اصلی جداول است که در این زمینه، EF Core پیشرفت‌هایی قابل ملاحظه‌ای را نسبت به EF 6.x داشته‌است. در EF 6.x تنها دو حالت کلیدهای اصلی خود افزاینده که توسط بانک اطلاعاتی مدیریت می‌شوند و یا تولید کلید اصلی در سمت کلاینت و توسط برنامه، پشتیبانی می‌شوند. در EF Core، مواردی مانند Sequence و Alternate keys نیز اضافه شده‌اند.


پیش فرض‌های تعیین کلید اصلی در EF Core

به صورت پیش فرض هر خاصیتی که به نام Id و یا type name>Id> باشد، به عنوان primary key تفسیر خواهد شد؛ مانند:
public class Car
{
    public string Id { get; set; }
و یا
public class Car
{
   public string CarId { get; set; }
در مثال اول، نام خاصیت، Id است و در مثال دوم، جمع نام کلاس به همراه Id ذکر شده‌است. یک چنین مواردی، نیازی به تنظیم اضافه‌تری ندارند.


نحوه‌ی تعیین کلید اصلی به صورت صریح

اگر یکی از دو حالت فوق برقرار نباشند، باید کلید اصلی را به نحو صریحی مشخص کرد.
الف) از طریق ویژگی‌ها
public class Car
{
   [Key]
   public string LicensePlate { get; set; }
در اینجا چون LicensePlate نه Id نام دارد و نه جمع نام کلاس به همراه Id است، باید به نحو صریحی توسط ویژگی Key مشخص شود.
ب) با استفاده از روش Fluent API
public class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
         modelBuilder.Entity<Car>()
                 .HasKey(c => c.LicensePlate);
    }
 }
روش تنظیم کلید اصلی به صورت صریح، از طریق کدنویسی است که به آن Fluent API یا API روان هم گفته می‌شود. برای اینکار باید متد OnModelCreating کلاس Context برنامه را بازنویسی کرد و سپس از طریق متد HasKey، نام خاصیت کلید اصلی را ذکر نمود.


پیشنیاز کار با ویژگی‌ها در EF Core

در اسمبلی که مدل‌های موجودیت‌ها شما قرار دارند، نیاز است وابستگی System.ComponentModel.Annotations به فایل project.json پروژه اضافه شود، تا ویژگی‌هایی مانند Key، شناسایی و قابل استفاده شوند:
{
   "dependencies": {
          "System.ComponentModel.Annotations": "4.1.0"
   }
}


تعیین کلید ترکیبی و یا Composite key

اگر نیاز است چندین خاصیت را به صورت کلید اصلی معرفی کرد که به آن composite key هم می‌گویند، تنها روش ممکن، استفاده از Fluent API و به صورت زیر است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Car>()
                       .HasKey(c => new { c.State, c.LicensePlate });
}
در قسمت HasKey می‌توان چندین خاصیت را نیز جهت تعیین کلید ترکیبی مشخص کرد.


روش‌های مختلف تولید خودکار مقادیر خواص

حالت پیش فرض تولید مقدار فیلدهای Id عددی، همان حالت خود افزاینده‌ای است که توسط بانک اطلاعاتی کنترل می‌شود و یا کلید اصلی که از نوع Guid تعیین شود نیز به صورت خودکار توسط بانک اطلاعاتی در حین عملیات Add، مقدار دهی می‌شود (با استفاده از الگوریتم Guid سری در SQL Server).
 اگر این حالات مطلوب شما نیست، حالت‌های سه گانه‌ی ذیل را می‌توان استفاده کرد:

الف) هیچ داده‌ی خودکاری تولید نشود
برای اینکار می‌توان با استفاده از ویژگی DatabaseGenerated و تنظیم مقدار آن به None، جلوی تولید خودکار کلید اصلی را گرفت. در این حالت باید هم در حین عملیات Add و هم در حین عملیات Update، مقادیر را خودتان مقدار دهی کنید:
public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BlogId { get; set; }

    public string Url { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
           .Property(b => b.BlogId)
           .ValueGeneratedNever();
}

ب) تولید داده‌های خودکار فقط در حالت Add
حالت Add به این معنا است که داده‌های خواص مشخصی، برای موجودیت‌های «جدید»، به صورت خودکار تولید خواهند شد. اینکه آیا واقعا این مقادیر به صورت خودکار تولید می‌شوند یا خیر، صرفا وابسته‌است به بانک اطلاعاتی در حال استفاده. برای مثال SQL Server برای نوع‌های Guid، به صورت خودکار با کمک الگوریتم SQL Server sequential GUID، کار مقدار دهی یک چنین فیلدهایی را انجام می‌دهد.
این فیلدها باید توسط ویژگی DatabaseGenerated و با مقدار Identity مشخص شوند. در اینجا Identity به معنای فیلدهایی است که به صورت خودکار توسط بانک اطلاعاتی مقدار دهی می‌شوند و الزاما به کلید اصلی اشاره نمی‌کنند. برای مثال در موجودیت ذیل، خاصیت تاریخ ثبت رکورد، از نوع Identity مشخص شده‌است. به این معنا که در حین ثبت اولیه‌ی رکورد آن، نیازی نیست تا خاصیت Inserted را مقدار دهی کرد. اما اینکه آیا SQL Server یک چنین کاری را به صورت خودکار انجام می‌دهد، پاسخ آن خیر است. SQL server فقط برای فیلدهای عددی و Guid ایی که با DatabaseGeneratedOption.Identity مزین شده باشند، مقادیر متناظری را به صورت خودکار تولید می‌کند. برای حالت DateTime نیاز است، مقدار پیش فرض فیلد را صریحا مشخص کرد که توسط ویژگی‌ها میسر نیست و فقط fluent API از آن پشتیبانی می‌کند.
public class Blog
{
   public int BlogId { get; set; }
   public string Url { get; set; }

   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public DateTime Inserted { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
           .Property(b => b.Inserted)
           .ValueGeneratedOnAdd();
}
برای تعیین مقدار پیش فرض خاصیت Inserted به نحوی که توسط SQL Server به صورت خودکار مقدار دهی شود، می‌توان از متد HasDefaultValueSql به نحو ذیل استفاده کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Inserted)
        .HasDefaultValueSql("getdate()");
}
البته باید درنظر داشت که اگر خاصیت DateTime تعریف شده در اینجا به همین نحو بکاربرده شود، اگر مقداری برای آن در حین تعریف یک وهله جدید از کلاس Blog درکدهای برنامه درنظر گرفته نشود، یک مقدار پیش فرض حداقل به آن انتساب داده خواهد شد (چون value type است). بنابراین نیاز است این خاصیت را از نوع nullable تعریف کرد (public DateTime? Inserted).

یک نکته: در حالت DatabaseGeneratedOption.Identity و یا ValueGeneratedOnAdd فوق، اگر مقداری به این نوع فیلدها انتساب داده شده باشد که با مقدار پیش فرض آن‌ها (property.ClrType.GetDefaultValue) متفاوت باشد، از این مقدار جدید، بجای تولید مقداری خودکار، استفاده خواهد شد. برای مثال مقدار پیش فرض رشته‌ها، نال، مقادیر عددی، صفر و برای Guid مقدار Guid.Empty است. اگر هر مقدار دیگری بجای این‌ها به فیلدهای فوق انتساب داده شوند، از آن‌ها استفاده می‌شود.

ج) تولید داده‌های خودکار در هر دو حالت Add و Update
تولید داده‌ها در حالت‌های Add و Update به این معنا است که یک چنین خواصی، همواره با فراخوانی متد SaveChanges، دارای مقدار خودکار جدیدی خواهند شد و نیازی نیست در کدها مقدار دهی شوند. برای مشخص سازی این نوع خواص، از ویژگی DatabaseGenerated با مقدار Computed و یا متد ValueGeneratedOnAddOrUpdate در حالت Fluent API می‌توان استفاده کرد:
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime LastUpdated { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
       .Property(b => b.LastUpdated)
       .ValueGeneratedOnAddOrUpdate();
}
همانطور که پیشتر نیز عنوان شد، تولید خودکار مقادیر فیلدها فقط در حالت‌های int و Guid انجام می‌شود (که برای مثال SQL Server از آن‌ها پشتیبانی می‌کند). در مثال فوق، خاصیت LastUpdated از نوع DateTime اینگونه تعریف شده‌است و SQL Server برای یک چنین فیلدهای خاصی، مقدار خودکاری را تولید نکرده و به دنبال مقدار پیش فرض آن می‌گردد. بنابراین در اینجا نیز باید مشخص سازی HasDefaultValueSql("getdate()") را که در قسمت قبل عنوان کردیم، صراحتا در قسمت تنظیمات Fluent API ذکر و تنظیم کرد.

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


خواص محاسباتی (Computed Columns) و تفاوت آن‌ها با DatabaseGeneratedOption.Computed

خواص محاسباتی (Computed Columns)، خواصی هستند که مقادیر آن‌ها در بانک اطلاعاتی محاسبه می‌شوند و کاملا متفاوت هستند با DatabaseGeneratedOption.Computed که مفهوم دیگری دارد. DatabaseGeneratedOption.Computed به این معنا است که این فیلد خاص، با هر بار فراخوانی SaveChanges باید مقدار محاسبه شده‌ی جدیدی را داشته باشد و روش تولید این مقدار خودکار، یا بر اساس Guidهای سری است، یا توسط فیلدهای خود افزاینده‌ی عددی و یا از طریق مقادیر پیش فرضی مانند getdate در حین ثبت یا به روز رسانی، مقدار دهی می‌شوند. اما خواص محاسباتی، یکی از امکانات «گزارشگیری سریع» SQL Server هستند و به نحو ذیل، تنها توسط Fluent API قابل تنظیم می‌باشند:
public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string DisplayName { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
          modelBuilder.Entity<Person>()
              .Property(p => p.DisplayName)
               .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
     }
 }
در اینجا فیلد DisplayName یک فیلد محاسباتی بوده و از حاصل جمع دو فیلد دیگر در سمت دیتابیس تشکیل می‌شود. این نگاشت و محاسبه چون در سمت بانک اطلاعاتی انجام می‌شود، بازدهی بیشتری دارد نسبت به حالتی که ابتدا دو فیلد به کلاینت منتقل شده و سپس در این سمت جمع زده شوند.


امکان تعریف Sequence در EF Core 1.0

Sequence قابلیتی است که به SQL Server 2012 اضافه شده‌است و توضیحات بیشتر آن‌را در مطلب «نحوه ایجاد Sequence و استفاده آن در Sql Server 2012» می‌توانید مطالعه کنید.
در EF Core، امکان مدلسازی Sequence نیز پیش بینی شده‌است. آن‌ها به صورت پیش فرض در مدل‌ها ذکر نمی‌شوند و همچنین وابستگی به جدول خاصی ندارند. به همین جهت امکان تعریف آن‌ها صرفا توسط Fluent API وجود دارد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
     modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared") 
           .StartsAt(1000).IncrementsBy(5);

     modelBuilder.Entity<Order>()
         .Property(o => o.OrderNo)
         .HasDefaultValueSql("NEXT VALUE FOR shared.OrderNumbers");
}
پس از اینکه یک Sequence  تعریف شد، می‌توان برای نمونه از آن جهت تولید مقادیر پیش فرض ستون‌ها استفاده کرد.
در مثال فوق، ابتدا یک Sequence نمونه به نام OrderNumbers تعریف شده‌است که از عدد 1000 شروع شده و واحد افزایش آن 5 است. سپس از این نام در قسمت مقدار پیش فرض ستون OrderNo استفاده شده‌است.

و یا از Sequence ‌ها می‌توان برای تعیین مقدار پیش فرض Primary key بجای حالت identity خود افزایش یابنده استفاده کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasSequence<int>("PrimaryKeyWithSequenceSequence");
    modelBuilder.Entity<PrimaryKeyWithSequence>(entity =>
     {
       entity.Property(e => e.PrimaryKeyWithSequenceId).HasDefaultValueSql("NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]");
     });
}
در اینجا یک توالی از نوع int تعریف شده و سپس هربار که قرار است رکوردی درج شود، مقدار id آن به صورت خودکار از طریق کوئری Select NEXT VALUE FOR
[PrimaryKeyWithSequenceSequence] دریافت و سپس بجای فیلد id درج می‌شود.

به این روش الگوریتم Hi-Low هم می‌گویند که یکی از مهم‌ترین اهداف آن داشتن یک سری Id منحصربفرد، جهت بالابردن سرعت insertها در یک batch است. در حالت عادی insertها، ابتدا یک insert انجام می‌شود، سپس کوئری گرفته شده و آخرین Id درج شده به کلاینت بازگشت داده می‌شود. این روش، برای انجام تنها یک insert، سریع است. اما برای batch insert، به شدت کارآیی پایینی دارد. به همین جهت دسترسی به بازه‌ای از اعداد منحصربفرد، پیش از شروع به insert تعداد زیادی رکورد، سرعت نهایی کار را بالا می‌برد.


نحوه‌ی تعریف ایندکس‌ها در EF Core 1.0

برای افزودن ایندکس‌ها به EF Core 1.0، تنها روش میسر، استفاده از Fluent API است (و برخلاف EF 6.x از روش data annotations فعلا پشتیبانی نمی‌کند؛ هرچند API جدید آن نسبت به EF 6.x بسیار واضح‌تر است و با ابهامات کمتر).
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
      modelBuilder.Entity<Blog>()
          .HasIndex(b => b.Url)
          .HasName("Index_Url");
اگر قسمت HasName را ذکر نکنید، نام آن <IX_<type name>_<property name درنظر گرفته می‌شود و برای اینکه ایندکس منحصربفردی را تعریف کنید، می‌توان متد IsUnique را به انتهای این زنجیره اضافه کرد:
 modelBuilder.Entity<Blog>().HasIndex(b => b.Url).HasName("Index_Url").IsUnique();
همچنین می‌توان همانند composite keys، در اینجا نیز ترکیبی از خواص را به صورت یک ایندکس معرفی نمود:
modelBuilder.Entity<Person>()
   .HasIndex(idx => new { idx.FirstName, idx.LastName })
   .IsUnique();
در این حالت اگر HasName ذکر نشود، نام آن همانند الگویی است که پیشتر عنوان شد؛ با این تفاوت که قسمت property name آن، جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ خواهد بود.

یک نکته: اگر از پروایدر SQL Server استفاده می‌کنید، می‌توان متد الحاقی ویژه‌ای را به نام ForSqlServerIsClustered نیز برای تعریف clustered indexes، در این زنجیره ذکر کرد.


امکان تعریف Alternate Keys در EF Core 1.0

به Unique Constraints در EF Core، نام Alternate Keys را داده‌اند و این مورد نیز تنها از طریق Fluent API قابل تنظیم است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Car>()
     .HasAlternateKey(c => c.LicensePlate)
     .HasName("AlteranteKey_LicensePlate");
}
برای یک Alternate Key به صورت خودکار هم ایندکس ایجاد می‌شود و هم اینکه این ایندکس منحصربفرد خواهد بود.
اگر متد HasName در اینجا ذکر نشود، نام پیش فرض آن  <type name>_<property name> خواهد بود و اگر همانند composite keys و یا ایندکس‌های ترکیبی، چند خاصیت ذکر شوند، قسمت property name به جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ تنظیم می‌شود.
برای نمونه اگر یک Alternate Key ترکیبی را به صورت ذیل تعریف کنیم:
modelBuilder.Entity<Person>()
     .HasAlternateKey(x => new { x.FirstName, x.LastName });
در قسمت مهاجرت‌هایی که قرار است به بانک اطلاعاتی اعمال شوند، به یک UniqueConstraint ترجمه می‌شود:
 table.UniqueConstraint("AK_Persons_FirstName_LastName", x => new { x.FirstName, x.LastName });


سؤال: یک Unique Constraint با Unique Index چه تفاوتی دارد؟

در پشت صحنه، پیاده سازی یک Unique Constraint با Unique Index تفاوتی ندارند. فقط از دیدگاه روشن‌تر شدن مقصود، استفاده‌ی از Unique Constraint ترجیح داده می‌شود.
البته از دیدگاه بانک اطلاعاتی پیاده سازی کننده نیز برای نمونه SQL Server، این تفاوت‌ها وجود دارند:
الف) یک Unique Constraint را نمی‌توان غیرفعال کرد؛ برخلاف Unique Indexها.
ب) Unique Constraint‌ها موارد اضافه‌تری را مانند FILLFACTOR و IGNORE_DUP_KEY نیز می‌توانند تنظیم کنند.
ج) امکان تعریف فیلترها برای Unique Indexها وجود دارد؛ برخلاف Unique Constraint ها.

که البته از دیدگاه EF، این سه مورد اهمیتی ندارند و بیشتر روشن‌تر شدن مقصود، هدف اصلی آن‌ها است.