مطالب
نگاشت اشیاء در AutoMapper توسط Attribute ها #2 - تبدیل ویژگی‌ها به نگاشت
پس از معرفی ویژگی‌های لازم، در ادامه با نحوه‌ی تبدیل این ویژگی‌ها به معادل نگاشت آن‌ها در automapper خواهم پرداخت.
متد زیر هسته‌ی اصلی عملیات است و کلیه‌ی نگاشت‌های لازم را انجام می‌دهد. این متد وظیفه‌ی تبدیل نگاشت‌ها را دارد. نگاشت‌هایی که با Attributes مشخص شده‌اند:
 public static void Initialize(Assembly assembly)
 {
     //register global convertors.
     AutoMapper.Mapper.CreateMap<DateTime, string>().ConvertUsing<DateTimeToPersianDateTimeConverter>();

     var typesToMap = from t in assembly.GetTypes()
         let attr = t.GetCustomAttribute<MapFromAttribute>()
         where attr != null
         select new {SourceType = attr.SourceType, Destination = t, Attribute = attr};

     foreach (var map in typesToMap)
     {
         AutoMapper.Mapper.CreateMap(map.SourceType, map.Destination)             
             .DoMapForMemberAttribute() // for different property names in source and destination
             .DoIgnoreMapAttribute()// ignore specified properties
             .DoUseValueResolverAttribute()// set value resolvers
             .DoIgnoreAllNonExisting()// its have to be the latest.
             ;
     } //endeach
     AutoMapper.Mapper.AssertConfigurationIsValid();
 }
ورودی این متد اسملبی مربوط به ویوومدل می‌باشد (برای زمانیکه ویوومدل‌ها در اسمبلی دیگری باشند).
در سطر اول، اقدام به رجیستر کردن کلیه‌ی مبدل‌های سراسری می‌کنیم. در این سطر مبدل تاریخ به کوچی خورشیدی مورد استفاده قرار گرفته است. سپس در اسمبلی داده شده، کلیه نوع‌هایی که ویژگی MapFromAttribute را دارند، یافته و جدا می‌کنیم. در حلقه‌ی foreach ابتدا نگاشت نوع مبدأ و مقصد را انجام می‌دهیم. خروجی این متد از نوع IMappingExpression است. گر چه این اینترفیس برای تغییر بسته است، ولی قابل توسعه می‌باشد و عملیات را توسط متدهای الحاقی انجام می‌دهیم(اصل OCP).
اگر به نحوه‌ی نامگذاری متدهای الحاقی تعریف شده دقت کرده باشید، تنها کلمه‌ی Do به ابتدای نام ویژگی‌ها اضافه شده است
.

متد الحاقی DoMapFormMemberAttribute
public static IMappingExpression DoMapForMemberAttribute(this IMappingExpression expression)
{
    var ok =
        from p in expression.TypeMap.DestinationType.GetProperties()
        let attr = p.GetCustomAttribute<MapForMemberAttribute>()
        where attr != null
        select new {AttributeValue = attr, PropertyName = p.Name};

     foreach (var property in ok)
     {
         expression.ForMember(property.PropertyName, 
             opt => opt.MapFrom(property.AttributeValue.MemberToMap));
     }
    return expression;
}
هر IMappingExpression دارای امکاناتی برای نگهداری و انجام فعالیت بر روی یک نگاشت می‌باشد. در کوئری ابتدای متد، کلیه‌ی پروپرتی‌هایی را که دارای ویژگی MapForMemeberAttribute می‌باشند، یافته و جدا می‌کنیم. این پروپرتی‌ها از نظر معادل اسمی در نوع مبدأ و مقصد متفاوت هستند. سپس در حلقه، کار اتصال پروپرتی مبدأ و مقصد صورت می‌گیرد.

