erp-platform/api/src/Kurs.Platform.Application/DeveloperKit/CrudMigrationAppService.cs

207 lines
7 KiB
C#
Raw Normal View History

2025-08-11 06:34:44 +00:00
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Application.Dtos;
using System.Text;
using Volo.Abp.Domain.Entities;
using Kurs.Platform.Entities;
using Kurs.Platform.DeveloperKit;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Kurs.Platform.Domain.DeveloperKit;
namespace Platform.Api.Application;
2025-11-05 09:02:16 +00:00
public class CrudMigrationAppService : CrudAppService<
CrudMigration,
CrudMigrationDto,
2025-08-11 06:34:44 +00:00
Guid,
PagedAndSortedResultRequestDto,
2025-11-05 09:02:16 +00:00
CreateUpdateCrudMigrationDto>, ICrudMigrationAppService
2025-08-11 06:34:44 +00:00
{
2025-11-05 09:02:16 +00:00
private readonly IRepository<CrudMigration, Guid> _migrationRepository;
2025-08-11 06:34:44 +00:00
private readonly IRepository<CustomEntity, Guid> _entityRepository;
private readonly IRepository<CustomEntity, Guid> _fieldRepository;
private readonly IApiMigrationRepository _customSqlExecutor;
2025-11-05 09:02:16 +00:00
public CrudMigrationAppService(
IRepository<CrudMigration, Guid> migrationRepository,
2025-08-11 06:34:44 +00:00
IRepository<CustomEntity, Guid> entityRepository,
IRepository<CustomEntity, Guid> fieldRepository,
IApiMigrationRepository customSqlExecutor
) : base(migrationRepository)
{
_migrationRepository = migrationRepository;
_entityRepository = entityRepository;
_fieldRepository = fieldRepository;
_customSqlExecutor = customSqlExecutor;
}
2025-11-05 09:02:16 +00:00
public virtual async Task<CrudMigrationDto> ApplyMigrationAsync(Guid id)
2025-08-11 06:34:44 +00:00
{
var migration = await _migrationRepository.GetAsync(id);
try
{
await _customSqlExecutor.ExecuteSqlAsync(migration.SqlScript);
migration.Status = "applied";
migration.AppliedAt = DateTime.UtcNow;
migration.ErrorMessage = null;
await _migrationRepository.UpdateAsync(migration, autoSave: true);
var entity = await _entityRepository.GetAsync(migration.EntityId);
entity.MigrationStatus = "applied";
await _entityRepository.UpdateAsync(entity, autoSave: true);
2025-11-05 09:02:16 +00:00
return ObjectMapper.Map<CrudMigration, CrudMigrationDto>(migration);
2025-08-11 06:34:44 +00:00
}
catch (Exception ex)
{
migration.Status = "failed";
migration.ErrorMessage = ex.Message;
await _migrationRepository.UpdateAsync(migration, autoSave: true);
var entity = await _entityRepository.GetAsync(migration.EntityId);
entity.MigrationStatus = "failed";
await _entityRepository.UpdateAsync(entity, autoSave: true);
throw;
}
}
2025-11-05 09:02:16 +00:00
public virtual async Task<List<CrudMigrationDto>> GetPendingMigrationsAsync()
2025-08-11 06:34:44 +00:00
{
var queryable = await _migrationRepository.GetQueryableAsync();
var pending = await queryable
.Where(m => m.Status == "pending")
.OrderBy(m => m.CreationTime)
.ToListAsync();
2025-11-05 09:02:16 +00:00
return ObjectMapper.Map<List<CrudMigration>, List<CrudMigrationDto>>(pending);
2025-08-11 06:34:44 +00:00
}
2025-11-05 09:02:16 +00:00
public virtual async Task<CrudMigrationDto> GenerateMigrationAsync(Guid entityId)
2025-08-11 06:34:44 +00:00
{
var queryable = await _entityRepository.GetQueryableAsync();
var entity = await queryable
.Include(x => x.Fields)
.FirstOrDefaultAsync(x => x.Id == entityId);
if (entity == null)
throw new EntityNotFoundException($"CustomEntity with id {entityId} not found");
var fileName = $"{DateTime.UtcNow:yyyyMMddHHmmss}_Create{entity.Name}Table.sql";
var sqlScript = GenerateSqlScript(entity);
2025-11-05 09:02:16 +00:00
var migration = new CrudMigration
2025-08-11 06:34:44 +00:00
{
EntityId = entityId,
EntityName = entity.Name,
FileName = fileName,
SqlScript = sqlScript,
Status = "pending"
};
migration = await _migrationRepository.InsertAsync(migration, autoSave: true);
entity.MigrationId = migration.Id;
entity.MigrationStatus = "pending";
await _entityRepository.UpdateAsync(entity, autoSave: true);
2025-11-05 09:02:16 +00:00
return ObjectMapper.Map<CrudMigration, CrudMigrationDto>(migration);
2025-08-11 06:34:44 +00:00
}
private string GenerateSqlScript(CustomEntity entity)
{
if (entity.Fields == null || !entity.Fields.Any())
throw new InvalidOperationException($"Entity '{entity.Name}' does not have any fields defined.");
var sb = new StringBuilder();
sb.AppendLine($@"/*
# Create {entity.DisplayName} table
1. New Table
- [{entity.TableName}]");
foreach (var field in entity.Fields)
{
sb.AppendLine($" - [{field.Name}] ({field.Type}{(field.IsRequired ? ", required" : "")})");
}
sb.AppendLine("*/");
sb.AppendLine($"\nIF OBJECT_ID(N'{entity.TableName}', N'U') IS NULL");
sb.AppendLine($"CREATE TABLE [{entity.TableName}] (");
sb.AppendLine(" [Id] UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),");
foreach (var field in entity.Fields)
{
var sqlType = GetSqlType(field.Type, field.MaxLength);
sb.Append($" [{field.Name}] {sqlType}");
if (field.IsRequired)
sb.Append(" NOT NULL");
if (!string.IsNullOrWhiteSpace(field.DefaultValue))
sb.Append($" DEFAULT {FormatDefaultValue(field.Type, field.DefaultValue)}");
sb.AppendLine(",");
}
if (entity.HasAuditFields)
{
sb.AppendLine(" [CreationTime] DATETIME2 DEFAULT SYSUTCDATETIME() NOT NULL,");
sb.AppendLine(" [CreatorId] UNIQUEIDENTIFIER NULL,");
sb.AppendLine(" [LastModificationTime] DATETIME2 DEFAULT SYSUTCDATETIME() NOT NULL,");
sb.AppendLine(" [LastModifierId] UNIQUEIDENTIFIER NULL,");
}
if (entity.HasSoftDelete)
{
sb.AppendLine(" [IsDeleted] BIT DEFAULT 0 NOT NULL,");
sb.AppendLine(" [DeleterId] UNIQUEIDENTIFIER NULL,");
sb.AppendLine(" [DeletionTime] DATETIME2 DEFAULT SYSUTCDATETIME() NOT NULL,");
}
// Remove last comma
var script = sb.ToString().TrimEnd(',', '\r', '\n');
script += "\n);\n";
return script;
}
private string GetSqlType(string fieldType, int? maxLength)
{
return fieldType.ToLower() switch
{
"string" => maxLength.HasValue ? $"NVARCHAR({maxLength.Value})" : "NVARCHAR(MAX)",
"number" => "INT",
"decimal" => "DECIMAL(18, 2)",
"boolean" => "BIT",
"date" => "DATETIME2",
"guid" => "UNIQUEIDENTIFIER",
_ => "NVARCHAR(MAX)"
};
}
private string FormatDefaultValue(string fieldType, string defaultValue)
{
return fieldType.ToLower() switch
{
"string" => $"N'{defaultValue}'",
"date" => $"'{defaultValue}'", // ISO format beklenir
"guid" => $"'{defaultValue}'",
"boolean" => (defaultValue.ToLower() == "true" || defaultValue == "1") ? "1" : "0",
_ => defaultValue
};
}
}