اشتراک‌ها
استفاده از فایل csv برای مقدار دهی اولیه‌ی جداول با استفاده از EF

protected override void Seed(SeedingDataFromCSV.Domain.LocationContext context)
{
    Assembly assembly = Assembly.GetExecutingAssembly();
    string resourceName = "SeedingDataFromCSV.Domain.SeedData.countries.csv";
    using (Stream stream = assembly.GetManifestResourceStream(resourceName))
    {
        using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
        {
            CsvReader csvReader = new CsvReader(reader);
            csvReader.Configuration.WillThrowOnMissingField = false;
            var countries = csvReader.GetRecords<Country>().ToArray();
            context.Countries.AddOrUpdate(c => c.Code, countries);
        }
    }

    resourceName = "SeedingDataFromCSV.Domain.SeedData.provincestates.csv";
    using (Stream stream = assembly.GetManifestResourceStream(resourceName))
    {
        using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
        {
            CsvReader csvReader = new CsvReader(reader);
            csvReader.Configuration.WillThrowOnMissingField = false;
            while (csvReader.Read())
            {
                var provinceState = csvReader.GetRecord<ProvinceState>();
                var countryCode = csvReader.GetField<string>("CountryCode");
                provinceState.Country = context.Countries.Local.Single(c => c.Code == countryCode);
                context.ProvinceStates.AddOrUpdate(p => p.Code, provinceState);
            }
        }
    }
}

یک کتابخانه مرتبط: EntityFramework.Seeder

استفاده از فایل csv برای مقدار دهی اولیه‌ی جداول با استفاده از EF
نظرات مطالب
معرفی واژه‌ی کلیدی جدید required در C# 11
یک نکته‌ی تکمیلی: بررسی صحت ساختار JSON دریافتی توسط واژه‌ی کلیدی required در زمان اجرای برنامه

فرض کنید یک Dto را به صورت زیر تعریف کرده‌اید و توسط یک API قرار است این اطلاعات را دریافت کنید:
public class OldCarDto
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public uint Horsepower { get; set; }
}
در این حالت هیچ کنترلی بر روی الزام به تکمیل تمام فیلدهای مورد نیاز وجود ندارد. برای مثال اگر JSON دریافتی به صورت زیر باشد:
var json = """
[
  {
    "brand": "Ferrari",
    "horsePower": 651
  },
  {
    "model": "F50",
    "horsePower": 512
  }
]
""";
که در لیست اشیاء آن، در یکی Model و در دیگری Brand وجود ندارد، عملیات Deserialize آن به صورت زیر، بدون هیچ خطایی به پایان می‌رسد:
var options = new JsonSerializerOptions
              {
                  PropertyNameCaseInsensitive = true,
                  PropertyNamingPolicy = JsonNamingPolicy.CamelCase
              };

var oldResults =  JsonSerializer.Deserialize<List<OldCarDto>>(json, options);
که البته در آن خواصی که وجود نداشته‌اند، با null مقدار دهی خواهند شد.

اگر اینبار تعریف Dto را به صورت زیر و بر اساس واژه‌ی کلیدی جدید required اصلاح کنیم:
public class NewCarDto
{
    public required string Brand { get; init; }
    public required string Model { get; init; }
    public required uint Horsepower { get; init; }
}
اینبار همان عملیات Deserialize زیر:
var newResults =  JsonSerializer.Deserialize<List<NewCarDto>>(json, options);
با این استثنای در زمان اجرا خاتمه خواهد یافت:
System.Text.Json.JsonException: JSON deserialization for type 'NewCarDto'
was missing required properties, including the following: model
که عنوان می‌کند به علت عدم قید خاصیت الزامی model، امکان deserialization وجود ندارد.
مطالب
آشنایی با Refactoring - قسمت 8

یکی از اشتباهاتی که همه‌ی ما کم و بیش به آن دچار هستیم ایجاد کلاس‌هایی هستند که «زیاد می‌دانند». اصطلاحا به آن‌ها God Classes هم می‌گویند و برای نمونه، پسوند یا پیشوند Util دارند. این نوع کلاس‌ها اصل SRP را زیر سؤال می‌برند (Single responsibility principle). برای مثال یک فایل ایجاد می‌شود و داخل آن از انواع و اقسام متدهای «کمکی» کار با دیتابیس تا رسم نمودار تا تبدیل تاریخ میلادی به شمسی و ... در طی بیش از 10 هزار سطر قرار می‌گیرند. یا برای مثال گروه بندی‌های خاصی را در این یک فایل از طریق کامنت‌های نوشته شده برای قسمت‌های مختلف می‌توان یافت. Refactoring مرتبط با این نوع کلاس‌هایی که «زیاد می‌دانند»، تجزیه آن‌ها به کلاس‌های کوچکتر، با تعداد وظیفه‌ی کمتر است.
به عنوان نمونه کلاس CustomerService زیر، دو گروه کار متفاوت را انجام می‌دهد. ثبت و بازیابی اطلاعات ثبت نام یک مشتری و همچنین محاسبات مرتبط با سفارشات مشتری‌ها:

using System;
using System.Collections.Generic;

namespace Refactoring.Day8.RemoveGodClasses.Before
{
public class CustomerService
{
public decimal CalculateOrderDiscount(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}

public bool CustomerIsValid(string customer, int order)
{
// do work
throw new NotImplementedException();
}

public IEnumerable<string> GatherOrderErrors(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}

public void Register(string customer)
{
// do work
}

public void ForgotPassword(string customer)
{
// do work
}
}
}

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

