نظرات مطالب
EF Code First #10
هنگام استفاده از EF 4 بوسیله WCF، ما خطایی در یافت می‌کنیم با این عنوان:
underlying connection was closed. the connection was closed unexpectedly

جستجو شد و جاهای مختلف اینطور گفته شده که در هنگام استفاده از EF با WCF، باید lazy loading غیرفعال شه ، در غیر اینصورت حلقه ایجاد میشه! مثلا اینجا
حالا این سوال پیش میاد که علت این مسال چیه، آیا راه دیگه ای وجود نداره. و مهمتر اینکه غیر فعال کردن lazy loading کارایی برنامه رو پایین نمیاره؟

نظرات مطالب
ASP.NET MVC #13
سلام آقای نصیری. قبل از اینکه سوالمو بپرسم باید بگم ممنون از مطلب خوبتون
آقای نصیری همونطور که اینجا گفتین من توسط MetaData برای فیلد Username داخل پروژه RemoteValidation رو فعال کردم . برای Insert مشکلی نیست و درست عمل میکنه. اما موقعی که میخوام اطلاعات یک User رو ویرایش کنم، مجدد این اعتبار سنجی فعال میشه و میگه که این Username وجود داره. برای غیرفعال کردنش در ویرایش باید چیکار کنم؟ ممنون میشم راهنماییم کنید
نظرات مطالب
ASP.NET MVC #13
باید برای حالت ویرایش از یک ViewModel جدید و از یک متد اعتبار سنجی جدید که می‌تونه کوئری صحیح بر اساس کاربر جاری لاگین شده به سیستم و کاربرهای ثبت شده در بانک اطلاعاتی بگیره، استفاده کنید. غیرفعال کردنش معنی نداره. چون سمت سرور باید یکبار دیگر همین مراحل رو پیش از ثبت نهایی بررسی کنید و در صورت لزوم پیغام لازم رو به کاربر بدید.


مطالب
رفع مشکل تبدیلات UTC با تغییر اطلاعات TimeZone ایران در ویندوز
در سال جدید، حتی اگر گزینه‌ی daylight saving time را در تنظیمات ویندوز غیرفعال کنید:


با اجرای قطعه کد زیر که سعی می‌کند DateTimeOffset.UtcNow را بر اساس اطلاعات منطقه‌ی زمانی ایران، به زمان محلی تبدیل کند:
var iranStandardTime = TimeZoneInfo.GetSystemTimeZones()
                                       .FirstOrDefault(timeZoneInfo =>
                                                           timeZoneInfo.StandardName.Contains("Iran",
                                                            StringComparison.OrdinalIgnoreCase));
Console.WriteLine(iranStandardTime.BaseUtcOffset); // 03:30:00

var dateTime = DateTimeOffset.UtcNow;
var iranTime = TimeZoneInfo.ConvertTime(dateTime, iranStandardTime);
Console.WriteLine(iranTime); // 06/02/1402 12:34:23 ?.? +04:30 --> this is coming from the `adjustment rules`
باز هم در خروجی، درنظر گرفته شدن 4:30+ مشخص است و به همین جهت در تبدیل صورت گرفته، ساعت نهایی، یک ساعت جلوتر از ساعت واقعی خواهد بود. علت آن هم به مفهوم dynamic daylight saving time برمی‌گردد که اطلاعات بیشتر آن‌را در این مطلب می‌توانید مطالعه کنید.

دات نت نیز در پشت صحنه، همین اطلاعات را دریافت و تحت عنوان Adjustment rules، مورد استفاده قرار می‌دهد:


همانطور که مشاهده می‌کنید، آخرین گزینه‌ی پویای آن به بازه‌ی زمانی 1402 تا سال 9378 تنظیم شده‌است و در طی این بازه، دات نت هنوز همان 4:30+ را جهت تبدیلات منطقه‌ی زمانی ایران به رسمیت می‌شناسد؛ مگر اینکه در به‌روز رسانی‌های بعدی ویندوز، این مشکل برطرف شود و یا ... می‌توانید از این وصله، جهت رفع این مشکل استفاده کنید:


پس از افزودن اطلاعات این فایل رجیستری به ویندوز، نه فقط daylight saving time غیرفعال می‌شود، بلکه اجزای dynamic daylight saving time نیز از سال 2023 به بعد، غیرفعال خواهند شد و اینبار دات نت، سال 1401 را سال آخر تنظیمات ساعت تابستانی درنظر می‌گیرد:


