This guide helps you migrate between different versions of DKNet Framework and provides guidance for handling breaking changes.
This is a major architectural migration from legacy packages to the new Domain-Driven Design framework.
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
DKNet.Fw.Extensions
DKNet.EfCore.*
packagesPrerequisites
# 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" />
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();
}
}
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
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));
}
}
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
}
}
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
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
}
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();
}
}
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;
}
}
Issue: Converting from int
to Guid
IDs
Solution:
int
IDs if possibleIssue: Public API contracts change Solution:
/api/v1/
, /api/v2/
)Issue: New patterns may impact performance Solution:
Issue: Service registration patterns change Solution:
// Old
services.AddScoped<ProductService>();
// New
services.AddScoped<IProductRepository, ProductRepository>();
services.AddMediatR(typeof(CreateProductHandler));
If you encounter issues during migration:
migration
labelπ‘ 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!