جستجو Contains روی کلید های ترکیبی
خیر؛ چون customUsers یک لیست از کلاس ساخته شده توسط خودمه (اگه لیست از جنس int، string و ... رو داشته باشم این مشکل نیست)به خاطر همین اکسپشن میده و میگه نمیتونم Linq رو ترجمه کنم.
ULID جایگزینی مناسب برای UUID
بهبود سرعت درج و بازیابی در ULID
مقایسه سرعت درج یک میلیون رکورد تایپ های ULID , UUID, int , DateTime
جستجو Contains روی کلید های ترکیبی
var customUsers = new List<User> { new User{FullName = "Ali Ahmadi", EyeColor = "Brown"}, new User{FullName = "Milad Rezaei", EyeColor = "Green"} }; var specificUsers = _context.Users.Where(x => customUsers.Any(xx=> xx.EyeColor == x.EyeColor || xx.FullName == x.FullName)).ToList();
آیا روش بالا به حل مسئله شما کمک میکنه؟ البته بجای اپراتور == میتوانید از متد Contains هم استفاده کنید که در نهایت به Is Exists در Sql Server ترجمه خواهد شد.
جستجو Contains روی کلید های ترکیبی
- هدف شما در اصل یافتن یک یا چند «شیء مشخص»، در یک جدول بانک اطلاعاتی است. اگر از EF استفاده میکنید، هر رکورد/شیء شما، قطعا یک Id منحصربفرد هم دارد (تا یک «شیء مشخص» را تشکیل دهد). فقط بر اساس این Id کوئری بگیرید (نه بر اساس لیست تمام ستونهای موجود). نتیجهی کار، شبیه به کوئری اولی میشود که نوشتید (که البته اینجا، List آن از نوع int است و یا کلا نوع Pk جدول کاربران) و فوق العاده هم سریع است.
- اگر Idهای اشیاء موجود در لیست فوق را ندارید، باید از PredicateBuilder استفاده کنید تا بتوانید کوئریهای Or پویایی را به ازای هر شیء، تولید کنید. الان این PredicateBuilder، جزئی از کتابخانهی Gridify هم هست.
var predicate = PredicateBuilder.False<User>(); foreach(var user in customUsers) { predicate = predicate.Or(u => u.FullName == user.FullName && u.EyeColor == user.EyeColor); } var specificUsers = _context.Users.Where(predicate).ToList();
اگر یک لیست رشته ایی داشته باشیم به راحتی میتونیم روی پایگاه داده جستجو کنیم:
var eyeColors = new List<string> { "Black", "Green", "Brown" }; var specificUsers = _context.Users.Where(x => eyeColors.Contains(x.EyeColor)).ToList();
حالا اگه لیست ما ترکیبی باشه این سرچ باید به چه صورت انجام بشه:
public class User { public string FullName { get; set; } public string EyeColor { get; set; } } var customUsers = new List<User> { new User{FullName = "Ali Ahmadi", EyeColor = "Brown"}, new User{FullName = "Milad Rezaei", EyeColor = "Green"} }; var specificUsers = _context.Users.Where(x => customUsers.Contains(new User(){EyeColor = x.EyeColor, FullName = x.FullName})).ToList();
روش فوق و روش های زیادی رو سرچ کردم ولی به جواب نرسیدم. آیا میشه از EF استفاده کرد یا راه حل دیگری دارد ؟
نگاهی به نحوهی طراحی Twitter
یک نکتهی تکمیلی: استفاده از BlazorStaticRendererService جهت تولید یک کامپوننت کش کردن قسمتی از محتوای صفحه در برنامههای Blazor SSR
فرض کنید هر کدام از آیتمهای منوی سمت راست صفحه، به همراه آماری مرتبط هم هستند که باید جداگانه محاسبه شوند. اگر قرار باشد بهازای هر کاربر و هر بازدید صفحهای، این اطلاعات دوباره محاسبه شوند، بار قابل توجهی به برنامه و سرور وارد خواهد شد و همچنین مرور صفحات هم بهشدت کند میشوند؛ چون قسمت منوی سمت راست صفحه، هربار باید از ابتدا رندر شود. در این مطلب، با سرویس BlazorStaticRendererService آشنا شدیم که کار آن، رندر کردن محتوای یک کامپوننت و بازگشت رشتهی نهایی معادل آن است. اگر این مورد را به همراه IMemoryCache توکار داتنت، ترکیب کنیم، به کامپوننتی شبیه به cache tag helper توکار ASP.NET Core میرسیم:
<cache expires-after="@TimeSpan.FromMinutes(10)"> @Html.Partial("_WhatsNew") *last updated @DateTime.Now.ToLongTimeString() </cache>
کدهای کامل آنرا در اینجا (^ و ^) میتوانید مطالعه کنید که به این صورت مورد استفاده قرار گرفتهاست تا فقط قسمتی از صفحه را کش کند و نه کل آنرا.
جالب توجهاست که OutputCache مخصوص ASP.NET Core، در Blazor SSR هم کار میکند. برای استفادهی از آن در Blazor SSR، پس از انجام تنظیمات ابتدایی میانافزار مخصوص آن که ترتیب افزودن آن باید به صورت زیر باشد:
app.UseStaticFiles(); app.UseSession(); app.UseRouting(); app.UseAntiforgery(); app.UseOutputCache(); app.MapRazorComponents<App>(); app.MapControllers(); app.Run();
فقط کافی است ویژگی OutputCache را به نحو زیر به فایل razor. خود اضافه کنید:
@attribute [OutputCache(Duration = 1000)]
که البته کار آن، کش کردن محتوای کل یک صفحهاست و نه فقط قسمتی از آن.
یک نکتهی تکمیلی: روش طراحی binding دو طرفه در Blazor SSR
در نکتهی قبل عنوان شد که مقدمات طراحی binding دو طرفه، داشتن حداقل سه خاصیت زیر در یک کامپوننت سفارشی است:
[Parameter] public T? Value { set; get; } [Parameter] public EventCallback<T?> ValueChanged { get; set; } [Parameter] public Expression<Func<T?>> ValueExpression { get; set; } = default!;
اگر این خواص را به کامپوننتهای توکار خود Blazor متصل کنیم (مانند InputBox آن و مابقی آنها)، نیازی به کدنویسی بیشتری ندارند و کار میکنند. اما اگر قرار است از یک input سادهی Html ای استفاده کنیم، نیاز است ValueChanged آنرا اینبار در متد OnInitialized فراخوانی کنیم؛ چون در زمان post-back به سرور است که مقدار آن در اختیار مصرف کنندهی کامپوننت قرار میگیرد. این مورد، مهمترین تفاوت نحوهی طراحی binding دوطرفه در Blazor SSR با مابقی حالات و نگارشهای Blazor است.
بررسی وقوع post-back به سرور به دو روش زیر میسر است:
الف) بررسی کنیم که آیا HttpPost ای رخدادهاست؟ سپس در همین لحظه، متد ValueChanged.InvokeAsync را فراخوانی کنیم:
[CascadingParameter] internal HttpContext HttpContext { set; get; } = null!; protected override void OnInitialized() { base.OnInitialized(); if (HttpContext.IsPostRequest()) { SetValueCallback(Value); } } private void SetValueCallback(string value) { if (!ValueChanged.HasDelegate) { return; } _ = ValueChanged.InvokeAsync(value); }
در این مثال نحوهی فعالسازی ارسال اطلاعات از یک کامپوننت سفارشی را به مصرف کنندهی آن ملاحظه میکنید. اینکار در قسمت OnInitialized و فقط در صورت ارسال اطلاعات به سمت سرور، فعال خواهد شد.
ب) میتوان در قسمت OnInitialized، بررسی کرد که آیا درخواست جاری به همراه اطلاعات یک فرم ارسال شدهی به سمت سرور است یا خیر؟ روش کار به صورت زیر است:
protected override void OnInitialized() { base.OnInitialized(); if (HttpContext.Request.HasFormContentType && HttpContext.Request.Form.TryGetValue(ValueField.HtmlFieldName, out var data)) { SetValueCallback(data.ToString()); } }
در اینجا از ValueField.HtmlFieldName که در نکتهی قبلی معرفی BlazorHtmlField به آن اشاره شد، جهت یافتن نام واقعی فیلد ورودی، استفاده شدهاست.
یک نکتهی تکمیلی: نحوهی نامگذاری ویژهی عناصر در فرمهای جدید Blazor SSR
اگر با نگارشهای دیگر Blazor کار کرده باشید، عموما یک EditForm را به صفحه اضافه کرده و چند المان را به آن اضافه میکنیم و ... کار میکند. حتی اگر کامپوننتهای سفارشی را هم بر این مبنا تهیه کنیم ... بازهم بدون نکتهی خاصی کار میکنند. اما ... در برنامههای Blazor SSR اینطور نیست! زمانیکه برای مثال مدل فرم را به این صورت تعریف میکنیم:
[SupplyParameterFromForm] public OrderPlace? MyModel { get; set; }
و آنرا به نحو متداولی در صفحه نمایش میدهیم:
<InputText @bind-Value="MyModel.City"/>
اگر به المان رندر شدهی در مرورگر مراجعه کنیم، ویژگی name حاصل، با MyModel.City مقدار دهی شدهاست و ... این موضوع درج نام خاصیت مدل (و یا اصطلاحا Html Field Prefix)، برای Blazor SSR بسیار مهم است! تاحدی که اگر از آن آگاه نباشید، ممکن است ساعتی را مشغول دیباگ برنامه شوید که چرا، مقدار نالی را دریافت کردهاید و یا عناصر تعریف شدهی در کامپوننتهای سفارشی، کار نمیکنند و مقدار نمیگیرند!
متاسفانه API بازگشت نام کامل عناصری که توسط Blazor SSR تولید میشود، عمومی نیست و internal است. اگر از کامپوننتهای استاندارد خود Blazor استفاده میکنید، نیازی نیست تا به این موضوع فکر کنید و مدیریت آن خودکار است؛ اما همینکه قصد تولید کامپوننتهای سفارشی مخصوص SSR را داشته باشید، اولین مشکلی را که با آن مواجه خواهید شد، دقیقا همین مسالهی تولید صحیح HtmlFieldPrefixها است.
برای رفع این مشکل و دسترسی به API پشت صحنهی تولید نام فیلدها در Blazor SSR، میتوان از کامپوننت پایهی InputBase خود Blazor ارثبری کرد و به این ترتیب به خاصیت جدید NameAttributeValue آن دسترسی یافت (این خاصیت به داتنت 8 و مخصوص Blazor SSR، اضافه شدهاست) که اینکار در کلاس BlazorHtmlField انجام شدهاست. روش استفادهی از آن هم به صورت زیر است:
private BlazorHtmlField<T?> ValueField => new(ValueExpression ?? throw new InvalidOperationException(message: "Please use @bind-Value here.")); [Parameter] public T? Value { set; get; } [Parameter] public EventCallback<T?> ValueChanged { get; set; } [Parameter] public Expression<Func<T?>> ValueExpression { get; set; } = default!;
زمانیکه میخواهیم در یک کامپوننت سفارشی، خاصیتی bind پذیر را طراحی کنیم، روش کار آن، مانند مثال فوق است که به همراه یک خاصیت، یک EventCallback و یک Expression است تا اعتبارسنجی و انقیاد دوطرفه را فعال کند. اما ... اگر همین Value را مستقیما در فیلدهای کامپوننت استفاده کنیم ... مقدار نمیگیرد؛ چون به همراه نام کامل خاصیت بایند شدهی به آن نیست. برای مثال بجای MyModel.City فقط City درج میشود (که به علت نداشتن .MyModel، سیستم binding از مقدار آن صرفنظر میکند). اکنون با استفاده از BlazorHtmlField فوق، میتوان به نام کامل تولیدی توسط Blazor SSR دسترسی یافت و از آن استفاده کرد:
<input type="text" dir="ltr" name="@ValueField.HtmlFieldName" id="@ValueField.HtmlFieldName" />
HtmlFieldName ای که در اینجا درج میشود، توسط خود Blazor محاسبه شده و با انتظارات موتور binding آن تطابق دارد و دیگر به خواص بایند شدهای که مقدار نمیگیرند، نخواهیم رسید.
یک نکتهی تکمیلی: استفاده از این DatePicker در برنامههای Blazor SSR
اگر علاقمند باشید تا از این DatePicker در برنامههای Blazor SSR بهصورت یک کامپوننت استفاده کنید، روش کار به صورت زیر است:
- نیاز به نسخهی اصلاح شدهی آن خواهید داشت.
- سپس هر المان ورودی که مزین به ویژگی data-dnt-date-picker بود، یافت شده و این DatePicker به آن متصل میشود.
- همچنین از این جهت که در برنامههای Blazor SSR، ویژگی enhanced navigation و نمایش Ajax ای صفحات با fetch، فعال است، باید حالت enhancedload را تحت نظر قرار داده و این کوئری گرفتن یافتن عناصر با ویژگی data-dnt-date-picker و اتصال مجددا به آنها را مدیریت کرد.
- تا همینجا و با این تنظیمات، نمایش این DatePicker فعال میشود و ... کار میکند. اگر علاقمند به تبدیل آن به یک کامپوننت مخصوص Blazor SSR هم هستید، میتوانید از کامپوننت DntInputPersianDatePicker.razor و کدهای آن، ایده بگیرید که جزئیات آن پیشتر در مطلب جاری بررسی شدهاست؛ هرچند این مطلب بیشتر به سایر نگارشهای Blazor میپردازد.