Documentation

adrs/012-phasing-out-enterprise-library.md

ADR-012: Phasing Out Enterprise Library Data

Status

Accepted

Context

The legacy Acsis system extensively uses Microsoft Enterprise Library Data Access Application Block for database operations. This library uses DataTable and DataSet objects, stored procedures, and a database-agnostic approach that was popular in the early 2000s. However, Enterprise Library has been deprecated by Microsoft and is no longer maintained.

Current situation:

  • Extensive use of DataTable and DataSet throughout the codebase
  • Heavy reliance on stored procedures for business logic
  • Database operations using Database.ExecuteDataSet() patterns
  • Complex data transformations between DataTables and domain objects
  • No compile-time type safety for database operations
  • Difficult to unit test data access code

Decision

We will phase out Enterprise Library Data and migrate to Entity Framework Core as the standard data access technology for all Dynaplex services.

Migration approach:

  • Create ent-lib-compat-shim to provide temporary compatibility
  • Gradually replace Enterprise Library calls with Entity Framework Core
  • Move from DataTable/DataSet to strongly-typed entities
  • Migrate stored procedure logic to LINQ queries where appropriate
  • Maintain backward compatibility during transition

Compatibility Shim Strategy

The ent-lib-compat-shim provides:

// Temporary compatibility layer
public class DatabaseShim
{
    private readonly DbContext _context;
    
    public DataSet ExecuteDataSet(string storedProcedure, params object[] parameters)
    {
        // Convert EF Core results to DataSet for legacy code
        var result = _context.Database
            .SqlQueryRaw($"EXEC {storedProcedure}", parameters)
            .ToDataSet();
        return result;
    }
}

Consequences

Positive

  • Type Safety: Strongly-typed entities with compile-time checking
  • LINQ Support: Powerful query composition and type-safe queries
  • Testing: Easy to mock and unit test with in-memory database
  • Maintenance: Active development and community support
  • Performance: Query optimization and efficient change tracking
  • Migrations: Code-first migrations for schema management
  • Modern Patterns: Support for repository, unit of work patterns

Negative

  • Migration Effort: Significant work to convert existing code
  • Learning Curve: Team needs to learn Entity Framework Core
  • Stored Procedures: Some complex procedures hard to replicate
  • Performance Tuning: Different optimization strategies needed
  • Breaking Changes: Cannot maintain 100% backward compatibility

Neutral

  • Query Patterns: Different approach to data access
  • Memory Usage: Different memory characteristics than DataTable
  • Connection Management: DbContext lifetime management required

Implementation Notes

Phase 1: Compatibility Layer (Current)

// Using shim to maintain compatibility
public DataTable GetAssets()
{
    return _database.ExecuteDataSet("sp_GetAssets")
        .Tables[0];
}

Phase 2: Hybrid Approach

// New code uses EF Core
public async Task<List<Asset>> GetAssetsAsync()
{
    return await _context.Assets
        .Where(a => a.IsActive)
        .ToListAsync();
}

// Legacy code still uses shim
public DataTable GetAssetsLegacy()
{
    return _shimDatabase.ExecuteDataSet("sp_GetAssets").Tables[0];
}

Phase 3: Full Migration

// Fully migrated to EF Core
public async Task<List<Asset>> GetAssetsAsync()
{
    return await _context.Assets
        .Include(a => a.Type)
        .Include(a => a.Location)
        .Where(a => a.IsActive)
        .OrderBy(a => a.Name)
        .ToListAsync();
}

Entity Configuration

public class AssetConfiguration : IEntityTypeConfiguration<Asset>
{
    public void Configure(EntityTypeBuilder<Asset> builder)
    {
        builder.ToTable("Assets", "catalog");
        builder.HasKey(a => a.Id);
        builder.Property(a => a.Name).IsRequired().HasMaxLength(100);
        builder.HasOne(a => a.Type)
            .WithMany(t => t.Assets)
            .HasForeignKey(a => a.TypeId);
    }
}

Migration Strategy by Component

  1. New Components: Use EF Core exclusively
  2. Active Components: Migrate during feature development
  3. Stable Components: Migrate during maintenance windows
  4. Legacy Components: Keep shim until retirement

Stored Procedure Handling

For complex stored procedures that must be retained:

// Call stored procedure through EF Core
var assets = await _context.Assets
    .FromSqlRaw("EXEC sp_ComplexAssetQuery @LocationId = {0}", locationId)
    .ToListAsync();

Performance Considerations

  • Use AsNoTracking() for read-only queries
  • Implement proper indexing strategies
  • Use projection for selecting specific fields
  • Batch operations where appropriate
  • Monitor query performance with logging

Testing Strategy

// Use in-memory database for unit tests
var options = new DbContextOptionsBuilder<CatalogContext>()
    .UseInMemoryDatabase(databaseName: "TestDb")
    .Options;

using var context = new CatalogContext(options);
// Test data access logic

Timeline

  • Q1 2024: Compatibility shim implementation ✓
  • Q2 2024: New components use EF Core ✓
  • Q3 2024: Begin migration of active components
  • Q4 2024: 50% of components migrated
  • Q1 2025: 75% of components migrated
  • Q2 2025: Complete migration, remove shim
  • ADR-009: Database Schema per Service Pattern (EF Core schema configuration)
  • ADR-018: Removing MediatR and CQRS Patterns (simplification theme)
  • ADR-008: .NET 9.0 with Latest C# Features (EF Core 9 features)