2026-05-03 19:50:51 +00:00
|
|
|
|
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;
|
2026-05-25 14:31:54 +00:00
|
|
|
|
using static Sozsoft.Settings.SettingsConsts;
|
2026-05-03 19:50:51 +00:00
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2026-05-25 14:31:54 +00:00
|
|
|
|
var dataDirectoryName = GetDataDirectoryName();
|
|
|
|
|
|
var sqlDataPath = Path.Combine(Directory.GetCurrentDirectory(), "Seeds", dataDirectoryName);
|
2026-05-03 19:50:51 +00:00
|
|
|
|
if (!Directory.Exists(sqlDataPath))
|
|
|
|
|
|
{
|
2026-05-25 14:31:54 +00:00
|
|
|
|
_logger.LogInformation("Seeds/{DirectoryName} directory not found, skipping SqlDataSeeder.", dataDirectoryName);
|
2026-05-03 19:50:51 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 16:49:46 +00:00
|
|
|
|
var isHostSeed = context.TenantId == null;
|
|
|
|
|
|
var sqlFiles = GetSqlFiles(sqlDataPath, isHostSeed);
|
2026-05-03 19:50:51 +00:00
|
|
|
|
|
|
|
|
|
|
if (sqlFiles.Length == 0)
|
|
|
|
|
|
{
|
2026-05-25 14:31:54 +00:00
|
|
|
|
_logger.LogInformation("No .sql files found in Seeds/{DirectoryName} directory, skipping SqlDataSeeder.", dataDirectoryName);
|
2026-05-03 19:50:51 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 14:31:54 +00:00
|
|
|
|
_logger.LogInformation(
|
2026-05-26 16:49:46 +00:00
|
|
|
|
"SqlDataSeeder started for provider '{Provider}' from Seeds/{DirectoryName}. Seed target: {SeedTarget}. {Count} file(s) to be processed.",
|
2026-05-25 14:31:54 +00:00
|
|
|
|
DefaultDatabaseProvider,
|
|
|
|
|
|
dataDirectoryName,
|
2026-05-26 16:49:46 +00:00
|
|
|
|
isHostSeed ? "Host" : "Tenant",
|
2026-05-25 14:31:54 +00:00
|
|
|
|
sqlFiles.Length);
|
2026-05-03 19:50:51 +00:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 14:31:54 +00:00
|
|
|
|
private static string GetDataDirectoryName()
|
|
|
|
|
|
{
|
|
|
|
|
|
return DefaultDatabaseProvider == DatabaseProvider.PostgreSql
|
|
|
|
|
|
? "PostgresData"
|
|
|
|
|
|
: "SqlData";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 16:49:46 +00:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 19:50:51 +00:00
|
|
|
|
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"),
|
2026-05-15 20:47:52 +00:00
|
|
|
|
(@"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"),
|
2026-05-03 19:50:51 +00:00
|
|
|
|
(@"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"),
|
2026-05-15 20:47:52 +00:00
|
|
|
|
(@"DROP\s+FUNCTION\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "FUNCTION"),
|
2026-05-03 19:50:51 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|