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;
///
/// 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.
///
public class SqlDataSeeder : IDataSeedContributor, ITransientDependency
{
private readonly IDbContextProvider _dbContextProvider;
private readonly ILogger _logger;
public SqlDataSeeder(
IDbContextProvider dbContextProvider,
ILogger 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);
}
}