متد الحاقی DoIgnoreMapAttribute  
public static IMappingExpression DoIgnoreAttribute(this IMappingExpression expression)
{
    foreach (var property in
        expression.TypeMap.DestinationType.GetProperties()
        .Where(x => x.GetCustomAttribute<IgnoreMapAttribute>() != null))
    {
        expression.ForMember(property.Name, opt => opt.Ignore());
    }
    return expression;
}
این متد کلیه‌ی پروپرتی‌هایی را که دارای ویژگی IgnoreMapAttribute باشند، از گردونه‌ی نگاشت automapper خارج می‌کند. به عنوان مثال پروپرتی Password در ویوومدل مربوط به تغییر گذرواژه را نظر بگیرید. این پروپرتی نباید مقدار معادلی در شیء EF داشته باشد. از طرفی هم باید در ویوو وجودداشته باشد. با استفاده از این ویژگی هیچ نگاشتی انجام نمی‌شود و می‌توان تضمین کرد که گذرواژه به ویوومدل و ویوو راه پیدا نمی‌کند.

متد الحاقی DoUseValueResolverAttribute 
public static IMappingExpression DoUseValueResolverAttribute(this IMappingExpression expression)
{
    var ok =
        from p in expression.TypeMap.DestinationType.GetProperties()
        let attr = p.GetCustomAttribute<UseValueResolverAttribute>()
        where attr != null
        select new {AttributeValue = attr, PropertyName = p.Name};

    foreach (var property in ok)
    {
        expression.ForMember(property.PropertyName,
            opt => opt.ResolveUsing(property.AttributeValue.ValueResolver));
    }
    return expression;
}
به شیوه‌ی قبل، ابتدا نوع هایی را که دارای ویژگی UseValueResolverAttribute باشند، یافته و جدا می‌کنیم. سپس در حلقه، کار نگاشت متناظر در automapper انجام می‌گیرد. لازم به ذکر است که متد opt.ResolveUsing یک شیء با کارآیی (can do) اینترفیس IValueResolver را به عنوان آرگومان می‌گیرد.

متد الحاقی DoIgnoreAllNonExisting  
public static IMappingExpression DoIgnoreAllNonExisting(this IMappingExpression expression)
{
    var attr = expression.TypeMap.DestinationType.GetCustomAttribute<MapFromAttribute>();
    
    if (attr?.IgnoreAllNonExistingProperty == false)//instead of if(attr == null || attr.IgnoreAllNonExistingProperty == false)
        return expression;
    
    foreach (var property in expression.TypeMap.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}
این متد برحسب پرچم تعیین شده در هنگام بکارگیری ویژگی MapFromAttribute رفتار می‌کند. به این صورت که اگر موقع تعریف، مقدار IgnoreAllNonExistingProperty را صحیح اعلام کنیم، تمام پروپرتی‌های مقصد را که معادل اسمی در مبدأ نداشته باشند و همچنین هیچگونه تنظیمی جهت مشخص سازی تکلیف نگاشت آن‌ها صورت نگرفته باشد، از گردونه‌ی نگاشت Automapper خارج می‌کند.

توضیح تکمیلی:
پس از تنظیم کلیه‌ی نگاشت‌ها در automapper جهت اطمینان از صحت تنظیمات، فراخوانی متد AutoMapper.Mapper.AssertConfigurationIsValid الزامی است. یکی از عواملی که باعث شکست این متد می‌شود، وجود پروپرتی‌هایی در نوع مقصد است، بطوریکه معادل اسمی در نوع مبدأ نداشته باشند و یا تنظیمی جهت مشخص سازی نگاشت آن انجام نشده باشد (پروپرتی که قابل نگاشت نباشد). در حقیقت این شکست بسیار مفید است. به این صورت که اگر این شکست صورت نگیرد در حین نگاشت مقادیر، باید از null یا مقدار default بدون اطلاع برنامه نویس برای مقداردهی پروپرتی استفاده کند و این یک حالت نامعلوم شیء است. اگر می‌خواهید این پروپرتی‌ها مقدار پیشفرضی بگیرند و همچنین باعث شکست عملیات هم نشوند، باید بطور صریح این موضوع را اعلام کنید. این اعلام یا باید به همین روش صورت بگیرد یا باید از ویژگی IgnorMapAttribute استفاده شود. تنها تفاوت این دو، نحوه‌ی اعمال تنظیم می‌باشد. IgnorMapAttribute باید روی تک تک پروپرتی‌های مدنظر قرار گیرد، ولی در روش اول تنها کافیست که مقدار true تنظیم گردد. به‌نظر استفاده از IgnoreMapAttribute باعث طولانی شدن کدها می‌شود؛ اما توصیه می‌شود که از همین شیوه استفاده کنید.

تا اینجا کدهای مورد نیاز نوشته شدند. در ادامه به ارائه‌ی یک مثال برای نگاشت اشیاء در Automapper توسط Attributeها می‌پردازم.
مدل ساده‌ی زیر را در نظر بگیرید:
public class Student
{
    public virtual int Id { set; get; }
    public virtual string Name { set; get; }
    public virtual string Family { set; get; }
    public virtual string Email { set; get; }
    public virtual DateTime RegisterDateTime { set; get; }
    public virtual ICollection<Book> Books { set; get; }
}
public class Book
{
    public virtual int Id { set; get; }
    public virtual string Name { set; get; }
    public virtual DateTime BorrowDateTime { set; get; }
    public virtual DateTime ExpiredDateTime { set; get; }
    public virtual decimal Price { set; get; }
    [ForeignKey("StudentIdFk")]
    public virtual Student Student { set; get; }
    public virtual int StudentIdFk { set; get; }
}
با ویوومدل متناظر ذیل:
[MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)]
public class AdminStudentViewModel
{
    // [IgnoreMap]
    public int Id { set; get; }

