DKNet

Migration Guide

This guide helps you migrate between different versions of DKNet Framework and provides guidance for handling breaking changes.

πŸ“‹ Table of Contents


πŸš€ Current Migration Scenarios

From Legacy DKNet to 2024.12.0+

This is a major architectural migration from legacy packages to the new Domain-Driven Design framework.

Key Changes

Migration Strategy

1. Assessment Phase

# Analyze your current usage
git grep -r "DKNet" --include="*.cs" src/
# Review dependencies
dotnet list package --include-transitive | grep DKNet

2. Incremental Migration

3. Component Migration Order

  1. Core Extensions β†’ DKNet.Fw.Extensions
  2. Data Access β†’ DKNet.EfCore.* packages
  3. Business Logic β†’ Domain entities and services
  4. API Layer β†’ Controllers/endpoints
  5. Infrastructure β†’ External service integrations

πŸ“¦ Version-Specific Migrations

Upgrading to .NET 9.0

Prerequisites

# Install .NET 9.0 SDK
./src/dotnet-install.sh --version 9.0.100

# Update global.json
{
  "sdk": {
    "version": "9.0.100"
  }
}

Project Files

<!-- Update target framework -->
<TargetFramework>net9.0</TargetFramework>

<!-- Update package references -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.App" />

Entity Framework Core Migration

Before (Legacy)

public class ProductRepository
{
    private readonly DbContext _context;
    
    public ProductRepository(DbContext context)
    {
        _context = context;
    }
    
    public async Task<Product> GetAsync(int id)
    {
        return await _context.Set<Product>().FindAsync(id);
    }
}

After (DKNet 2024.12.0+)

public class ProductRepository : Repository<Product>, IProductRepository
{
    public ProductRepository(AppDbContext context) : base(context) { }
    
    public async Task<Product?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await GetByIdAsync(id, cancellationToken);
    }
    
    // Specification support
    public async Task<IEnumerable<Product>> FindAsync(Specification<Product> spec)
    {
        return await Gets().Where(spec.ToExpression()).ToListAsync();
    }
}

πŸ—οΈ Architecture Migration

From N-Layer to Onion Architecture

Legacy Structure

Solution/
β”œβ”€β”€ Web/              # Presentation
β”œβ”€β”€ Business/         # Business Logic
β”œβ”€β”€ Data/            # Data Access
└── Common/          # Shared

New Structure (Onion)

Solution/
β”œβ”€β”€ Api/             # Presentation Layer
β”œβ”€β”€ AppServices/     # Application Layer
β”œβ”€β”€ Domains/         # Domain Layer
└── Infra/           # Infrastructure Layer

Domain Entity Migration

Before

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public DateTime CreatedAt { get; set; }
}

After

[Table("Products", Schema = "catalog")]
public class Product : AggregateRoot
{
    public Product(string name, decimal price, string createdBy)
        : base(Guid.NewGuid(), createdBy)
    {
        Name = name;
        Price = price;
    }

    public string Name { get; private set; }
    public decimal Price { get; private set; }

    public void UpdatePrice(decimal newPrice, string updatedBy)
    {
        Price = newPrice;
        SetUpdatedBy(updatedBy);
        AddEvent(new ProductPriceChangedEvent(Id, Price));
    }
}

πŸ”„ CQRS Migration

Command/Query Separation

Before (Traditional Service)

public class ProductService
{
    public async Task<Product> CreateProductAsync(CreateProductDto dto)
    {
        // Create logic
    }
    
    public async Task<Product> GetProductAsync(int id)
    {
        // Get logic
    }
}

After (CQRS)

// Command
public record CreateProductCommand : IRequest<ProductResult>
{
    public string Name { get; init; }
    public decimal Price { get; init; }
}

public class CreateProductHandler : IRequestHandler<CreateProductCommand, ProductResult>
{
    public async Task<ProductResult> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        // Command handling logic
    }
}

// Query
public record GetProductQuery : IRequest<ProductResult>
{
    public Guid Id { get; init; }
}

public class GetProductHandler : IRequestHandler<GetProductQuery, ProductResult>
{
    public async Task<ProductResult> Handle(GetProductQuery request, CancellationToken cancellationToken)
    {
        // Query handling logic
    }
}

πŸ“Š Database Migration

