یک نکته‌ی تکمیلی: روش استفاده از خواص راهبری، در به‌روزسانی‌های دسته‌ای

در همین مثال مطلب جاری، فرض کنید موجودیت کاربر، به همراه خاصیت محاسباتی تعداد کتاب‌ها (NumberOfBooks) هم هست:

public class User
{
    // ...

    public virtual List<Book> Books { get; set; } = new();

    public int NumberOfBooks { get; set; }
}

اگر سعی کنیم این خاصیت را به صورت زیر به‌روز رسانی کنیم که در آن مقدار تعداد کتاب‌ها به کمک خاصیت راهبری Books محاسبه شود:

await context.Users.Where(x=> x.Id == 1).ExecuteUpdateAsync(s => s.SetProperty(user => user.NumberOfBooks,
                user => user.Books.Count()));

به خطای زیر می‌رسیم:

... could not be translated. Additional information: Translation of member 'Books' on entity type 'User' failed. This commonly occurs when the specified member is unmapped.

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

await context.Users
             .Where(user => user.Id == 1)
             .Select(user => new
             {
                user.NumberOfBooks,
                BooksCount = user.Books.Count()
             })
             .ExecuteUpdateAsync(s => s.SetProperty(arg => arg.NumberOfBooks, arg => arg.BooksCount));

در اینجا تنها کاری که انجام شده، انجام محاسبات و نگاشت‌های لازم، پیش از فراخوانی متد ExecuteUpdateAsync است.

در یک پروژه دات نت 8 با بک اند api و فرانت blazor wasm با توجه به نیاز مندیهای زیر چه راه حلی پیشنهاد میشه؟

1- تبدیل id از نوع int درهمه مدلهای برگشتی به یک مقدار غیر قابل حدس توسط کاربر

2- دریافت مدل یا کوئری استرینگ شامل Id و تبدیل آن به مقدار اصلی

فرض براین است که قصد استفاده از Guid به عنوان کلید اصلی جداول را بخاطر مشکلاتی نظیر حجم جدوال و ایندکس گذاری و... رو نداریم.

با سلام و تشکر.

بعد از آپ گرید کردن پروژه به دات نت 8 سیستم به خوبی لاگین میشه و مشکلی نداره ولی وقتی گزینه Users Manager رو کلیک می کنیم، در سمت سرور در کنترلر UserAccountManagerController همونطور که در عکس زیر قابل مشاهده است IsAuthenticated برای کاربر false و در سمت کلاینت هم با ارور 401 مواجه میشیم. ممنون میشم راهنمایی بفرمایید.

‫۶ روز قبل، جمعه ۲۳ شهریور ۱۴۰۳، ساعت ۰۸:۰۲

یک نکته‌ی تکمیلی: روش اجرای پیش‌فرض کارهای پس زمینه، ترتیبی است.

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

builder.Services.Configure<HostOptions>(options =>
{
    options.ServicesStartConcurrently = true;
    options.ServicesStopConcurrently = true;
});

مزیت اینکار، شروع و همچنین پایان سریعتر برنامه، با داشتن تعداد زیادی کار پس‌زمینه است.

یک نکته‌ی تکمیلی: SemaphoreSlim، کمی سنگین است و امکانات سفارشی سازی کمی هم دارد؛ اگر به‌دنبال یک نمونه‌ی سریعتر و با مصرف حافظه‌ی کمتری هستید، کتابخانه‌ی AsyncKeyedLock مفید‌تر است. این کتابخانه را می‌توان جایگزین neosmart/AsyncLock درنظر گرفت.

‫۱۱ روز قبل، یکشنبه ۱۸ شهریور ۱۴۰۳، ساعت ۱۵:۳۰

یک نکته‌ی تکمیلی: روش نمایش خودکار آرگومان‌های نامدار در Rider

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

الف) ویژگی فرمت کردن کدها را در حالت ذخیره سازی تغییرات، فعال کنید:

با اینکار، هربار که تغییرات را ذخیره می‌کنید، تنظیمات کدنویسی، به صورت خودکار به فایل‌های ذخیره نشده‌، اعمال می‌شوند.

ب) به قسمت Settings -> Editor -> Cody Style -> C# -> Syntax Style مراجعه کرده و در قسمت تنظیمات آرگومان‌ها، حداقل گزینه‌های Literal values و String literal values را بر روی named argumets قرار دهید تا نکات مطلب جاری، به صورت خودکار اعمال شوند:

همانطور که در مثال before/after تصویر فوق هم مشخص است، مزیت اینکار، مفهوم پیدا کردن اعداد و رشته‌های وارد شده به عنوان آرگومان‌های متدها هستند.

‫۱۳ روز قبل، جمعه ۱۶ شهریور ۱۴۰۳، ساعت ۱۲:۱۷

یک نکته‌ی تکمیلی: امکان کار همزمان هم با HttpClient وجود دارد!

تا پیش از ارائه‌ی NET Core.، روش متداول دریافت فایل‌ها، عموما به صورت زیر و همزمان/synchronous بود:

var client = new WebClient();
client.DownloadFile(downloadUrl, filePath);