مابقی موارد در اینجا ذکر نشده‌اند، چون به عنوان ورودی دارای daylight saving time غیرفعال تشخیص داده می‌شوند.

اکنون اگر مجددا همان مثال ابتدای بحث را اجرا کنیم، بجای 4:30، عدد 3:30 قابل مشاهده است.
 

پاسخ به بازخورد‌های پروژه‌ها
مشکل در راه اندازی
آخرین نکته‌ی ارسالی را هم در آن مطلب مطالعه کنید.
اگر حین ارائه‌ی برنامه‌ی خود فایل exe مربوط به nuget را ارائه ندهید، پیام خطای یافت نشدن آن‌را در حین Build مشاهده خواهید کرد. برای رفع آن تنها کافی است فایل NuGet.targets را گشوده و دریافت خودکار nuget.exe را فعال کنید:
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">true</DownloadNuGetExe>
این تنظیم به صورت پیش فرض غیرفعال است.  
مطالب
به روز رسانی ساده‌تر اجزاء ارتباطات در EF Code first به کمک GraphDiff
دو نوع حالت کلی کارکردن با EF وجود دارند: متصل و منقطع.
در حالت متصل مانند برنامه‌های متداول دسکتاپ، Context مورد استفاده در طول عمر صفحه‌ی جاری زنده نگه داشته می‌شود. در این حالت اگر شیءایی اضافه شود، حذف شود یا تغییر کند، توسط EF ردیابی شده و تنها با فراخوانی متد SaveChanges، تمام این تغییرات به صورت یکجا به بانک اطلاعاتی اعمال می‌شوند.
در حالت غیرمتصل مانند برنامه‌های وب، طول عمر Context در حد طول عمر یک درخواست است. پس از آن از بین خواهد رفت و دیگر فرصت ردیابی تغییرات سمت کاربر را نخواهد یافت. در این حالت به روز رسانی کلیه تغییرات انجام شده در خواص و همچنین ارتباطات اشیاء موجود، کاری مشکل و زمانبر خواهد بود.
برای حل این مشکل، کتابخانه‌ای به نام GraphDiff طراحی شده‌است که صرفا با فراخوانی متد UpdateGraph آن، به صورت خودکار، محاسبات تغییرات صورت گرفته در اشیاء منقطع و اعمال آن‌ها به بانک اطلاعاتی صورت خواهد گرفت. البته ذکر متد SaveChanges پس از آن نباید فراموش شود.


اصطلاحات بکار رفته در GraphDiff

برای کار با GraphDiff نیاز است با یک سری اصطلاح آشنا بود:

Aggregate root
گرافی است از اشیاء به هم وابسته که مرجع تغییرات داده‌ها به شمار می‌رود. برای مثال یک سفارش و آیتم‌های آن‌را درنظر بگیرید. بارگذاری آیتم‌های سفارش، بدون سفارش معنایی ندارند. بنابراین در اینجا سفارش aggregate root است.

AssociatedCollection/AssociatedEntity
حالت‌های Associated به GraphDiff اعلام می‌کنند که اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root نباید به روز رسانی شوند. در این حالت تنها ارجاعات به روز رسانی خواهند شد.
اگر خاصیت راهبری از نوع ICollection است، حالت AssociatedCollection و اگر صرفا یک شیء ساده است، از AssociatedEntity استفاده خواهد شد.

OwnedCollection/OwnedEntity
حالت‌های Owned به GraphDiff اعلام می‌کنند که جزئیات و همچنین ارجاعات اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root باید به روز رسانی شوند.


دریافت و نصب GraphDiff

برای نصب خودکار کتابخانه‌ی GraphDiff می‌توان از دستور نیوگت ذیل استفاده کرد:
 PM> Install-Package RefactorThis.GraphDiff


بررسی GraphDiff در طی یک مثال

مدل‌های برنامه آزمایشی، از سه کلاس ذیل که روابط many-to-many و one-to-many با یکدیگر دارند، تشکیل شده‌است:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace GraphDiffTests.Models
{
    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public virtual ICollection<Tag> Tags { set; get; } // many-to-many

        [ForeignKey("UserId")]
        public virtual User User { get; set; }
        public int UserId { get; set; }

        public BlogPost()
        {
            Tags = new List<Tag>();
        }
    }

    public class Tag
    {
        public int Id { set; get; }

        [StringLength(maximumLength: 450), Required]
        public string Name { set; get; }

        public virtual ICollection<BlogPost> BlogPosts { set; get; } // many-to-many

        public Tag()
        {
            BlogPosts = new List<BlogPost>();
        }
    }

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<BlogPost> BlogPosts { get; set; } // one-to-many
    }
}
- یک مطلب می‌تواند چندین برچسب داشته باشد و هر برچسب می‌تواند به چندین مطلب انتساب داده شود.
- هر کاربر می‌تواند چندین مطلب ارسال کند.