using System;
using System.Collections.Generic;

namespace Refactoring.Day8.RemoveGodClasses.After
{
public class CustomerOrderService
{
public decimal CalculateOrderDiscount(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}

public bool CustomerIsValid(string customer, int order)
{
// do work
throw new NotImplementedException();
}

public IEnumerable<string> GatherOrderErrors(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}
}
}

namespace Refactoring.Day8.RemoveGodClasses.After
{
public class CustomerRegistrationService
{
public void Register(string customer)
{
// do work
}

public void ForgotPassword(string customer)
{
// do work
}
}
}

مطالب
نمایش اخطارها و پیام‌های بوت استرپ به کمک TempData در ASP.NET MVC
در بوت استرپ برای نمایش اعلانی به کاربر، از کلاس alert می‌توان استفاده کرد. برای نمایش این اعلان کافی است محتوای خود را درون یک div با کلاس alert قرار دهیم: 
<div class="alert">
    نمایش اعلانات
</div>

تعدادی کلاس دیگر نیز جهت استفاده از رنگ‌های مختلف نیز توسط بوت استرپ ارائه شده است: 

همچنین اگر مایل بودید می‌توانید با افزودن یک دکمه با کلاس close و ویژگی data-dismiss مساوی alert، امکان بستن پیام را در اختیار کاربر قرار دهید: 

<div class="alert alert-warning alert-dismissable">
  <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
  نمایش اعلان
</div>

در ادامه قصد داریم این پیام را بعد از ثبت اطلاعات، به کاربر نمایش دهیم. یعنی در داخل کد، امکان صدا زدن این نوع پیام‌ها را داشته باشیم.

ابتدا کلاس‌های زیر را تعریف می‌کنیم:

کلاس Alert

public class Alert
{
        public const string TempDataKey = "TempDataAlerts";
        public string AlertStyle { get; set; }
        public string Message { get; set; }
        public bool Dismissable { get; set; }
}

در کلاس فوق خصوصیت یک alert را تعریف کرده‌ایم (از خاصیت TempDataKey جهت پاس دادن alert‌ها به view استفاده می‌کنیم).

  کلاس AlertStyles
public class AlertStyles
{
        public const string Success = "success";
        public const string Information = "info";
        public const string Warning = "warning";
        public const string Danger = "danger";
}
کلاس فوق نیز جهت نگهداری اسامی کلاس‌های alert، مورد استفاده قرار می‌گیرد. قدم بعدی، استفاده از کلاس‌های فوق و انتقال alerts توسط TempData به داخل viewها می‌باشد. برای جلوگیری از زیاد شدن حجم کدهای تکراری داخل کنترلرها و همچنین به عنوان یک Best practice، یک کنترلر Base را برای اینکار تعریف می‌کنیم و متدهای موردنیاز را داخل آن می‌نویسیم:
public class BaseController : Controller
{
        public void Success(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Success, message, dismissable);
        }

        public void Information(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Information, message, dismissable);
        }

        public void Warning(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Warning, message, dismissable);
        }

        public void Danger(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Danger, message, dismissable);
        }

        private void AddAlert(string alertStyle, string message, bool dismissable)
        {
            var alerts = TempData.ContainsKey(Alert.TempDataKey)
                ? (List<Alert>)TempData[Alert.TempDataKey]
                : new List<Alert>();

            alerts.Add(new Alert
            {
                AlertStyle = alertStyle,
                Message = message,
                Dismissable = dismissable
            });

            TempData[Alert.TempDataKey] = alerts;
        }
}
از متدهایی که به صورت عمومی تعریف شده‌اند جهت ارسال پیام به view استفاده می‌کنیم. متد AddAlert نیز جهت ایجاد لیستی از پیام‌ها(Alert) مورداستفاده قرار می‌گیرد؛ زیرا ممکن است بخواهید هم زمان از چندین متد عمومی فوق استفاده کنید، یعنی چندین پیام را به کاربر نمایش دهید. 

نکته: در کد فوق از TempData جهت پاس دادن شیء alerts استفاده کرده‌ایم. TempData به صورت short-lived عمل می‌کند به دو دلیل: 1- بلافاصله بعد از خوانده شدن، حذف خواهد شد. 2- پس از پایان درخواست از بین خواهد رفت. از TempData جهت پاس دادن داده‌ها از درخواست فعلی به درخواست بعدی (redirect از یک صفحه به صفحه دیگر) استفاده می‌شود. یعنی در زمان redirect سعی می‌کند داده‌های بین redirectها را در خود نگه دارد. اگر از ViewBag و ViewData استفاده می‌کردیم داده‌های داخل آنها بلافاصله بعد از redirect شدن null می‌شدند.
به طور مثال اکشن متد زیر را در نظر بگیرید:
public ActionResult Index()
        {
            var userInfo = new
            {
                Name = "Sirwan",
                LastName = "Afifi",
            };
 
            ViewData["User"] = userInfo;
            ViewBag.User = userInfo;
            TempData["User"] = userInfo;

            return RedirectToAction("About");
        }
view :
@{
    ViewBag.Title = "About";
}