هرچند ... WebClient امکان دریافت فایل‌ها را به صورت غیرهمزمان هم دارد، اما API آن با async/await هماهنگ نیست و طراحی آن قدیمی است.

پس از آن،‌ HttpClient ارائه شد که از روز اول، async بود و کاملا هماهنگ با async/await و روش کدنویسی جدید آن. اما ... شاید در قسمت‌هایی نیاز باشد تا بتوان کدهای قدیمی را بدون تبدیل کردن آن‌ها به نمونه‌های async، به همان شکل همزمان، بازنویسی کنیم. برای رفع این مشکل، از زمان دات‌نت 5، متد Send همزمان هم به API آن اضافه شده‌است:

var response = httpClient.Send(new HttpRequestMessage(HttpMethod.Post, "http://site.com"));
using var reader = new StreamReader(response.Content.ReadAsStream());            
var content = reader.ReadToEnd();
‫۱۳ روز قبل، پنجشنبه ۱۵ شهریور ۱۴۰۳، ساعت ۲۲:۱۰

یک نکته‌ی تکمیلی: روش لغو صف درخواست‌های مکرر fetch ارسالی به سمت سرور

ورودی جستجوی بالای صفحه‌ را درنظر بگیرید که به‌ازای هربار ورود حرفی، یک درخواست fetch جدید را به سمت سرور ارسال می‌کند تا نتایج جستجوی حاصل را دریافت کند. مشکل اینجاست که ما تنها به آخرین درخواست fetch ارسال شده‌ی به سمت سرور نیاز داریم و نه به تمام درخواست‌های دیگری که صادر شده‌اند. به همین جهت این صف درخواست‌های fetch قبلی، غیربهینه بوده و ترافیک بالایی را سبب می‌شوند. یک روش مواجه شدن با این مساله، استفاده از مفهومی به نام debounce است که در پشت صحنه، از یک تایمر استفاده می‌کند و فقط هر چند ثانیه یکبار، یک درخواست جدید را به همراه آخرین متن ورودی، به سمت سرور ارسال خواهد کرد. راه دیگری هم برای مواجه شدن با این مشکل، در مرورگرهای جدید پیش‌بینی شده‌است که AbortController نام دارد. با استفاده از آن می‌توان «سیگنالی» را به صف درخواست‌های پرتعداد fetch قبلی حاصل از ورود اطلاعات کاربر ارسال کنیم که ... «لغو شوید» و به سمت سرور ارسال نشوید.

برای توضیح بهتر آن، به مثال زیر دقت کنید:

<!DOCTYPE html>
<html>
  <body>
    <input id="search" type="number" />
    <script>
      const results = [];
      const search = document.getElementById("search");

      let controller = new AbortController();
      let signal = controller.signal;

      const onChange = () => {
        const value = search.value;
        if (value) {
          controller.abort();
          controller = new AbortController();
          signal = controller.signal;
          getPost(value, signal);
        }
      };
      search.onkeyup = onChange;
    </script>
  </body>
</html>

- در اینجا یک input box را داریم که ابتدا، یافت شده و سپس به رخ‌داد onkeyup آن، متد onChange نسبت داده شده‌است تا هربار که کاربر، اطلاعاتی را وارد می‌کند، فراخوانی شود.

- در ابتدای اسکریپت هم نحوه‌ی نمونه سازی شیء استاندارد جاوااسکریپتی AbortController و دسترسی به شیء signal آن‌را مشاهده می‌کنید.

- در متد onChange، ابتدا مقدار جدید ورودی کاربر، دریافت می‌شود، سپس این AbortController، لغو می‌شود و بعد یک نمونه‌ی جدید از آن ایجاد شده و مجددا به شیء signal آن دسترسی پیدا می‌کنیم تا آن‌را به متد getPost ارسال کنیم. این متد هم چنین پیاده سازی را دارد:

const getPost = (value, signal) => {
          fetch(
            `https://site.com/search/${value}`,
            { signal }
          );          
      };

همانطور که مشاهده می‌کنید، تابع fetch، قابلیت پذیرش شیء signal را هم دارد. زمانیکه با هربار تایپ کاربر، متد ()controller.abort فراخوانی می‌شود، سیگنالی را به fetch «قبلی» متصل به آن ارسال می‌کند که ... دیگر به سمت سرور ارسال نشو و متوقف شو. با اینکار فقط آخرین ورودی کاربر، سبب بروز یک fetch موفق می‌شود و ترافیک ارسالی به سمت سرور کاهش پیدا می‌کند (چون تمام fetchهای قبلی، سیگنال abort را دریافت کرده‌اند)؛ مانند مثال زیر که کاربر، 5 بار حروفی را وارد کرده و به ازای هربار ورود حرفی، یک درخواست fetch جدید، ایجاد شده، اما ... فقط آخرین درخواست ارسالی او موفق بوده و نتیجه‌ای را بازگشت داده و مابقی درخواست‌ها ... abort شده‌اند. این عملیات abort، در سمت کاربر اعمال می‌شود؛ یعنی اصلا درخواستی به سمت سرور ارسال نمی‌شود و این لغو درخواست، توسط برنامه‌ی سمت سرور انجام نشده‌است.