در این حالت، Context برنامه چنین شکلی را خواهد یافت:
using System;
using System.Data.Entity;
using GraphDiffTests.Models;

namespace GraphDiffTests.Config
{
    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<BlogPost> BlogPosts { get; set; }
        public DbSet<Tag> Tags { get; set; }


        public MyContext()
            : base("Connection1")
        {
            this.Database.Log = sql => Console.Write(sql);
        }
    }
}
به همراه تنظیمات به روز رسانی ساختار بانک اطلاعاتی به صورت خودکار:
using System.Data.Entity.Migrations;
using System.Linq;
using GraphDiffTests.Models;

namespace GraphDiffTests.Config
{
    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            if(context.Users.Any())
                return;

            var user1 = new User {Name = "User 1"};
            context.Users.Add(user1);

            var tag1 = new Tag { Name = "Tag1" };
            context.Tags.Add(tag1);

            var post1 = new BlogPost { Title = "Title...1", Content = "Content...1", User = user1};
            context.BlogPosts.Add(post1);

            post1.Tags.Add(tag1);

            base.Seed(context);
        }
    }
}
در متد Seed آن یک سری اطلاعات ابتدایی ثبت شده‌اند؛ یک کاربر، یک برچسب و یک مطلب.




در این تصاویر به Id هر کدام از رکوردها دقت کنید. از آن‌ها در ادامه استفاده خواهیم کرد.
در اینجا نمونه‌ای از نحوه‌ی استفاده از GraphDiff را جهت به روز رسانی یک Aggregate root ملاحظه می‌کنید:
            using (var context = new MyContext())
            {
                var user1 = new User { Id = 1, Name = "User 1_1_1" };
                var post1 = new BlogPost { Id = 1, Title = "Title...1_1", Content = "Body...1_1",
                    User = user1, UserId = user1.Id };
                var tags = new List<Tag>
                {
                    new Tag {Id = 1, Name = "Tag1_1"},
                    new Tag {Id=12, Name = "Tag2_1"},
                    new Tag {Name = "Tag3"},
                    new Tag {Name = "Tag4"},
                };
                tags.ForEach(tag => post1.Tags.Add(tag));

                context.UpdateGraph(post1, map => map
                    .OwnedEntity(p => p.User)
                    .OwnedCollection(p => p.Tags)
                    );

                context.SaveChanges();
            }
پارامتر اول UpdateGraph، گرافی از اشیاء است که قرار است به روز رسانی شوند.
پارامتر دوم آن، همان مباحث Owned و Associated بحث شده در ابتدای مطلب را مشخص می‌کنند. در اینجا چون می‌خواهیم هم برچسب‌ها و هم اطلاعات کاربر مطلب اول به روز شوند، نوع رابطه را Owned تعریف کرده‌ایم.
در حین کار با متد UpdateGraph، ذکر Idهای اشیاء منقطع از Context بسیار مهم هستند. اگر دستورات فوق را اجرا کنیم به خروجی ذیل خواهیم رسید:




- همانطور که مشخص است، چون id کاربر ذکر شده و همچنین این Id در post1 نیز درج گردیده است، صرفا نام او ویرایش گردیده است. اگر یکی از موارد ذکر شده رعایت نشوند، ابتدا کاربر جدیدی ثبت شده و سپس رابطه‌ی مطلب و کاربر به روز رسانی خواهد شد (userId آن به userId آخرین کاربر ثبت شده تنظیم می‌شود).
- در حین ثبت برچسب‌ها، چون Id=1 از پیش در بانک اطلاعاتی موجود بوده، تنها نام آن ویرایش شده‌است. در سایر موارد، برچسب‌های تعریف شده صرفا اضافه شده‌اند (چون Id مشخصی ندارند یا Id=12 در بانک اطلاعاتی وجود خارجی ندارد).
- چون Id مطلب مشخص شده‌است، فیلدهای عنوان و محتوای آن نیز به صورت خودکار ویرایش شده‌اند.