<h1>Tempdata</h1><p>@TempData["User"]</p>
<h1>ViewData</h1><p>@ViewData["User"]</p>
<h1>ViewBag</h1><p>@ViewBag.User</p>
اگر کد فوق را تست کنید خواهید دید که در خروجی تنها اطلاعات داخل TempData نمایش داده می‌شود.
معمولاً برای ارسال داده‌های خطاها از TempData استفاده می‌شود.

اکنون در هر کنترلری که می‌خواهید پیامی را به صورت alert، پس از ثبت اطلاعات به کاربر نمایش دهید، باید از کنترلر BaseController  ارث‌بری کنید:
public class NewsController : BaseController
{
readonly INewsService _newsService;
        readonly IUnitOfWork _uow;
        public NewsController(INewsService newsService, IUnitOfWork uow)
        {
            _newsService = newsService;
            _uow = uow;
        }
        [HttpPost]
        [ValidateAntiForgeryToken]
        [ValidateInput(false)]
        public ActionResult Create(News news)
        {
            if (ModelState.IsValid)
            {
                _newsService.AddNews(news);
                _uow.SaveChanges();
                Success(string.Format("خبر با عنوان  <b>{0}</b> با موفقیت ذخیره گردید!", news.Title), true);
                return RedirectToAction("Index");
            }
            Danger("خطا در هنگام ثبت اطلاعات ");
            return View(news);
        }
        [HttpPost]
        public ActionResult Delete(int id)
        {
            _newsService.DeleteNewsById(id);
            _uow.SaveChanges();
            Danger("اطلاعات مورد نظر با موفقیت حذف گردید!", true);
            return RedirectToAction("Index");
        }
}

نمایش پیام‌ها 
برای نمایش پیام‌ها یک partial view با نام _Alerts در مسیر Views\Shared ایجاد می‌کنیم: 
@{
    var alerts = TempData.ContainsKey(Alert.TempDataKey)
                ? (List<Alert>)TempData[Alert.TempDataKey]
                : new List<Alert>();

    if (alerts.Any())
    {
        <hr />
    }

    foreach (var alert in alerts)
    {
        var dismissableClass = alert.Dismissable ? "alert-dismissable" : null;
        <div class="alert alert-@alert.AlertStyle @dismissableClass">
            @if (alert.Dismissable)
            {
                <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
            }
            @Html.Raw(alert.Message)
        </div>
    }
}
در کد فوق، ابتدا شیء alerts را از TempData دریافت کرده‌ایم و توسط یک حلقه foreach، داخل آن به ازای هر آیتم، آن را پیمایش می‌کنیم و در نهایت کد html متناظر با هر alert را در خروجی نمایش می‌دهیم.
اکنون جهت استفاده از partial view فوق در جایی که می‌خواهید پیام نمایش داده شود partial view فوق را فراخوانی کنید (به عنوان مثال داخل فایل Layout): 
<div>
                    @{ Html.RenderPartial("_Alerts"); }
                    @RenderBody()
</div>

مطالب
کنترل نرخ ورود اطلاعات در برنامه‌های Angular
فرض کنید قصد دارید همزمان با تایپ کاربر، نتایج جستجو را به او نمایش دهید. این جستجو نیز عموما به همراه ارسال یک درخواست HTTP به سمت سرور و نمایش اطلاعات بازگشتی به کاربر است. جهت کاهش تعداد رفت و برگشت‌های به سرور، کاهش بار سرور و همچنین کاهش تعداد بار به روز رسانی رابط کاربری، کتابخانه‌ی RxJS به همراه متدهایی است که امکان کاهش نرخ ورودی کاربر را میسر می‌کنند.


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

در اینجا کنترلر و اکشن متدی را جهت جستجوی قسمتی از نام کشورها، مشاهده می‌کنید:
    [Route("api/[controller]")]
    public class TypeaheadController : Controller
    {
        [HttpGet("[action]")]
        public async Task<IActionResult> SearchCountries(string term)
        {
            await Task.Delay(1000); // simulating a slow operation

            var items = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };
            var results = string.IsNullOrWhiteSpace(term) ? items :
                           items.Where(item => item.StartsWith(term, StringComparison.OrdinalIgnoreCase));
            return Json(results.ToArray());
        }
    }
از این کنترلر به نحو ذیل در برنامه‌ی Angular برای ارسال اطلاعات و انجام جستجو استفاده می‌شود:
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { ErrorObservable } from "rxjs/observable/ErrorObservable";
import { catchError, map } from "rxjs/operators";

@Injectable()
export class SearchService {

  constructor(private http: HttpClient) { }

  searchCountries(term: string): Observable<string[]> {
    return this.http
      .get(`/api/Typeahead/SearchCountries?term=${encodeURIComponent(term)}`)
      .pipe(
        map(response => response || {}),
        catchError((error: HttpErrorResponse) => ErrorObservable.create(error))
      );
  }
}
در اینجا از اپراتور pipe مخصوص RxJS 5.5 استفاده شده‌است.


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

صرفنظر از نوع فرمی که استفاده می‌کنید (مبتنی بر قالب‌ها و یا واکنشی)، جهت انتقال هربار فشرده شدن کلیدی به کدهای کامپوننت، می‌توان از رخ‌داد input استفاده کرد:
<label>Country: </label>
<input type="text" (input)="onSearch1Change($event.target.value)" />
<ul class="list-group">
   <li class="list-group-item" *ngFor="let country of countries1">
        {{country}}
   </li>
