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); } }