diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs index 1f94777..9fb3ca6 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/DataBuilder/AonprdDataBuilder.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; +using Pf2eModel; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; @@ -11,11 +12,6 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; /// internal class AonprdDataBuilder(AonprdLoader loader) { - /// - /// Класс работы с базой данных. - /// - public readonly Pf2eDatabase Database = Pf2eDatabase.Instance; - /// /// Основной класс для работы с данными Архива Нефиса. /// @@ -38,7 +34,10 @@ internal class AonprdDataBuilder(AonprdLoader loader) { Console.WriteLine("Truncating all old data..."); - Database.ExecuteCommand("SELECT f_truncate_tables();"); + using Pf2eDbContext context = new(); + string cmd = context.Entities.Truncate() + "\n" + context.TruncateAll(); + + Console.WriteLine(cmd); Console.WriteLine("Truncation completed!"); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs index 0249020..cd88174 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs @@ -2,13 +2,12 @@ using Konsole; using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; +using Pf2eModel; namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; internal class PF2eFoundryDataBuilder { - public Pf2eDatabase Database = Pf2eDatabase.Instance; - /// /// Загрузчик данных. /// @@ -51,7 +50,10 @@ internal class PF2eFoundryDataBuilder { Console.WriteLine("Truncating all old data..."); - Database.ExecuteCommand("SELECT f_truncate_tables();"); + using Pf2eDbContext context = new(); + string cmd = context.Entities.Truncate() + "\n" + context.TruncateAll(); + + Console.WriteLine(cmd); Console.WriteLine("Truncation completed!"); } diff --git a/Pf2eDataScrapper/Pf2eDatabase.cs b/Pf2eDataScrapper/Pf2eDatabase.cs deleted file mode 100644 index b3197f6..0000000 --- a/Pf2eDataScrapper/Pf2eDatabase.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Text.RegularExpressions; -using Npgsql; - -namespace Pf2eDataScrapper; - -/// -/// Main class for database connection and operations. -/// -public partial class Pf2eDatabase -{ - /// - /// Подключение к базе данных. - /// - public readonly NpgsqlConnection Connection; - - /// - /// Имя пользователя для подключения к базе данных. - /// - public readonly string Username; - - /// - /// Название базы данных. - /// - public readonly string DatabaseName; - - private Pf2eDatabase() - { - Username = UsernamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - DatabaseName = DatabaseNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - - Connection = new(EnvSettings.DatabaseConnection); - Connection.Open(); - } - - ~Pf2eDatabase() - { - Connection.Close(); - } - - /// - /// Singleton instance of the database connection manager. - /// - public static Pf2eDatabase Instance { get; } = new(); - - /// - /// Current transaction associated with this database connection. - /// - public NpgsqlTransaction? Transaction { get; private set; } - - /// - /// Begin a new transaction on the database connection. - /// - public void StartTransaction() - { - if (Transaction != null) - { - throw new InvalidOperationException("Transaction already started."); - } - - Transaction = Connection.BeginTransaction(); - } - - /// - /// Commit the current transaction to the database. - /// - public void Commit() - { - if (Transaction == null) - { - throw new InvalidOperationException("No transaction started."); - } - - Transaction.Commit(); - } - - /// - /// Execute a SQL command against the database. - /// - /// The SQL command to execute - public void ExecuteCommand(string sql) - { - NpgsqlCommand command = GetCommand(sql); - - command.ExecuteNonQuery(); - } - - /// - /// Creates a new SQL command for the database connection. - /// - /// The SQL command string - /// A new NpgsqlCommand object - public NpgsqlCommand GetCommand(string sql) - { - return new(sql, Connection); - } - - /* - public void RebuildDatabase() - { - // Закрываем текущее соединение - Connection.Close(); - - // Получение имён - var databaseNamePattern = DatabaseNamePattern(); - var userConnection = databaseNamePattern.Replace(EnvSettings.DatabaseConnection, ";"); - var databaseName = databaseNamePattern.Match(EnvSettings.DatabaseConnection).Groups[1].Value; - var username = UserNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - - // Временное подключение к базе данных - NpgsqlConnection tmpConnection = new(userConnection); - tmpConnection.Open(); - - // Удаляем базу данных - NpgsqlCommand dropCommand = new($"DROP DATABASE \"{databaseName}\";", tmpConnection); - dropCommand.ExecuteNonQuery(); - - // Создаём новую базу данных - NpgsqlCommand createCommand = new($@" - CREATE DATABASE {databaseName} - WITH OWNER = {username} - ENCODING = 'UTF8' - CONNECTION LIMIT = -1; - ", tmpConnection); - createCommand.ExecuteNonQuery(); - - string[] permissionCommands = [ - $"GRANT TEMPORARY, CONNECT ON DATABASE {databaseName} TO PUBLIC;", - $"GRANT ALL ON DATABASE {databaseName} TO {username} WITH GRANT OPTION;", - $"GRANT ALL ON DATABASE {databaseName} TO postgres;" - ]; - - foreach (var command in permissionCommands) - { - NpgsqlCommand permissionCommand = new(command, tmpConnection); - permissionCommand.ExecuteNonQuery(); - } - - // Закрываем временное соединение - tmpConnection.Close(); - - // Заново создаём соединение - Connection = new NpgsqlConnection(EnvSettings.DatabaseConnection); - } - - */ - - [GeneratedRegex(@"(?:^|;)Database=(.*?)(?:$|;)")] - private static partial Regex DatabaseNamePattern(); - - [GeneratedRegex(@"(?:^|;)User=(.*?)(?:$|;)")] - private static partial Regex UsernamePattern(); -} diff --git a/Pf2eModel/EfCleanHelper.cs b/Pf2eModel/EfCleanHelper.cs new file mode 100644 index 0000000..1cb9cee --- /dev/null +++ b/Pf2eModel/EfCleanHelper.cs @@ -0,0 +1,97 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Pf2eModel; + +/// +/// Класс, обеспецивающий общий способ для отчистки данных таблиц. +/// +/// Источник: https://www.codeproject.com/Articles/5339402/Delete-All-Rows-in-Entity-Framework-Core-6 +/// +public static class EfCleanHelper +{ + /// + /// Определение полного названия таблицы по служебному классу. + /// + /// Служебный класс, определяющий таблицу базы данных. + /// Схема таблицы по умолчанию ("public" для PostgresSQL) + /// + private static string GetName(IEntityType entityType, string defaultSchemaName = "public") + { + object? schema = entityType.FindAnnotation("Relational:Schema")?.Value; + + string tableName = + entityType.GetAnnotation("Relational:TableName")?.Value?.ToString() + ?? throw new($"Не могу определить название таблицы для типа {entityType}"); + + string schemaName = schema?.ToString() ?? defaultSchemaName; + + string name = $"\"{schemaName}\".\"{tableName}\""; + return name; + } + + /// + /// Определение полного названия таблицы по типу сущности. + /// + /// Тип искомой сущности. + /// Контекст, в котором производится поиск таблицы. + /// Строковое представление названия таблицы. + public static string TableName(DbContext dbContext) + where T : class + { + IEntityType entityType = + dbContext.Model.FindEntityType(typeof(T)) + ?? throw new($"Не могу определить название таблицы для класса {typeof(T)}"); + + return GetName(entityType); + } + + /// + /// Определение полного названия таблицы по типу сущности. + /// + /// Тип искомой сущности. + /// CLR-представление таблицы, название которой требуется определить. + /// Строковое представление названия таблицы. + public static string TableName(DbSet dbSet) + where T : class + { + IEntityType entityType = dbSet.EntityType; + return GetName(entityType); + } + + /// + /// Отчистка содержимого таблицы. + /// + /// Тип сущности, таблицу которой необходимо отчистить. + /// CLR-представление таблицы, которую требуется отчистить. + /// Строковое представление выполненной SQL-команды. + public static string Truncate(this DbSet dbSet) + where T : class + { + string cmd = $"TRUNCATE TABLE {TableName(dbSet)} CASCADE;"; + DbContext context = dbSet.GetService().Context; + context.Database.ExecuteSqlRaw(cmd); + return cmd; + } + + /// + /// Получение SQL-команды отчистки содержимого всех таблиц базы данных. + /// + /// Контекст базы данных, в которой необходимо отчистить все строки. + /// Строковое представление выполненных SQL-команды. + public static string TruncateAll(this DbContext context) + { + IEnumerable tables = context.Model.GetEntityTypes(); + List cmdList = []; + + foreach (IEntityType entityType in tables) + { + string cmd = $"TRUNCATE TABLE {GetName(entityType)} CASCADE;"; + context.Database.ExecuteSqlRaw(cmd); + cmdList.Add(cmd); + } + + return string.Join('\n', cmdList); + } +} diff --git a/Pf2eModel/Migrations/20250418062358_Function.cs b/Pf2eModel/Migrations/20250418062358_Function.cs index d735d1d..f26cf8e 100644 --- a/Pf2eModel/Migrations/20250418062358_Function.cs +++ b/Pf2eModel/Migrations/20250418062358_Function.cs @@ -12,25 +12,6 @@ public partial class Function : Migration { migrationBuilder.Sql( """ -CREATE OR REPLACE FUNCTION f_truncate_tables() - RETURNS void - LANGUAGE plpgsql AS -$func$ -BEGIN - EXECUTE - (SELECT 'TRUNCATE TABLE ' - || string_agg(format('%I.%I', schemaname, tablename), ', ') - || ' CASCADE;' - FROM pg_tables - WHERE schemaname = 'public' - AND tablename != '__EFMigrationsHistory' - ); -END -$func$; -""" - ); - migrationBuilder.Sql( - """ CREATE OR REPLACE FUNCTION f_drop_tables() RETURNS void LANGUAGE plpgsql AS diff --git a/Pf2eModel/Pf2eDbContextModel.cs b/Pf2eModel/Pf2eDbContextModel.cs index 5966a03..99e5fb6 100644 --- a/Pf2eModel/Pf2eDbContextModel.cs +++ b/Pf2eModel/Pf2eDbContextModel.cs @@ -52,7 +52,12 @@ public partial class Pf2eDbContext private void BindEntity(EntityTypeBuilder entity) where T : class, IEntity { - entity.HasOne(e => e.Entity).WithOne().HasForeignKey(e => e.Id).IsRequired(); + entity + .HasOne(e => e.Entity) + .WithOne() + .HasForeignKey(e => e.Id) + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); } ///