</ul>
و سپس متد مدیریت کننده‌ی آن در کامپوننت نیز به صورت زیر تعریف می‌شود:
onSearch1Change(value: string) {
 
}
در این حالت روش ابتدایی واکنش نشان دادن به هر ورودی، تزریق SearchService فوق به سازنده‌ی این کامپوننت
 constructor(private searchService: SearchService) { }
و سپس مشترک متد جستجوی سمت سرور آن، شدن است.
این روش ابتدایی سه مشکل را به همراه دارد:
الف) به ازای هر بار فشرده شدن کلیدی در Input box، یک درخواست به سمت سرور ارسال می‌شود. برای مثال اگر هدف اصلی کاربر، جستجوی کشورهای شروع شده‌ی با alg باشد، سه درخواست به سمت سرور ارسال می‌شوند و سه بار هم رابط کاربری به روز می‌شود.
ب) اگر در این بین، کاربر حرفی را کم و زیاد کند، درخواست‌های قبلی لغو نمی‌شوند.
ج) درخواست‌ها به صورت موازی به سرور ارسال می‌شوند. ممکن است نتیجه‌ی یکی زودتر و دیگری دیرتر دریافت شود. در این حالت آخرین نتیجه‌ی رسیده، نتایج قبلی را بازنویسی می‌کند که ممکن است الزاما نتیجه‌ای نباشد که کاربر درخواست کرده‌است.


کنترل نرخ ورود اطلاعات توسط متد debounceTime

با اعمال اپراتور debounceTime به رخ‌داد تغییرات ورودی، می‌توان نرخ ورودی کاربر و واکنش نشان دادن به آن‌را کاهش داد. برای مثال اگر این عدد به 300 میلی ثانیه تنظیم شده باشد، صرفا به اولین ورودی رسیده‌ی پس از 300 میلی ثانیه واکنش نشان داده می‌شود و از مابقی صرفنظر خواهد شد. به این ترتیب دیگر به ازای هربار فشرده شدن کلیدی توسط کاربر جستجو صورت نمی‌گیرد. همچنین با ترکیب آن با اپراتور distinctUntilChanged می‌توان تنها به تغییرات غیرتکراری واکنش نشان داد:
export class AutocompleteSampleComponent implements OnInit {

  countries1: string[] = [];
  private model1Changed: Subject<string> = new Subject<string>();
  private dueTime = 300;

  constructor(private searchService: SearchService) { }

  ngOnInit() {
    this.model1Changed
      .pipe(
        debounceTime(this.dueTime),
        distinctUntilChanged(),
        flatMap(inputValue => {
          console.log("debounced input value1", inputValue);
          return this.searchService.searchCountries(inputValue);
        })
      )
      .subscribe(countries => {
        this.countries1 = countries;
      });
  }

  onSearch1Change(value: string) {
    this.model1Changed.next(value);
  }
}
بنابراین بجای اینکه متد this.searchService.searchCountries دقیقا داخل onSearch1Change فراخوانی شود، باید بتوان تغییرات صورت گرفته‌ی نهایی را پس از اعمال debounceTime و distinctUntilChanged به آن ارسال کرد و سپس نتیجه را به کاربر نمایش داد.
برای این منظور یک Subject تعریف شده‌است تا کار مدیریت تغییرات رسیده (کلیدهای فشرده شده‌ی توسط کاربر) را انجام دهد. در این‌حالت فرصت خواهیم داشت تا انواع و اقسام اپراتورهای RxJS را با هم ترکیب و صرفا نتیجه‌ی نهایی (آخرین ورودی یکتای با تاخیر او) را به searchService ارسال کنیم.
متد onSearch1Change نیز تنها کافی است با فراخوانی متد next این Subject‌، جریان تغییرات رسیده را به آن انتقال دهد.
در اینجا برای انتقال آخرین ورودی یکتای با تاخیر به متد this.searchService.searchCountries از اپراتور flatMap استفاده شده‌است. این اپراتور، آخرین ورودی فیلتر شده را دریافت کرده و به متد searchCountries ارسال می‌کند. همچنین خروجی آن نیز یک Observable است. به همین جهت در ادامه می‌توان توسط متد subscribe، مشترک آن شد و آرایه‌ی countries دریافتی از سرور را به کاربر نمایش داد.



بهبود کارآیی جستجو با لغو درخواست‌های پیشین

تا اینجا توانستیم نرخ ورود اطلاعات کاربر را به صورت کنترل شده‌ای به متد this.searchService.searchCountries ارسال کنیم و نه اینکه به ازای هر بار ورود اطلاعات توسط آن، یکبار این متد فراخوانی شود. اما همانطور که در تصویر فوق مشاهده می‌کنید، در اینجا هدف نهایی کاربر، جستجوی نام کشورهای شروع شده‌ی با alg بوده است و در این بین چندین بار سعی و خطا انجام داده‌است تا به alg رسیده‌است. مشکل اینجا است که هیچکدام از درخواست‌های قبلی او که مدنظر نبوده‌اند، لغو نشده‌اند و تمام آن‌ها صورت گرفته و همچنین سبب به روز رسانی‌های مکرر رابط کاربری شده‌اند.
برای رفع یک چنین مشکلی و لغو خودکار درخواست‌های قبلی، اپراتور دیگری به نام switchMap وجود دارد که دقیقا یک چنین کاری را انجام می‌دهد. در اینجا برخلاف اپراتور flatMap، تمام درخواست‌های تمام نشده‌ی قبلی، لغو شده و صرفا آخرین مورد پردازش می‌شود.


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