    [MapForMember("Name")]
    public string FirstName { set; get; }

    [MapForMember("Family")]
    public string LastName { set; get; }

 [IgnoreMap]  public string Email { set; get; } [MapForMember("RegisterDateTime")] public string RegisterDateTimePersian { set; get; } [UseValueResolver(typeof (BookCountValueResolver))] public int BookCounts { set; get; } [UseValueResolver(typeof (BookPriceValueResolver))] public decimal TotalBookPrice { set; get; } };
در تنظیم ویژگی MapFromAttribute ابتدا نوع مبدأ (Student) را مشخص کردیم و بعد صراحتاً گفتیم که از نگاشت پروپرتی‌های بلاتکلیف صرف نظر کند و همچنین پرچم انتقال Data Annotation‌های EF به ویوومدل را هم برافراشتیم. توسط MapForMember پروپرتی FirstName را به پروپرتی Name در مبدأ تنظیم کردیم و LastName را به Family. همچنین Email را بصورت صریح از نگاشت شدن منع کردیم. پروپرتی BookCounts تعداد کتاب‌ها را محاسبه می‌کند و TotalBookPrice قیمت کلیه‌ی کتاب‌ها را. برای این موارد از تأمین کننده‌ی داده (Value Resolver) استفاده کردیم. این تأمین کننده‌ها می‌توانند اینچنین پیاده سازی شوند:
public class BookCountValueResolver : ValueResolver<Student, int>
{
    protected override int ResolveCore(Student source) => source.Books.Count;
};
public class BookPriceValueResolver : ValueResolver<Student, decimal>
{
    protected override decimal ResolveCore(Student source) => source.Books.Sum(b => b.Price);
};
نحوه‌ی پیکربندی و مشاهده‌ی نتایج را در یک برنامه‌ی تحت کنسول پیاده سازی کردم. متد Main آن می‌تواند اینچنین باشد:
static void Main(string[] args)
{
    var assemblyToLoad = Assembly.GetAssembly(typeof (AdminStudentViewModel));//get assembly
    global::AttributesForAutomapper.Configuration.Initialize(assemblyToLoad);//init automaper
    IList<Student> lst;
    using (var context = new MySampleContext())
    {
        lst = context.Students.Include(x => x.Books).ToList();
    }
    foreach (var student in lst)
        {
            WriteLine( $"[{student.Id}]*\n{student.Name} {student.Family}.\nmailto:{student.Email}.\nRegistered at'{student.RegisterDateTime}'");
            foreach (var book in student.Books)
                WriteLine($"\tBook name:{book.Name}, Book price:{book.Price}");
        }
    
    var lstViewModel = AutoMapper.Mapper.Map<IList<Student>, IList<AdminStudentViewModel>>(lst);
    foreach (var adminStudentViewModel in lstViewModel)
    {
        WriteLine(
            $"[{adminStudentViewModel.Id}]*\n\t{adminStudentViewModel.FirstName} {adminStudentViewModel.LastName}.\n\t" +
            $"mailto:{adminStudentViewModel.Email}.\n\tRegistered at'{adminStudentViewModel.RegisterDateTimePersian}'\n\t" +
            $"Book Counts: {adminStudentViewModel.BookCounts} with total price of {adminStudentViewModel.TotalBookPrice}");
    }
    WriteLine("Press any key to exit...");
    ReadKey();
}
ابتدا اسمبلی مربوط به ویوومدل‌ها را مشخص می‌کنیم. سپس این اسمبلی را جهت تبدیل ویژگی‌ها به نگاشت‌های معتبر automapper به متد Initialize ارسال می‌کنیم. تنها بکار بردن همین دوسطر برای اعمال تنظیم‌ها مورد نیاز می‌باشد. بعد از اجرای موفق متد Initialize، نگاشت‌های اشیاء آماده هستند.
نمونه‌ی خروجی:
[1]*
Morteza Raeisi.
mailto:MrRaeisi@outlook.com.
Registered at'23/08/1392 19:11:43'    // I'm using Windows 10 with Persian calendar as default, On other OS or calendar settings, this value is different.
        Book name:AutoMapper Attr, Book price:1000.00
        Book name:Second Book, Book price:2500.00
        Book name:Hungry Book, Book price:2500.00
