Designing a Robust Parking Fee Calculation and Concurrency-Safe Management System

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.

Tags: C# EntityFrameworkCore ConcurrencyControl WindowsForms ParkingSystem

Posted on Wed, 16 Sep 2026 16:15:05 +0000 by NSW42