همچنین در حالت استفاده‌ی از flatMap، ممکن است کاربر نتیجه‌ی اشتباهی را نیز دریافت کند. از این جهت که درخواست‌های ارسالی به سمت سرور، به صورت موازی اجرا می‌شوند. در این حالت ممکن است یکی زودتر و دیگری دیرتر به پایان برسد و کاربر نتیجه‌ای را که مشاهده می‌کند، دقیقا آن چیزی نباشد که جستجو کرده‌است (رابط کاربری آخرین درخواست پایان یافته را نمایش می‌دهد که نتیجه‌ی آن الزاما به ترتیب ورود اطلاعات کاربر نیست).
// A1: Request for `ABC`
// A2: Response for `ABC`
// B1: Request for `ABCX`
// B2: Response for `ABCX`
--A1----------A2-->
------B1--B2------>
برای نمونه فرض کنید دو درخواست A1 و B1 به همراه پاسخ‌های A2 و B2 را داریم. درخواست A1 پیش از B1 ارسال شده‌است؛ اما پاسخ B1 زودتر از پاسخ A2 از سرور دریافت شده‌است. در این حالت کاربر عبارت ABCX را وارد کرده‌است اما پاسخ عبارت ABC پیشین را در رابط کاربری مشاهده می‌کند (آخرین پاسخ رسیده در رابط کاربری (یا همان A2)، پاسخ‌های قبلی (یا همان B2) را بازنویسی می‌کند).

در حالت استفاده‌ی از flatMap‌، مشترک هر رخ‌داد رسیده خواهیم شد؛ بدون قطع اشتراک خودکار از سایر observableهای ایجاد شده‌ی پیشین. اما در حالت استفاده‌ی از switchMap‌، ابتدا کار لغو اشتراک خودکار از تمام observableهای قبلی صورت می‌گیرد و سپس یک observable جدید را ایجاد می‌کند. به همین جهت است که استفاده‌ی از switchMap‌  به همراه درخواست‌های http، سبب لغو خودکار درخواست‌های پیشین می‌شود. در این حالت نه تنها تعداد بار به روز رسانی رابط کاربری کاهش پیدا می‌کند، بلکه تضمین خواهد شد دیگر کاربر نتیجه‌ی اشتباهی را نیز مشاهده نکند.



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
نظرات اشتراک‌ها
ارتقا به dotnet core 2
با سلام و احترام
قسمت آخر مقاله یعنی قطعه کد
// Only used by EF Tooling
        public static IWebHost BuildWebHost(string[] args)
        {
            return WebHost.CreateDefaultBuilder()
                .ConfigureAppConfiguration((ctx, cfg) =>
                {
                    cfg.SetBasePath(Directory.GetCurrentDirectory())
                        .AddJsonFile("appsettings.json", true) // require the json file!
                        .AddEnvironmentVariables();
                })
                .ConfigureLogging((ctx, logging) => { }) // No logging
                .UseStartup()
                .UseSetting("DesignTime", "true")
                .Build();
        }
راه حل استانداردی نمیباشد و البته که با این حال نیاز به تکمیل شدن دارد به این شکل که باید در فایل startup.cs از تنظیم اضافه شده یعنی
UseSetting("DesignTime", "true")
جهت جلوگیری از Initial شدن دیتابیس در هنگام استفاده از Entity Framework Core Tooling به طور مثال عملیات Migration استفاده کرد
if (_config["DesignTime"] != "true")
{
  using (var scope = app.ApplicationServices.CreateScope())
  {
    var initializer = scope.ServiceProvider.GetRequiredService<YourSampleDataInitializer>();
    initializer.RunAsync().Wait();
  }
}
در ادامه روش توصیه شده توسط مایکروسافت پیاده سازی اینترفیس جدید IDesignTimeDbContextFactory میباشد که به واسطه آن EF موفق به یافتن Context برنامه در هنگام عملیات Migration میشود.قطعه کد زیر نمونه ای از این پیاده سازی است.
public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext>
    {
        public BloggingContext Create(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
            optionsBuilder.UseSqlite("Data Source=blog.db");

            return new BloggingContext(optionsBuilder.Options);
        }
    }
البته همه این موارد زمانی درست کار خواهد کرد که فایل Program.cs برنامه را نیز به شکل جدید آن یعنی
public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
تغییر دهیم.دلیل آن هم ... EF Core 2.0: design-time DbContext discovery
نظرات مطالب
نحوه‌ی نگاشت فیلدهای فرمول در Fluent NHibernate
از پاسخگویی شما بسیار ممنونم. من هر روز از شما مطلب جدیدی یاد میگیرم.
من قصد کشدار کردن بحث رو ندارم و اینم آخرین ارسال من در مورد این بحث است.فکر می کنم نتونستم منظورم رو واضح برسونم. فرض کنیم کلاس زیر وجود داره:
public class Project
{
public virtual int Id { get; set; }
public virtual long ProjectCode { get; set; }
public virtual string Name { get; set; }
public virtual int CreateDate { get; set; }

public virtual string SepratedDate
{
get { return myFunc(CreateDate); }
private set { ; }
}
}

من میخواهم در متد زیر لیستی از کلاس بالا رو به DataSet تبدیل کنم:

public DataSet dsGetAll(bool includeArchived)
{
using (var repository = new Repository())
{
var projects = repository.Find(x => x.IsArchive == includeArchived
);

var ds = new CollectionToDataSet>(projects.ToList());

return ds.CreateDataSet();
}
}

ولی خطا می ده که SepratedDate در جدول وجود نداره!!!
{"Invalid column name 'SepratedDate'."}
could not execute query
[ select project0_.Id as Id15_, project0_.ProjectCode as ProjectC2_15_, project0_.Name as Name15_, project0_.IsArchive as IsArchive15_, project0_.CreateDate as CreateDate15_, project0_.SepratedDate as Seprated6_15_ from tblProject project0_ where case when project0_.IsArchive=1 then 'true' else 'false' end=case when @p0='true' then 'true' else 'false' end
نظرات مطالب
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
در کد بالا، برای حالت ویرایش یک سلول در نوع select از mvc controller استفاده شده. من کد بالا رو برای webApi میخوام تنظیم کنم. رشته html رو تولید میکنم و پاس میدم به خروجی، اما خطا میده. 
کد رو ببینید.
 [HttpGet]
public string SelectAllJqTree()
        {
            var result = _kpiTypeService.SelectAll().Select(e => new
            {
                value = e.Id,
                text = e.Name,
            });

            var select = "<select>{0}</select>";
            var option = "<option value='{0}' >{1}</option>";

            var options = "";

            foreach (var item in result)
            {
                options += string.Format(option, item.value, item.text);
            }

            return  string.Format(select, options).Trim();
}
و در کد js ستون "kpiType" رو هم اینجوری تنظیم کردم:
 colModel: [
                    {
                        name: 'KpiTypeID', index: 'KpiTypeID', align: 'right', width: 300,
                        editable: true, edittype: 'select',
                        editoptions: {
                            dataUrl: '/api/KPIType/SelectAllJqTree'
                        },
                        editrules: {
                            required: true
                        }
                    }
              ]
خروجی این رو با جی کوئری که با صفحه اضافه میکنم، یک کنترل صحیح رو تولید میکنه.
$.ajax({
                url: "/api/KPIType/SelectAllJqTree",
                context: document.body
            }).done(function (data) {
                $("#selection").html(data);
            });

اما اینجا بهم پیام میده که 
Uncaught Error: Syntax error, unrecognized expression: "<select><option value='1' >عملکردی</option><option value='2' >بودجه ای</option>
چه نکته ای وجود داره؟ ایا مربوط به webApi هست؟
مطالب
Value Types ارجاعی در C# 7.2
در C# 7.2 می‌توان با value types (مانند structs) همانند reference types (مانند کلاس‌ها) رفتار کرد. جائیکه کارآیی برنامه بسیار حائز اهمیت باشد (مانند بازی‌ها)، استفاده از structs و value types بسیار مرسوم است؛ از این جهت که این نوع‌ها بر روی heap تخصیص داده نمی‌شوند. اما مشکل آن‌ها این است که زمانیکه به متدها ارسال می‌شوند، مقدار آن‌ها ارسال خواهد شد و برای این منظور نیاز به ایجاد یک کپی جدید از آن‌ها می‌باشد. برای رفع این مشکل و کاهش سربار کپی کردن اشیاء، اکنون در C# 7.2 می‌توان value types را همانند reference types به متدها ارسال کرد.


واژه‌ی کلیدی جدید in

C# 7.2‌، واژه‌ی کلیدی جدیدی را به نام in جهت تعریف پارامترها، معرفی کرده‌است. زمانیکه از آن استفاده می‌شود به این معنا است که value type ارسالی به آن، توسط ارجاعی از آن، در اختیار متد قرار می‌گیرد و نه توسط مقدار کپی شده‌ی آن (حالت پیش‌فرض) و همچنین متد استفاده کننده‌ی از آن، مقدار این شیء را تغییر نمی‌دهد.
واژه‌ی کلیدی in مکمل واژه‌های کلیدی ref و out است که پیشتر به همراه زبان #C ارائه شده بودند:
- واژه‌ی کلیدی out: مقدار آرگومان مزین شده‌ی توسط آن، باید درون متد تنظیم شود و صرفا کاربرد ارائه‌ی یک خروجی اضافه‌تر توسط آن متد را دارد.
- واژه‌ی کلیدی ref: مقدار آرگومان مزین شده‌ی توسط آن، ممکن است درون متد تنظیم شود، یا خیر و همچنین توسط ارجاع به آن منتقل می‌شود.
- واژه‌ی کلیدی in: مقدار آرگومان مزین شده‌ی توسط آن، درون متد تغییر نخواهد کرد و همچنین توسط ارجاع به آن منتقل می‌شود.

برای مثال اگر پارامترهای value type متد زیر را از نوع in معرفی کنیم، امکان تغییر مقدار آن‌ها درون متد وجود نخواهد داشت:
public static int Add(in int number1, in int number2)
{
   number1 = 5; // Cannot assign to variable 'in int' because it is a readonly variable
   return number1 + number2;
}
و کامپایلر با صدور خطای readonly بودن پارامتر number1، از انجام اینکار جلوگیری می‌کند


واژه‌ی کلیدی جدید in تا چه اندازه‌ای بر روی کارآیی برنامه تاثیر دارد؟

زمانیکه یک value type را به متدی ارسال می‌کنیم، ابتدا به مکان جدیدی از حافظه کپی شده و سپس مقدار clone شده‌ی آن، به متد ارسال می‌شود. با استفاده از واژه‌ی کلیدی in، دقیقا همان ارجاع به مقدار اولیه، به متد ارسال خواهد شد؛ بدون ایجاد کپی اضافه‌تری از آن. برای بررسی تاثیر این عملیات بر روی کارآیی برنامه، می‌توان از BenchmarkDotNet استفاده کرد. برای این منظور ابتدا ارجاعی را به BenchmarkDotNet اضافه می‌کنیم:
<ItemGroup>
     <PackageReference Include="BenchmarkDotNet" Version="0.10.12" />
</ItemGroup>
سپس متدهایی را که قرار است کارآیی آن‌ها بررسی شوند، با ویژگی Benchmark مزین خواهیم کرد:
using BenchmarkDotNet.Attributes;

namespace CS72Tests
{
    public struct Input
    {
        public decimal Number1;
        public decimal Number2;
    }

    [MemoryDiagnoser]
    public class InBenchmarking
    {
        const int loops = 50000000;
        Input inputInstance = new Input();

        [Benchmark(Baseline = true)]
        public decimal RunNormalLoop_Pass_By_Value()
        {
            decimal result = 0M;
            for (int i = 0; i < loops; i++)
            {
                result = Run(inputInstance);
            }
            return result;
        }

        [Benchmark]
        public decimal RunInLoop_Pass_By_Reference()
        {
            decimal result = 0M;
            for (int i = 0; i < loops; i++)
            {
                result = RunIn(in inputInstance);
            }
            return result;
        }

        public decimal Run(Input input)
        {
            return input.Number1;
        }

        public decimal RunIn(in Input input)
        {
            return input.Number1;
        }
    }
}
در آخر برای اجرای آن خواهیم داشت:
static void Main(string[] args)
{
    var summary = BenchmarkRunner.Run<InBenchmarking>();
و در این حالت برنامه را باید توسط دستور «dotnet run -c release» اجرا کرد (اندازه گیری کارآیی در حالت release و نه دیباگ پیش‌فرض)
با این خروجی نهایی:
                      Method |      Mean |    Error |   StdDev | Scaled | Allocated |
---------------------------- |----------:|---------:|---------:|-------:|----------:|
 RunNormalLoop_Pass_By_Value | 280.04 ms | 2.219 ms | 1.733 ms |   1.00 |       0 B |
 RunInLoop_Pass_By_Reference |  91.75 ms | 1.733 ms | 1.780 ms |   0.33 |       0 B |
همانطور که ملاحظه می‌کنید، کارآیی برنامه در حالت استفاده‌ی از پارامترهای in، حداقل 3 برابر شده‌است.


امکان استفاده‌ی از واژه‌ی کلیدی in در حین تعریف متدهای الحاقی

در حین تعریف متدهای الحاقی، واژه‌ی کلیدی in باید پیش از واژه‌ی کلیدی this ذکر شود:
    public static class Factorial
    {
        public static int Calculate(in this int num)
        {
            int result = 1;
            for (int i = num; i > 1; i--)
                result *= i;

            return result;
        }
    }
در این حالت اگر برنامه را به صورت زیر اجرا کنیم (یکبار با ذکر صریح in، بار دیگر بدون in و یکبار هم به صورت فراخوانی متد الحاقی بر روی عدد):
int num = 3;
Console.WriteLine($"(in num) -> {Factorial.Calculate(in num)}");
Console.WriteLine($"(num) -> {Factorial.Calculate(num)}");
Console.WriteLine($"num. -> {num.Calculate()}");
خروجی‌های ذیل را دریافت خواهیم کرد:
(in num) -> 6
(num) -> 6
num. -> 6
به عبارتی حین فراخوانی و استفاده‌ی از متدی که پارامتر آن به صورت in تعریف شده‌است، ذکر in ضروری نیست.

و به طور کلی استفاده‌ی از in در مکان‌های ذیل مجاز است:
• methods
• delegates
• lambdas
• local functions
• indexers
• operators
 

محدودیت‌های استفاده‌ی از پارامترهای in

الف) محدودیت استفاده از پارامترهای in در تعریف overloads
مثال زیر را در نظر بگیرید:
    public class CX
    {
        public void A(Input a)
        {
            Console.WriteLine("int a");
        }

        public void A(in Input a)
        {
            Console.WriteLine("in int a");
        }
    }
در اینجا overloadهای تعریف شده‌ی متد A تنها در ذکر واژه‌ی کلیدی in یا modifier متفاوت هستند.
اگر سعی کنیم وهله‌ای از این کلاس را ایجاد کرده و از متدهای A آن استفاده کنیم:
    public class Y
    {
        public void Test()
        {
            var inputInstance = new Input();
            var cx = new CX();
            cx.A(inputInstance); // The call is ambiguous between the following methods or properties: 'CX.A(Input)' and 'CX.A(in Input)'
        }
    }
خطای کامپایلر مبهم بودن متد A مورد استفاده صادر خواهد شد. یعنی نمی‌توان overload ایی را تعریف کرد که تنها در modifier از نوع in با دیگری متفاوت باشد؛ چون ذکر in در حین فراخوانی متد، اختیاری است.

ب) پارامترهای از نوع in را در متدهای iterator نمی‌توان استفاده کرد:
public IEnumerable<int> B(in int a) // Iterators cannot have ref or out parameters
{
   Console.WriteLine("in int a");
   yield return 1;
}