...
[1]*
Morteza Raeisi. //MapForMemebers
mailto:.  // IgnoreMap
Registered at'1392/08/23 19:11' // Convert using
Book Counts: 3 with total price of 6000.00  // Value resolvers
...
دریافت کدها + مثال
نظرات مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت پنجم - اعتبارسنجی تنظیمات آغازین برنامه
در یک پروژه nullable ref types فعال است و در دیگری خیر:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Nullable>enable</Nullable>
  </PropertyGroup>
اگر می‌خواهید هر دو مثل هم عمل کنند، باید به اخطارهای کامپایلر دقت کنید و Name را nullable تعریف کنید:
public class Person
{
     public string? Name { get; set; }
     public string? Family { get; set; }
نظرات اشتراک‌ها
استفاده از Auto-ignore در AutoMapper
نکته 
اگر از Profile برای پیکربندی نگاشت‌ها استفاده میکنید ، مطلب گفته شده در سایت ثالث جواب نخواهد داد. به روش زیر عمل کنید
  public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
        {
            foreach (var property in expression.TypeMap.GetUnmappedPropertyNames())
            {
                expression.ForMember(property, a => a.Ignore());
            }
            return expression;
        }
و در کلاس Profile 
 public class UserProfile : Profile
    {
        protected override void Configure()
        {
            
            CreateMap<RegisterViewModel, ApplicationUser>()
                .IgnoreAllNonExisting();
               
        }

        public override string ProfileName
        {
            get { return this.GetType().Name; }
        }
    }

اشتراک‌ها
ساده ترین راه برای پیاده سازی GetHashCode

In .NET Core 2.1 a new struct was added. It’s called  System.HashCode  and it makes generating hash codes super convenient. Have a look at this class. 

class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }

public override int GetHashCode() => HashCode.Combine(FirstName, LastName);
}
ساده ترین راه برای پیاده سازی GetHashCode
مطالب
چگونگی رسیدگی به Null property در AutoMapper

AutoMapper کتابخانه‌ای برای نگاشت اطلاعات یک شیء به شی‌ءایی دیگر به صورت خودکار می‌باشد.

در این مقاله چگونگی رسیدگی به Null property را در AutoMapper   بررسی خواهیم کرد. فرض کنید شیء منبع دارای یک خاصیت Null  است و می‌خواهید به وسیله Automaper شیء منبع را به مقصد نگاشت نمایید. اما می‌خواهید در صورت Null بودن شیء مبدا، یک مقدار پیش فرض برای شیء مقصد در نظر گرفته شود .

برای نمونه کلاسuser   را که در آن از کلاس Address یک خاصیت تعریف شده، در نظر بگیرید. اگر مقدار آدرس در شیء منبع خالی بود شاید شما بخواهید مقدار آن را به صورت empty string و یا با یک مقدار پیش فرض در مقصد مقدار دهی کنید.

همانند مثال زیر : 

public class UserSource
{
  public Address Address{get;set;}
}
 
