Memfilter database dengan Linq
Saya sedang membangun aplikasi web MVC dengan ASP.CORE 3.1 dan EF yang terhubung ke MSSQL.
Hari ini, akhirnya setelah 3 hari, saya agak mencapai kode yang berfungsi untuk memfilter tampilan saya.
Saya punya database
- Stempel tanggal (interval 15 menit) dan
- Nilai INT mobil (3 jenis (OA, NA, NS))
- melalui Border (13 dari mereka).
- Data untuk kedua arah (Plus, Minus).
- Saya perlu memfilter ini dengan di atas ditambah mengubah interval waktu (60, 30, 15 menit)
- dan pada Hari Kerja (Senin, Jumat, Sab, Minggu, dan Biasa (Selasa-Kamis)).
Tapi saya melakukan penyaringan untuk pertama kalinya dalam hidup saya di sini. Senang itu berhasil tetapi ingin mendengar bagaimana saya bisa meningkatkan ini. Set data tidak besar. Sekitar 17 kolom x 300.000 baris.
Visualisasi
Model
Borders.cs
public class Borders
{
[Key]
public int Id { get; set; }
public int TransitId { get; set; }
public DateTime Day { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public int OA_Plus { get; set; }
public int NA_Plus { get; set; }
public int NS_Plus { get; set; }
public int ALL_Plus { get; set; }
public int OA_Minus { get; set; }
public int NA_Minus { get; set; }
public int NS_Minus { get; set; }
public int ALL_Minus { get; set; }
public int OA_Sum { get; set; }
public int NA_Sum { get; set; }
public int NS_Sum { get; set; }
public int ALL_Sum { get; set; }
}
ViewModel
BordersViewModel.cs
public class BordersViewModel
{
// DB items
public IEnumerable<Borders> Borders { get; set; }
// Other ViewModels
public IEnumerable<ChartIntensityVM> ChartIntensityVM { get; set; }
// Contains only String Key, Int Value
// Filters
public string FilterTransitNumber { get; set; } = "All";
public string FilterSeason { get; set; } = "All";
public string FilterDay { get; set; } = "All";
public string FilterTimeInterval { get; set; } = "1";
public string FilterDirection { get; set; } = "All";
public bool FilterChkboxOA { get; set; } = true;
public bool FilterChkboxNA { get; set; } = true;
public bool FilterChkboxNS { get; set; } = true;
// SelectListItems
public List<SelectListItem> ListTransitNumbers { get; set; } = new List<SelectListItem>()
{
new SelectListItem { Value = "All", Text = "All" },
};
public List<SelectListItem> ListSeasons { get; set; } = new List<SelectListItem>()
{
new SelectListItem { Value = "All", Text = "All" },
new SelectListItem { Value = "Q1", Text = "Spring (1-3)" },
new SelectListItem { Value = "Q2", Text = "Summer (3-6)" },
new SelectListItem { Value = "Q3", Text = "Fall (6-9)" },
new SelectListItem { Value = "Q4", Text = "Winter (9-12)" },
};
public List<SelectListItem> ListDays { get; set; } = new List<SelectListItem>()
{
new SelectListItem { Value = "All", Text = "All" },
new SelectListItem { Value = "Ordinary", Text = "Ordinary day (Tue, Wed, Thr)" },
new SelectListItem { Value = "Friday", Text = "Friday" },
new SelectListItem { Value = "Sunday", Text = "Sunday" },
new SelectListItem { Value = "Monday", Text = "Monday" },
};
public List<SelectListItem> ListTimeIntervals { get; set; } = new List<SelectListItem>()
{
new SelectListItem { Value = "60", Text = "1 hr" },
new SelectListItem { Value = "30", Text = "0.5 hr" },
new SelectListItem { Value = "15", Text = "0.25 hr" },
};
public List<SelectListItem> ListDirections { get; set; } = new List<SelectListItem>()
{
new SelectListItem { Value = "All", Text = "All" },
new SelectListItem { Value = "Plus", Text = "Plus" },
new SelectListItem { Value = "Minus", Text = "Minus" },
};
}
Melihat
Index.cshtml
classical view, you can see that in the picture above
SelectLists, Checkboxes, Filterbutton that is doing form POST method
Kontroler
BordersController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(BordersViewModel vm)
{
if (ModelState.IsValid)
{
vm.Borders = await _db.Borders.OrderBy(x => x.Start).ToListAsync();
populateListTransitNumbers(vm);
}
// FILTER - TransitNumber (1 - 16)
//================================
if (vm.FilterTransitNumber != "All")
{
vm.Borders = vm.Borders.Where(x => x.TransitId == Convert.ToInt32(vm.FilterTransitNumber));
}
// // FILTER - Day (Monday, Friday, Sunday, Ordinary [Tuesday, Wednesday, Thursday])
//==================================================================================
if (vm.FilterDay != "All")
{
List<string> ordinaryDays = new List<string>() { "Tuesday", "Wednesday", "Thursday" };
switch (vm.FilterDay)
{
case "Monday":
case "Friday":
case "Sunday":
vm.Borders = vm.Borders.Where(x => x.Start.DayOfWeek.ToString() == vm.FilterDay);
break;
default:
vm.Borders = vm.Borders.Where(x => ordinaryDays.Contains(x.Start.DayOfWeek.ToString()));
break;
}
}
// FILTER - Season (Q1 - Q4)
//==========================
if (vm.FilterSeason != "All")
{
switch (vm.FilterSeason)
{
case "Q1":
vm.Borders = vm.Borders.Where(x => x.Start.Month >= 1 && x.Start.Month <= 3);
break;
case "Q2":
vm.Borders = vm.Borders.Where(x => x.Start.Month >= 3 && x.Start.Month <= 6);
break;
case "Q3":
vm.Borders = vm.Borders.Where(x => x.Start.Month >= 6 && x.Start.Month <= 9);
break;
case "Q4":
vm.Borders = vm.Borders.Where(x => x.Start.Month >= 9 && x.Start.Month <= 12);
break;
}
}
// FILTER - TimeInterval (60, 30, 15)
//===================================
var filteredBordersInterval = vm.Borders
.GroupBy(x =>
{
var stamp = x.Start;
stamp = stamp.AddMinutes(-(stamp.Minute % Convert.ToInt32(vm.FilterTimeInterval)));
stamp = stamp.AddMilliseconds(-stamp.Millisecond - 1000 * stamp.Second);
return stamp;
})
.Select(g => new
{
TransitId = g.First().TransitId,
Start = g.Key,
OA_Plus = g.Sum(gi => gi.OA_Plus),
NA_Plus = g.Sum(gi => gi.NA_Plus),
NS_Plus = g.Sum(gi => gi.NS_Plus),
OA_Minus = g.Sum(gi => gi.OA_Minus),
NA_Minus = g.Sum(gi => gi.NA_Minus),
NS_Minus = g.Sum(gi => gi.NS_Minus),
});
// ORDER BY TIME INTERVAL
// Ignore the whole DateTime day, just group by HH:mm and SUM column rows
//=======================
var filteredBordersGrouped = filteredBordersInterval
.GroupBy(x => x.Start.ToString("HH:mm"))
.Select(g => new
{
DayTime = g.Key,
OA_Plus = g.Sum(gi => gi.OA_Plus),
NA_Plus = g.Sum(gi => gi.NA_Plus),
NS_Plus = g.Sum(gi => gi.NS_Plus),
OA_Minus = g.Sum(gi => gi.OA_Minus),
NA_Minus = g.Sum(gi => gi.NA_Minus),
NS_Minus = g.Sum(gi => gi.NS_Minus),
});
// FINAL FILTER into X/Y values for the Chart
// Sum only those cars that are checked and both or individual directions
//===========================================
vm.ChartIntensityVM = filteredBordersGrouped
.GroupBy(x => x.DayTime)
.Select(g =>
{
int PlusSum = g.Sum(gi => vm.FilterChkboxOA == true ? gi.OA_Plus : 0)
+ g.Sum(gi => vm.FilterChkboxNA == true ? gi.NA_Plus : 0)
+ g.Sum(gi => vm.FilterChkboxNS == true ? gi.NS_Plus : 0);
int MinusSum = g.Sum(gi => vm.FilterChkboxOA == true ? gi.OA_Minus : 0)
+ g.Sum(gi => vm.FilterChkboxNA == true ? gi.NA_Minus : 0)
+ g.Sum(gi => vm.FilterChkboxNS == true ? gi.NS_Minus : 0);
int AllSum = vm.FilterDirection == "All" ? PlusSum + MinusSum : (vm.FilterDirection == "Plus" ? PlusSum : MinusSum);
return new ChartIntensityVM
{
Key = g.Key,
Value = AllSum,
};
});
return View(vm);
}
Jawaban
Beberapa komentar singkat
Ikuti standar penamaan. Nama properti, dll. Tidak boleh berisi apa pun kecuali karakter alfanumerik. Tidak ada garis bawah dll.
Gunakan nama yang bermakna.
"OA"tidak ada artinya,"OA_Plus"bahkan lebih membingungkan.Sebuah nama kelas tidak boleh jamak (beberapa pengecualian berlaku):
Borders.Begitu banyak string ajaib.
"All"muncul berkali-kali, misalnya. Pertimbangkan untuk memindahkan ini kestatickelas sebagaipublic const stringproperti.Jangan menyingkat tanpa tujuan: memberi nama
Chkboxtidak akan membuat kode Anda berjalan lebih cepat.Jangan menyebut sesuatu sebagai "ListXXXX", misalnya
ListDays. Jika itu adalah daftar hari, beri nama "Hari".ListDaystampaknya menghilangkan hari Sabtu, dan "Thr" bukanlah singkatan yang benar untuk "Kamis".Musim Anda / perempat tampaknya tumpang tindih:
"Spring (1-3)"vs"Summer (3-6)". IMHO harus "1-3", "4-6", dll. Namun, ini juga diterapkan dengan cara ini dalam logika bisnis Anda -vm.Borders.Where(x => x.Start.Month >= 3 && x.Start.Month <= 6);- jadi saya rasa itu adalah logika "yang benar"? Tetap saja, tidak masuk akal bagi saya bahwa Q1 menampilkan data selama tiga bulan, sedangkan yang lain menampilkan data selama empat bulan. IMHO ini terlihat seperti bug besar.public async Task<IActionResult> Index(BordersViewModel vm)lebih dari 100 baris. Saya akan memindahkan hampir semua kode itu ke kelas terpisah.
Judul pertanyaan Anda mengatakan "Memfilter database dengan Linq", namun pertanyaan Anda berfungsi sepenuhnya dengan model tampilan. Saya benar-benar bertanya-tanya apakah beberapa pemfilteran yang Anda lakukan dapat dilakukan dengan lebih efisien dalam kueri, alih-alih (apa yang tampaknya Anda lakukan) membuang semua data yang tersedia dalam Bordersobjek dan kemudian menerapkan banyak logika pemfilteran.