165 lines
6.5 KiB
C#
165 lines
6.5 KiB
C#
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);
|
||
}
|
||
}
|