public class UserDestination
{
  public string Address{get;set;}
}
ابتدا نگاشت‌ها را تعریف می‌کنیم:
AutoMapper.Mapper.CreateMap<UserSource, UserDestination>()
          .ForMember(dest => dest.Address
          , opt => opt.NullSubstitute("Address not found")
          );
کد بالا نشان دهنده تبدیل Address به Address است ولی دارای متد اختیاری NullSubstitute می‌باشد و بیانگر این است که اگر آدرس شیء منبع Null بود، مقدار پیش فرضی را برای شیء مقصد در نظر بگیرد. در انتها  می‌توان نگاشت را در برنامه متناسب با نیاز خود انجام داد:
var model = AutoMapper.Mapper.Map<UserSource, UserDestination>(user);
var models = AutoMapper.Mapper.Map<IEnumerable<UserSource>, IEnumerable<UserDestination>>(users);
نظرات مطالب
معرفی System.Text.Json در NET Core 3.0.
یک نکته‌ی تکمیلی: نوشتن تبدیلگرهای نوع‌ها برای System.Text.Json

System.Text.Json، در حال حاضر از مفهومی به نام type coercion/inference، پشتیبانی نمی‌کند. type coercion یعنی تبدیل یک مقدار، به مقداری دیگر که به صورت مستقیم قابل انتساب به یکدیگر نیستند. برای مثال اگر رشته‌ی "true" را درنظر بگیریم، قابلیت انتساب به یک خاصیت از نوع bool را ندارد. برای یک چنین مواردی در این API جدید، باید تبدیلگر نوشت.
یک مثال:
using System.Collections.Generic;
using System.Text.Json;

namespace JsonTests
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool IsInStock { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {            
         var products = JsonSerializer.Deserialize<List<Product>>("[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]");
        }
    }
}
در این مثال، خاصیت IsInStock از نوع bool است، اما مقداری را که باید از طریق متد Deserialize دریافت کنیم، یک رشته‌ی bool ای است که قابل انتساب به bool نیست. در این حالت اگر برنامه را اجرا کنیم به استثنای زیر خواهیم رسید:
An unhandled exception of type 'System.Text.Json.JsonException' occurred in System.Text.Json.dll
Inner exceptions found, see $exception in variables window for more details.
Innermost exception System.InvalidOperationException : Cannot get the value of a token type 'String' as a boolean.
برای رفع این مشکل، می‌توان تبدیلگر زیر را تدارک دید:
    public class BooleanConverter : JsonConverter<bool>
    {
        public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var value = reader.GetString();
            if (value.Equals("true", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("yes", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("1", StringComparison.Ordinal))
            {
                return true;
            }

            if (value.Equals("false", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("no", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("0", StringComparison.Ordinal))
            {
                return false;
            }

            throw new NotSupportedException($"`{value}` can't be converted to `bool`.");
        }

        public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
        {
            switch (value)
            {
                case true:
                    writer.WriteStringValue("true");
                    break;
                case false:
                    writer.WriteStringValue("false");
                    break;
            }
        }
    }
برای نوشتن یک تبدیلگر bool، کلاس مرتبط، باید <JsonConverter<bool را پیاده سازی کند. کلاس JsonConverter نیز به صورت زیر تعریف شده‌است:
    public abstract class JsonConverter<T> : JsonConverter
    {
        protected internal JsonConverter();

        public override bool CanConvert(Type typeToConvert);
        public abstract T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options);
        public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options);
    }
و پیاده سازی دو متد Read و Write آن الزامی است.
در متد Read آن، مقدار رشته‌ای دریافت شده‌ی از منبع داده، در اختیار ما قرار می‌گیرد. سپس باید بر اساس این مقدار، مقدار متناظری را از نوع T که در اینجا bool است، بازگشت دهیم. برای مثال اگر یکی از مقادیر رشته‌ای true ،yes و 1 را دریافت کردیم، بجای آن true را بازگشت می‌دهیم.

اکنون برای استفاده‌ی از آن خواهیم داشت:
var options = new JsonSerializerOptions();
options.Converters.Add(new BooleanConverter());
var products = JsonSerializer.Deserialize<List<Product>>(
   "[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]",
   options);
و یا روش دیگر انجام اینکار، استفاده از ویژگی JsonConverter، برای معرفی تبدیلگر تهیه شده‌است:
[JsonConverter(typeof(BooleanConverter))]
public bool IsInStock { get; set; }

از این تبدیلگر برای حالت Serialize نیز می‌توان استفاده کرد:
var options  = new JsonSerializerOptions() {WriteIndented = true };
options.Converters.Add(new BooleanConverter());
var data = JsonSerializer.Serialize<List<Product>>(productList, options);
نظرات مطالب
معرفی System.Text.Json در NET Core 3.0.
یک نکته‌ی تکمیلی
در NET Core 3.0 Preview 7. امضای این کلاس‌ها و متدها به صورت زیر تغییر کرده‌است و متد Parse به Deserialize تبدیل شده‌است:
namespace System.Text.Json
{
    public static class JsonSerializer
    {
        public static object Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options = null);
        public static object Deserialize(string json, Type returnType, JsonSerializerOptions options = null);
        public static TValue Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonSerializerOptions options = null);
        public static TValue Deserialize<TValue>(string json, JsonSerializerOptions options = null);
        public static object Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerOptions options = null);
        public static TValue Deserialize<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options = null);
        public static ValueTask<object> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
        public static ValueTask<TValue> DeserializeAsync<TValue>(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
        public static string Serialize(object value, Type inputType, JsonSerializerOptions options = null);
        public static string Serialize<TValue>(TValue value, JsonSerializerOptions options = null);
        public static void Serialize(Utf8JsonWriter writer, object value, Type inputType, JsonSerializerOptions options = null);
        public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options = null);
        public static Task SerializeAsync(Stream utf8Json, object value, Type inputType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
        public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
        public static byte[] SerializeToUtf8Bytes(object value, Type inputType, JsonSerializerOptions options = null);
        public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonSerializerOptions options = null);
    }
}
همچنین JsonConverter نیز به تنظیمات آن اضافه شده‌است:
namespace System.Text.Json
{
    public sealed class JsonSerializerOptions
    {
        public JsonSerializerOptions();