ج) پارامترهای از نوع in را در متدهای async نمی‌توان استفاده کرد:
public async Task C(in int a) // Async methods cannot have ref or out parameters
{
   await Task.Delay(1000);
}


تاثیر کار با متدهای داخلی تغییر دهنده‌ی وضعیت یک struct

مثال زیر را درنظر بگیرید. به نظر شما خروجی آن چیست؟
using System;

namespace CS72Tests
{
    struct MyStruct
    {
        public int MyValue { get; set; }

        public void UpdateMyValue(int value)
        {
            MyValue = value;
        }
    }

    public static class TestInStructs
    {
        public static void Run()
        {
            var myStruct = new MyStruct();
            myStruct.UpdateMyValue(1);

            UpdateMyValue(myStruct);

            Console.WriteLine(myStruct.MyValue);
        }

        static void UpdateMyValue(in MyStruct myStruct)
        {
            myStruct.UpdateMyValue(5);
        }
    }
}
در اینجا اگر متد TestInStructs.Run را اجرا کنیم، خروجی آن، نمایش عدد 1 خواهد بود.
در ابتدا مقدار struct را به 1 تنظیم و سپس ارجاع آن‌را به متدی دیگر که مقدار آن‌را به 5 تنظیم می‌کند، ارسال کردیم. در این حالت برنامه بدون مشکل کامپایل و اجرا می‌شود.  علت اینجا است که کامپایلر #C زمانیکه متدی را در داخل یک struct فراخوانی می‌کند، یک clone از آن struct را ایجاد کرده و متد را بر روی آن clone اجرا می‌کند؛ چون نمی‌داند که آیا این متد وضعیت و مقدار این struct را تغییر می‌دهد یا خیر. در این حالت کپی اصلی بدون تغییر باقی می‌ماند (در نهایت عدد 1 را مشاهده خواهیم کرد)، اما در آخر فراخوان، ارجاعی از struct را دریافت نکرده و بر روی کپی آن کار می‌کند. بنابراین مزیت بهبود کارآیی، از دست خواهد رفت.

