Parking Fee Computation Logic
The fee calculation implements tiered pricing with daily caps and ceiling-based hourly ronuding. Below is a refactored implementation using immutable inputs, explicit duration handling, and configurable rate policies:
public record PricingPolicy(decimal HourlyRate, decimal DailyCap, int MinimumChargeHours = 1);
public static decimal ComputeFee(DateTimeOffset entry, DateTimeOffset exit, PricingPolicy policy)
{
if (exit < entry) throw new ArgumentException("Exit time must be after entry time.");
var totalMinutes = (int)(exit - entry).TotalMinutes;
var fullHours = (int)Math.Ceiling(totalMinutes / 60.0);
var effectiveHours = Math.Max(fullHours, policy.MinimumChargeHours);
var baseCharge = effectiveHours * policy.HourlyRate;
var days = (int)(exit.Date - entry.Date).TotalDays + 1;
return Math.Min(baseCharge, policy.DailyCap) +
Math.Max(0, days - 1) * policy.DailyCap;
}
Transactional Vehicle Exit Workflow
This version separates concerns: fee computation, persistence logic, and UI feedback. It uses async/await for database operations and avoids blocking the UI thread:
private async Task<bool> FinalizeVehicleExitAsync(string plateNumber)
{
if (string.IsNullOrWhiteSpace(plateNumber)) return false;
await using var context = new ParkingDbContext();
var entry = await context.VehicleEntries
.FirstOrDefaultAsync(e => e.LicensePlate == plateNumber && e.ExitTime == null);
if (entry == null) return false;
var now = DateTimeOffset.Now;
var fee = ComputeFee(entry.EntryTime, now, new PricingPolicy(5.0m, 50.0m));
var result = MessageBox.Show($"Total fee: ¥{fee:F2}. Confirm exit?",
"Confirm Payment", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result != DialogResult.Yes) return false;
await using var tx = await context.Database.BeginTransactionAsync();
try
{
entry.ExitTime = now;
entry.Fee = fee;
var spot = await context.ParkingSpots.FindAsync(entry.ParkingSpotId);
if (spot != null) spot.IsOccupied = false;
await context.SaveChangesAsync();
await tx.CommitAsync();
return true;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
Optimized Concurrent Entry Registration
Leverages database-level row locking via SELECT FOR UPDATE semantics (emulated with AsNoTracking().FirstOrDefault() followed by explicit attach) and includes retry logic for transient failures:
private async Task<bool> ReserveParkingSpotAsync(string plate)
{
for (int attempt = 0; attempt < 3; attempt++)
{
await using var context = new ParkingDbContext();
try
{
// Find and lock an available spot
var spot = await context.ParkingSpots
.Where(s => !s.IsOccupied)
.OrderBy(s => s.SpotId)
.FirstOrDefaultAsync();
if (spot == null) return false;
// Mark as occupied before saving
spot.IsOccupied = true;
var entry = new VehicleEntry
{
LicensePlate = plate,
EntryTime = DateTimeOffset.Now,
ParkingSpotId = spot.SpotId
};
context.ParkingSpots.Update(spot);
context.VehicleEntries.Add(entry);
await context.SaveChangesAsync();
return true;
}
catch (DbUpdateConcurrencyException)
{
if (attempt == 2) throw;
await Task.Delay(100 * (int)Math.Pow(2, attempt)); // Exponential backoff
}
}
return false;
}
Asynchronous Paginated Record Retrieval
Uses Entity Framework Core’s native async methods and avoids Task.Run for database calls — improving scalability and eliminating unnecessary thread pool usage:
private async Task LoadHistoryPageAsync(int pageNumber, int pageSize)
{
lblLoading.Visible = true;
try
{
await using var context = new ParkingDbContext();
var records = await context.VehicleEntries
.Where(e => e.ExitTime.HasValue)
.OrderByDescending(e => e.EntryTime)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
dgvRecords.DataSource = records;
var totalCount = await context.VehicleEntries
.CountAsync(e => e.ExitTime.HasValue);
var totalPages = (int)Math.Ceiling((double)totalCount / pageSize);
lblPageInfo.Text = $"Page {pageNumber} of {totalPages}";
}
catch (Exception ex)
{
MessageBox.Show($"Failed to load history: {ex.Message}", "Error");
}
finally
{
lblLoading.Visible = false;
}
}
Real-Time Spot Status Updates
Implements hybrid polling with event-driven invalidation: a lightweight background timer checks only changed spots (via change tracking or timestamp comparison), while UI updates are triggered via INotifyPropertyChanged on bound collections.
Visual Representation Strategy
Generates dynamic SVG-based floor plans client-side using System.Drawing.Common, where each spot renders as a <rect> element styled by occupancy state (fill="#4CAF50" for free, fill="#F44336" for occupied), supporting zoom and click-through navigation.