        public bool AllowTrailingCommas { get; set; }
        public IList<JsonConverter> Converters { get; }
        public int DefaultBufferSize { get; set; }
        public JsonNamingPolicy DictionaryKeyPolicy { get; set; }
        public bool IgnoreNullValues { get; set; }
        public bool IgnoreReadOnlyProperties { get; set; }
        public int MaxDepth { get; set; }
        public bool PropertyNameCaseInsensitive { get; set; }
        public JsonNamingPolicy PropertyNamingPolicy { get; set; }
        public JsonCommentHandling ReadCommentHandling { get; set; }
        public bool WriteIndented { get; set; }

        public JsonConverter GetConverter(Type typeToConvert);
    }
}
با این تعریف:
namespace System.Text.Json.Serialization
{
    public abstract class JsonConverter
    {
        public abstract bool CanConvert(Type typeToConvert);
    }
}
نظرات مطالب
EF Code First #7
سلام وحید جان ممنون از این همه لطف

من یک پروژه را با CodeFirst شروع کردم اما یه جایی اشتباه کردم فکر کنم اشتباهم توی یکی از Mapping‌ها باشه. اگه لطف کنید ببینید مشکل چیه.بدون استفاده از Mapping مشکلی نیست و دیتا بیس با روابطی که میخوام ایجاد میشه اما وقتی از Mapping استفاده میکنم با این خطا مواجه میشم:
{"Sequence contains more than one matching element"} 
چندتا کلاس‌ها به شکل زیر هست:
public class Driver
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string NationalCode { get; set; }
        public string CellPhone { get; set; }
        public string LicenseNumber { get; set; }
        public bool IsDriverAssistance { get; set; }

        [InverseProperty("Driver")]
        public virtual ICollection<Transference> Transferences { get; set; }
        [InverseProperty("DriverAssistance")]
        public virtual ICollection<Transference> TransferencesForAssistance { get; set; }
        [InverseProperty("Driver")]
        public virtual ICollection<Tanker> Tankers { get; set; }
        [InverseProperty("DriverAssistance")]
        public virtual ICollection<Tanker> TankersForAssistance { get; set; }

    }

 public class Transference
    {
        public string Id { get; set; }
        public DateTime Date { get; set; }
        public Int16 Lytrazh { get; set; }
        public bool IsEMS { get; set; }
        public DateTime LoadingDate { get; set; }
        public DateTime DeliveryDate { get; set; }
        [InverseProperty("Transferences")]
        public virtual Driver Driver { get; set; }
        [InverseProperty("TransferencesForAssistance")]
        public virtual Driver DriverAssistance { get; set; }
        public virtual TypeOfTanker TypesOfTanker { get; set; }
        public virtual Tanker Tanker { get; set; }
        public virtual Consumer Consumer { get; set; }

    }