Schema Updates

Add Migration for New Structure

# Create migration
dotnet ef migrations add UpgradeToOnionArchitecture

# Review generated migration
# Update database
dotnet ef database update

Data Migration Script

-- Migrate existing data to new schema
-- Add audit fields
ALTER TABLE Products ADD CreatedBy NVARCHAR(255) NOT NULL DEFAULT 'SYSTEM';
ALTER TABLE Products ADD CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE();
ALTER TABLE Products ADD UpdatedBy NVARCHAR(255) NULL;
ALTER TABLE Products ADD UpdatedAt DATETIME2 NULL;

-- Convert INT IDs to GUIDs (if needed)
-- This is a complex migration - consider keeping INT IDs if possible

πŸ§ͺ Testing Migration

From Legacy Testing to Modern Patterns

Before

[Test]
public async Task CreateProduct_ShouldReturnProduct()
{
    // In-memory database setup
    var options = new DbContextOptionsBuilder<DbContext>()
        .UseInMemoryDatabase(databaseName: "TestDb")
        .Options;
        
    using var context = new DbContext(options);
    var repository = new ProductRepository(context);
    
    // Test logic
}

After

[Test]
public async Task CreateProduct_ShouldReturnProduct()
{
    // TestContainers setup
    await using var container = new MsSqlBuilder()
        .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
        .Build();
        
    await container.StartAsync();
    
    var connectionString = container.GetConnectionString();
    var services = new ServiceCollection();
    services.AddDbContext<AppDbContext>(options => 
        options.UseSqlServer(connectionString));
    
    // Test with real database
}

πŸ› οΈ Migration Tools

Automated Migration Helper

public class MigrationHelper
{
    public static async Task MigrateDataAsync(IServiceProvider serviceProvider)
    {
        using var scope = serviceProvider.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        
        // Ensure database is created
        await context.Database.EnsureCreatedAsync();
        
        // Run custom migrations
        await MigrateProductsAsync(context);
        await MigrateUsersAsync(context);
    }
    
    private static async Task MigrateProductsAsync(AppDbContext context)
    {
        // Custom migration logic for products
        var products = await context.Set<OldProduct>().ToListAsync();
        foreach (var oldProduct in products)
        {
            var newProduct = new Product(
                oldProduct.Name, 
                oldProduct.Price, 
                "MIGRATION");
            context.Set<Product>().Add(newProduct);
        }
        await context.SaveChangesAsync();
    }
}

Configuration Migration

public static class ConfigurationMigration
{
    public static IServiceCollection MigrateFromLegacy(
        this IServiceCollection services, 
        IConfiguration configuration)
    {
        // Map old configuration to new structure
        var legacyConfig = configuration.GetSection("Legacy");
        var newConfig = new DKNetOptions
        {
            DatabaseConnectionString = legacyConfig["Database:ConnectionString"],
            EnableAuditFields = bool.Parse(legacyConfig["Audit:Enabled"] ?? "true"),
            // ... other mappings
        };
        
        services.Configure<DKNetOptions>(options =>
        {
            options.DatabaseConnectionString = newConfig.DatabaseConnectionString;
            options.EnableAuditFields = newConfig.EnableAuditFields;
        });
        
        return services;
    }
}

⚠️ Common Issues

1. ID Type Changes

Issue: Converting from int to Guid IDs Solution:

2. Breaking API Changes

Issue: Public API contracts change Solution:

3. Performance Issues

Issue: New patterns may impact performance Solution:

4. Dependency Injection Changes

Issue: Service registration patterns change Solution:

// Old
services.AddScoped<ProductService>();

// New
services.AddScoped<IProductRepository, ProductRepository>();
services.AddMediatR(typeof(CreateProductHandler));

πŸ“‹ Migration Checklist

Pre-Migration

During Migration

Post-Migration


πŸ†˜ Getting Help

If you encounter issues during migration:

  1. Check Documentation: Review Getting Started and Examples
  2. Search Issues: Look for similar problems in GitHub Issues
  3. Ask Questions: Create a new issue with the migration label
  4. Join Discussions: Participate in GitHub Discussions

πŸ’‘ Migration Tip: Take your time with migration. It’s better to migrate correctly in stages than to rush and introduce bugs. Use the SlimBus template as your reference implementation!