البته در اینجا اگر می‌خواستیم مقدار MyValue را مستقیما تغییر دهیم، کامپایلر از آن جلوگیری می‌کرد و این کد هیچگاه کامپایل نمی‌شد:
static void UpdateMyValue(in MyStruct myStruct)
{
   myStruct.MyValue = 5; // Cannot assign to a member of variable 'in MyStruct' because it is a readonly variable
   myStruct.UpdateMyValue(5);
}
مطالب
تعریف نوع جنریک به صورت متغیر

در تهیه مثال Auto Mapping به کمک امکانات توکار NH 3.2 به این مورد نیاز پیدا کردم:
بتوان نوع متد جنریک را به صورت متغیر تعریف کرد و این نوع در زمان کامپایل برنامه مشخص نباشد. مثلا چیزی شبیه به این مثال:

using System;

namespace GenericsSample
{
class TestGenerics
{
public static void Print<T>(T data)
{
Console.WriteLine("Print<T>");
}
}

class Program
{
static void Main(string[] args)
{
var type = typeof(Nullable<int>);
TestGenerics.Print<type>(1);
}
}
}

این نوع فراخوانی متد Print در دات نت به صورت پیش فرض غیرمجاز است و نوع جنریک را نمی‌توان به صورت متغیر معرفی کرد.
که البته این هم راه حل دارد و به کمک Reflection قابل حل است:

using System;

namespace GenericsSample
{
class TestGenerics
{
public static void Print<T>(T data)
{
Console.WriteLine("Print<T>");
}
}

class Program
{
static void Main(string[] args)
{
var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
var method = typeof(TestGenerics).GetMethod("Print");
var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
genericMethod.Invoke(null, new object[] { 1 });
}
}
}

دو متد MakeGenericType و MakeGenericMethod برای ساخت پویای نوع‌های جنریک و همچنین ارسال آن‌ها به متدهای جنریک در دات نت وجود دارند که مثالی از نحوه استفاده از آن‌ها را در بالا ملاحظه می‌کنید.

مثال دوم:
اگر کلاس TestGenerics نسخه غیرجنریک متد Print را هم داشت، ‌چطور؟ مثلا:

class TestGenerics
{
public static void Print<T>(T data)
{
Console.WriteLine("Print<T>");
}

public static void Print(object data)
{
Console.WriteLine("Print");
}
}

اینبار اگر برنامه فوق را اجرا کنیم، پیغام Ambiguous match found را حین فراخوانی GetMoethod دریافت خواهیم کرد؛ چون دو متد با یک نام در کلاس یاد شده وجود دارند. برای حل این مشکل باید به نحو زیر عمل کرد:

using System;
using System.Linq;

namespace GenericsSample
{
class TestGenerics
{
public static void Print<T>(T data)
{
Console.WriteLine("Print<T>");
}

public static void Print(object data)
{
Console.WriteLine("Print");
}
}

class Program
{
static void Main(string[] args)
{
var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
var method = typeof(TestGenerics).GetMethods()
.First(x => x.Name == "Print" && (x.GetParameters()[0]).ParameterType.IsGenericParameter);
var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
genericMethod.Invoke(null, new object[] { 1 });
}
}
}

GetMethods تمام متدها را بازگشت داده و سپس بر اساس متادیتای متدها، ‌می‌توان تشخیص داد که کدام یک جنریک است.