فکر کنم مشکل از این کلاس زیر باشه:
 public class TransferenceConfig : EntityTypeConfiguration<Transference>
    {
        public TransferenceConfig()
        {
            // one-to-many
            this.HasRequired(x => x.Consumer)
                .WithMany(x => x.Transferences);

                // one-to-many
            this.HasRequired(x => x.TypesOfTanker)
                .WithMany(x => x.Transferences);

            // one-to-many
            this.HasRequired(x => x.Tanker)
                .WithMany(x => x.Transferences);

            // one-to-many
            this.HasRequired(x => x.Driver)
                .WithMany(x => x.Transferences);

            // one-to-many
            this.HasRequired(x => x.DriverAssistance)
                .WithMany(x => x.Transferences);

        }
    }
نظرات مطالب
طراحی و پیاده سازی زیرساختی برای مدیریت خطاهای حاصل از Business Rule Validationها در ServiceLayer
بسیار ممنون از شما بابت این مقاله
چند پیشنهاد داشتم :
1 - در مورد  Railway-oriented Programming   مقاله جداگانه تهیه شود.
2- برای تکمیل این بخش مطلبی در مورد  Either   تهیه شود. برای اطلاع بیشتر
3- استفاده از لایه Service باعث نقض قانون  encapsulation شده و بهتر است در برنامه‌های بزرگ از آن به این طریق استفاده نشود زیرا باعث complex و discovery کد و درنهایت بروز خطا در برنامه خواهد شد. مانند:
namespace EF_Sample07.DomainClasses
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}
namespace EF_Sample07.ServiceLayer
{
    public interface IProductService
    {
        void AddNewProduct(Product product);
    }
}
برای حل این مشکل باید از immutable ، value object  و  Shadow Properties کمک  گرفت
namespace EF_Sample07.DomainClasses
{
    public class Product : Entity
    {
        public Product ( ProductName name, decimal price)
        {
              this.Name=name;
              this.Price = price;
         }  
        private string _name ;
        public ProductName Name { get=> (ProductName)_name;  set=> _ name = value } //value object with Shadow Properties  
        public decimal Price { get;}
        
    }
}

و شاید به دلیل یک سری از دلایل بخواهیم در مدل برنامه قانون encapsulation رو نقض کنیم که اگر با این سناریو مواجه شدیم میتوانیم از مهندس ساخت اشیا یعنی Builder استفاده ببریم.
4- بد نیست جامعه dotnettips این سری از مقاله ها رو به فارسی برگردونه.
 
نظرات مطالب
شروع به کار با DNTFrameworkCore - قسمت 2 - طراحی موجودیت‌های سیستم
موجودیت طرف‌حساب
public class Party : Entity, INumberedEntity
{
    public const int MaxFirstNameLength = 50;
    public const int MaxLastNameLength = 50;
    public const int MaxDescriptionLength = 1024;

    public string Number { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Description { get; set; }
    //...
}
موجودیت مشتری
public class Customer : TrackableEntity, IAggregateRoot, IPassivable
{
    public bool IsActive { get; set; }
    public byte[] RowVersion { get; set; }
    //...
    public Party Party { get; set; }
}

موجودیت پرسنل
public class Personnel : TrackableEntity, IAggregateRoot, IPassivable
{
    public bool IsActive { get; set; }
    public byte[] RowVersion { get; set; }
    //...
    public Party Party { get; set; }
}

تنظیمات مرتبط با ارتباط آنها
builder.HasOne(c => c.Party).WithOne().HasForeignKey<Customer>(c => c.Id)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(p => p.Party).WithOne().HasForeignKey<Personnel>(p => p.Id)
    .OnDelete(DeleteBehavior.Restrict);