sozsoft-platform/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlDataSeeder.cs
2026-05-26 19:49:46 +03:00

165 lines
6.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Sozsoft.Platform.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using static Sozsoft.Settings.SettingsConsts;
namespace Sozsoft.Platform.Data.Seeds;
/// <summary>
/// SqlTableDesigner üzerinden deploy edilen tabloları Seeds/SqlData/*.sql dosyalarından okuyarak veritabanına uygular.
/// Her dosya, tek bir tabloya (veya ilgili ALTER/INDEX script'lerine) ait IF OBJECT_ID kontrolü içeren T-SQL batch'leri içermelidir.
/// Veritabanı silinip yeniden oluşturulduğunda bu seeder tüm tablo scriptlerini yeniden çalıştırır.
/// </summary>
public class SqlDataSeeder : IDataSeedContributor, ITransientDependency
{
private readonly IDbContextProvider<PlatformDbContext> _dbContextProvider;
private readonly ILogger<SqlDataSeeder> _logger;
public SqlDataSeeder(
IDbContextProvider<PlatformDbContext> dbContextProvider,
ILogger<SqlDataSeeder> logger)
{
_dbContextProvider = dbContextProvider;
_logger = logger;
}
public async Task SeedAsync(DataSeedContext context)
{
var dataDirectoryName = GetDataDirectoryName();
var sqlDataPath = Path.Combine(Directory.GetCurrentDirectory(), "Seeds", dataDirectoryName);
if (!Directory.Exists(sqlDataPath))
{
_logger.LogInformation("Seeds/{DirectoryName} directory not found, skipping SqlDataSeeder.", dataDirectoryName);
return;
}
var isHostSeed = context.TenantId == null;
var sqlFiles = GetSqlFiles(sqlDataPath, isHostSeed);
if (sqlFiles.Length == 0)
{
_logger.LogInformation("No .sql files found in Seeds/{DirectoryName} directory, skipping SqlDataSeeder.", dataDirectoryName);
return;
}
_logger.LogInformation(
"SqlDataSeeder started for provider '{Provider}' from Seeds/{DirectoryName}. Seed target: {SeedTarget}. {Count} file(s) to be processed.",
DefaultDatabaseProvider,
dataDirectoryName,
isHostSeed ? "Host" : "Tenant",
sqlFiles.Length);
var dbContext = await _dbContextProvider.GetDbContextAsync();
foreach (var filePath in sqlFiles)
{
var fileName = Path.GetFileName(filePath);
try
{
var sqlContent = await File.ReadAllTextAsync(filePath);
// Split by GO batch separators (SQL Server convention)
var batches = Regex.Split(sqlContent, @"^\s*GO\s*$",
RegexOptions.Multiline | RegexOptions.IgnoreCase);
foreach (var batch in batches)
{
var sql = batch.Trim();
if (string.IsNullOrWhiteSpace(sql))
continue;
try
{
var (action, objectName, objectType) = ExtractSqlInfo(sql);
if (!string.IsNullOrEmpty(objectName))
_logger.LogInformation("Executing: {Action} {ObjectType} {ObjectName}", action, objectType, objectName);
else
_logger.LogInformation("Executing SQL batch from {FileName}", fileName);
await dbContext.Database.ExecuteSqlRawAsync(sql);
}
catch (Exception ex)
{
_logger.LogError(ex, "SQL batch failed in file {FileName}: {Sql}", fileName, sql);
throw;
}
}
}
catch (Exception ex) when (ex is not InvalidOperationException)
{
_logger.LogError(ex, "Failed to process SQL seed file: {FileName}", fileName);
throw;
}
}
_logger.LogInformation("SqlDataSeeder completed. {Count} file(s) processed.", sqlFiles.Length);
}
private static string GetDataDirectoryName()
{
return DefaultDatabaseProvider == DatabaseProvider.PostgreSql
? "PostgresData"
: "SqlData";
}
private static string[] GetSqlFiles(string dataDirectoryPath, bool includeHostData)
{
var sqlFiles = Directory.GetFiles(dataDirectoryPath, "*.sql")
.OrderBy(f => Path.GetFileName(f))
.ToList();
if (includeHostData)
{
var hostDataPath = Path.Combine(dataDirectoryPath, "HostData");
if (Directory.Exists(hostDataPath))
{
sqlFiles.AddRange(Directory.GetFiles(hostDataPath, "*.sql")
.OrderBy(f => Path.GetFileName(f)));
}
}
return sqlFiles.ToArray();
}
private static (string Action, string? ObjectName, string? ObjectType) ExtractSqlInfo(string sql)
{
var patterns = new[]
{
(@"CREATE\s+TABLE\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "TABLE"),
(@"CREATE\s+VIEW\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "VIEW"),
(@"CREATE\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "PROCEDURE"),
(@"CREATE\s+FUNCTION\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "FUNCTION"),
(@"ALTER\s+TABLE\s+(\[?\w+\]?\.?\[?\w+\]?)", "ALTER", "TABLE"),
(@"ALTER\s+VIEW\s+(\[?\w+\]?\.?\[?\w+\]?)", "ALTER", "VIEW"),
(@"ALTER\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "ALTER", "PROCEDURE"),
(@"ALTER\s+FUNCTION\s+(\[?\w+\]?\.?\[?\w+\]?)", "ALTER", "FUNCTION"),
(@"CREATE\s+(UNIQUE\s+)?INDEX\s+(\[?\w+\]?)", "CREATE", "INDEX"),
(@"CREATE\s+TRIGGER\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "TRIGGER"),
(@"DROP\s+TABLE\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "TABLE"),
(@"DROP\s+VIEW\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "VIEW"),
(@"DROP\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "PROCEDURE"),
(@"DROP\s+FUNCTION\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "FUNCTION"),
};
foreach (var (pattern, action, type) in patterns)
{
var match = Regex.Match(sql, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
var captured = match.Groups[match.Groups.Count - 1].Value;
return (action, captured, type);
}
}
return ("UNKNOWN", null, null);
}
}