و ... تمام این کارها صرفا با فراخوانی متدهای UpdateGraph و سپس SaveChanges رخ داده‌است.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
GraphDiffTests.zip
مطالب
C# 7 - Discards
در تکمیل سری بررسی ویژگی‌های C# 7.0، ذکر ویژگی Discards نیز ضروری است. Discards به معنای متغیرهای محلی هستند که قابل انتساب بوده، اما قابل خواندن نیستند. دارای نامی نیستند و تنها توسط یک _ مشخص می‌شوند. در اینجا underscore یا _، یک واژه‌ی کلیدی است؛ مانند var و قابلیت خوانده شدن را ندارد (نمی‌تواند در سمت راست یک انتساب قرار گیرد).


علت وجود Discards در C# 7.0

گاهی از اوقات می‌خواهیم از مقادیر بازگشت داده شده‌ی توسط متدها، خصوصا آرگومان‌های out، صرفنظر کنیم:
 if (bool.TryParse("TRUE", out parsedValue)) { /* Do your stuff */ }
در این مثال، parsedValue برای ما اهمیتی نداشته و تنها می‌خواهیم از خروجی متد TryParse استفاده کنیم. به علاوه می‌خواهیم آن‌را در ادامه‌ی کد نیز قابل دسترسی کنیم. در C# 7.0 برای رسیدن به این مقصود از Discards استفاده می‌شود:
 if (bool.TryParse("TRUE", out bool _)) { /* Do your stuff */ }
در این‌حالت، _ ذکر شده، در بدنه‌ی if و یا حتی در خارج از آن، قابل دسترسی نیست و کامپایلر از آن صرفنظر می‌کند.


مکان‌هایی که می‌توان از Discards در آن‌ها استفاده کرد

پارامترهای از نوع out، یکی از مکان‌هایی هستند که می‌توان از Discards برای معرفی آن‌ها استفاده کرد. سایر کاربردهای آن شامل موارد ذیل  می‌شوند:
-در تعاریف tuples برای صرفنظر کردن از پارامتری خاص (Value Tuple deconstructions)
  var (arg1, _, _) = (1, 2, 3);
در اینجا فرض کنید خروجی یک متد که یک tuple را بر می‌گرداند، شامل سه عدد است که تنها به مورد اول آن‌ها علاقمندیم. در اینجا می‌توان سایر پارامترهای صرفنظر شده را توسط _ معرفی کرد.

-در pattern matching برای صرفنظر کردن از نوعی خاص
// in pattern matching
switch (input)
{
    case int number:
        Add(number);
        break;
    case object[] _:
        // ignore
        break;
}

- در پارامترهای delegates
// in delegate parameters
var usersWithPosts =  
    context.Users
           .Join(context.Posts,
                 user => user.UserId,
                 post => post.UserId,
                 (user, _) => user);



واژه‌ی کلیدی _

یک واژه‌ی کلیدی زمینه‌ای است
_ یک contextual keyword به حساب می‌آید؛ مانند var. به این معنا که اگر پیشتر در کدهای خود، یک متغیر محلی را به نام  _ تعریف کرده باشید، با _ بکار رفته‌ی به صورت discard، تداخل نخواهد کرد:
bool _ = false, v = false;
if (bool.TryParse("TRUE", out var _))
{
    /* Do your stuff */
    v = _;
}
در اینجا مقدار v داخل بدنه‌ی if، برخلاف تصور false خواهد بود. علت اینجا است که اولین _ تعریف شده یک local variable است و دومین _ یک discard به حساب می‌آید. بنابراین مهم نیست که رشته‌ی TRUE قابلیت parse به true را دارد و نتیجه‌ی true در _ پارامتر خروجی out قرار می‌گیرد. واژه‌ی کلیدی _ قابلیت خواندن را نداشته و فقط مقدار متغیر محلی _ تعریف شده‌ی پیش از discard، خوانده خواهد شد.

قابلیت تعریف مجدد را دارد
در مثال
 var (arg1, _, _) = (1, 2, 3);
هرچند واژه‌ی کلیدی _ به معنای تعریف یک متغیر محلی فقط نوشتنی است، اما قابلیت تکرار تعریف آن وجود دارد. یعنی در این حالت نیازی نیست تا دومین متغیر را با یک _ و سومین را با دو __ مشخص کرد. واژه‌ی کلیدی _ قابلیت تعریف و استفاده‌ی مجدد را دارد.