مدیریت شروط اختیاری و مقادیر Null در عبارتهای LINQ و Entity Framework Core
نویسنده: هادی مزارعی
تاریخ: ۱۴۰۴/۱۰/۰۳ ۱۵:۱۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
ExecutionDate) و «تاریخ اجرای بعدی» (NextExecutionDate) است. ExecutionDate اجباری است، اما NextExecutionDate اختیاری (Nullable) میباشد. اگر NextExecutionDate مقدار نداشته باشد، باید تمام رکوردهای بعد از ExecutionDate بدون سقف زمانی واکشی شوند.DbContext به صورت زیر طراحی شده است:public class Component
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Fitup> Fitups { get; set; } = new List<Fitup>();
}
public class Fitup
{
public int Id { get; set; }
public int ComponentId { get; set; }
public Component Component { get; set; }
public ICollection<Weld> Welds { get; set; } = new List<Weld>();
}
public class Weld
{
public int Id { get; set; }
public int FitupId { get; set; }
public Fitup Fitup { get; set; }
public Report Report { get; set; } // Owned Entity or Navigation
}
public class Report
{
public int Id { get; set; }
public DateTime Date { get; set; }
// سایر فیلدها...
}
// کلاس ورودی جستجو
public class SearchCommand
{
public DateTime ExecutionDate { get; set; }
public DateTime? NextExecutionDate { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<Component> Components { get; set; }
// تنظیمات مربوط به روابط در OnModelCreating...
}NextExecutionDate، از عملگر شرطی ? : در داخل عبارت LINQ استفاده شده است.var query = context.Components.Where(x =>
!x.Fitups.Any() || // شرط ۱: بدون جفتکاری
(x.Fitups.Any() && x.Fitups.Any(fitup => !fitup.Welds.Any())) || // شرط ۲: بدون جوش
(command.NextExecutionDate.HasValue ? // شرط ۳: بررسی بازه تاریخ
(x.Fitups.Any() && x.Fitups.Any(fitup =>
fitup.Welds.Any(weld =>
weld.Report.Date >= command.ExecutionDate
&& weld.Report.Date < command.NextExecutionDate.Value)))
:
(x.Fitups.Any() && x.Fitups.Any(fitup =>
fitup.Welds.Any(weld =>
weld.Report.Date >= command.ExecutionDate)))
)
);Fitups.Any(...) در دو بخش true و false شرط، نگهداری کد را دشوار میکند.ExecutionDate دو بار تکرار شده است.Date < Next) باید نادیده گرفته شود (همیشه True باشد).var query = context.Components.Where(x =>
!x.Fitups.Any()
|| x.Fitups.Any(f => !f.Welds.Any())
|| x.Fitups.Any(f =>
f.Welds.Any(w =>
w.Report.Date >= command.ExecutionDate // شرط کف بازه (همیشه ثابت)
&&
(!command.NextExecutionDate.HasValue || w.Report.Date < command.NextExecutionDate.Value) // شرط سقف بازه (پویا)
)
)
);(!HasValue || Date < Value) دو حالت رخ میدهد:NextExecutionDate مقدار NULL داردResult=(Date≥Start)∧(true∨...) Result=(Date≥Start)∧true Result=Date≥Start
NextExecutionDate دارای مقدار استResult=(Date≥Start)∧(false∨Date<End) Result=(Date≥Start)∧(Date<End)
(!HasValue || Column < Value) را معمولاً به الگوی استاندارد SQL زیر ترجمه میکند:WHERE (@p_NextDate IS NULL) OR (ReportDate < @p_NextDate)
NULL باشد، دیتابیس فرض میکند که اسکن کردن کل جدول بهینه است.if/else در کد C# تقسیم کنیم تا دو کوئری SQL مجزا تولید شود، یا از هینتهای دیتابیس (OPTION(RECOMPILE)) استفاده شود. اما برای اکثر کاربردهای معمول، کد اصلاح شده در بخش ۳ کاملاً قابل قبول است.CREATE INDEX IX_Report_Date ON Reports (Date); -- یا اگر Report داخل Weld است CREATE INDEX IX_Weld_ReportDate ON Welds (Report_Date);
?: شود. با استفاده از الگوی (Parameter IS NULL OR Column == Parameter) میتوانیم کوئریهای تمیز، خوانا و قابل ترجمه به SQL استاندارد بنویسیم. در کد نهایی ارائه شده، با حذف تکرارها و استفاده از خواص جبر بولی، منطق انتخاب دادهها به شکلی منسجم پیادهسازی شد که هم حالت بازه باز (Open-ended range) و هم بازه بسته (Closed range) را به درستی پوشش میدهد.