diff --git a/DataConnection/DataConnection.csproj b/DataConnection/DataConnection.csproj new file mode 100644 index 0000000..7e48384 --- /dev/null +++ b/DataConnection/DataConnection.csproj @@ -0,0 +1,20 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + + diff --git a/DataConnection/EF/DatabaseDiscovery/SqlServerDatabaseDiscovery.cs b/DataConnection/EF/DatabaseDiscovery/SqlServerDatabaseDiscovery.cs new file mode 100644 index 0000000..6f8b15b --- /dev/null +++ b/DataConnection/EF/DatabaseDiscovery/SqlServerDatabaseDiscovery.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using System.Threading.Tasks; +using DataConnection.Interfaces; +using Microsoft.Data.SqlClient; + +namespace DataConnection.EF.DatabaseDiscovery; + +/// +/// Implementazione di IDatabaseDiscovery per SQL Server +/// +public class SqlServerDatabaseDiscovery : IDatabaseDiscovery +{ + private static readonly List _systemDatabases = new List + { + "master", "tempdb", "model", "msdb", "distribution" + }; + + public async Task> GetAvailableDatabasesAsync(string serverConnectionString, bool excludeSystemDatabases = true) + { + List databases = new List(); + + using (SqlConnection connection = new SqlConnection(serverConnectionString)) + { + await connection.OpenAsync(); + + // Query per ottenere tutti i database sul server + string query = @" + SELECT name + FROM sys.databases + WHERE state_desc = 'ONLINE'"; + + if (excludeSystemDatabases) + { + query += " AND name NOT IN ('master', 'tempdb', 'model', 'msdb', 'distribution')"; + } + + query += " ORDER BY name"; + + using (SqlCommand command = new SqlCommand(query, connection)) + { + using (SqlDataReader reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + databases.Add(reader.GetString(0)); + } + } + } + } + + return databases; + } + + public async Task> GetDatabasesInfoAsync(string serverConnectionString, bool excludeSystemDatabases = true) + { + Dictionary databasesInfo = new Dictionary(); + + using (SqlConnection connection = new SqlConnection(serverConnectionString)) + { + await connection.OpenAsync(); + + // Query per ottenere informazioni dettagliate sui database + string query = @" + SELECT + d.name, + CAST((SELECT SUM(CAST(size AS BIGINT)) * 8.0 / 1024 FROM sys.master_files WHERE database_id = d.database_id) AS DECIMAL(18,2)) AS size_mb, + d.create_date, + d.state_desc, + SUSER_SNAME(d.owner_sid) AS owner, + CASE WHEN d.name IN ('master', 'tempdb', 'model', 'msdb', 'distribution') THEN 1 ELSE 0 END AS is_system_db + FROM sys.databases d + WHERE state_desc = 'ONLINE'"; + + if (excludeSystemDatabases) + { + query += " AND d.name NOT IN ('master', 'tempdb', 'model', 'msdb', 'distribution')"; + } + + query += " ORDER BY d.name"; + + using (SqlCommand command = new SqlCommand(query, connection)) + { + using (SqlDataReader reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + string dbName = reader.GetString(0); + + databasesInfo[dbName] = new DatabaseInfo + { + Name = dbName, + SizeMB = reader.IsDBNull(1) ? 0 : Convert.ToDouble(reader.GetDecimal(1)), + CreationDate = reader.GetDateTime(2), + Status = reader.GetString(3), + Owner = reader.IsDBNull(4) ? null : reader.GetString(4), + IsSystemDatabase = reader.GetInt32(5) == 1 + }; + } + } + } + } + + return databasesInfo; + } +} diff --git a/DataConnection/EF/DbManagerOptions.cs b/DataConnection/EF/DbManagerOptions.cs new file mode 100644 index 0000000..b3f61c8 --- /dev/null +++ b/DataConnection/EF/DbManagerOptions.cs @@ -0,0 +1,116 @@ +using System; +using Microsoft.EntityFrameworkCore; +using DataConnection.Interfaces; +using DataConnection.EF.DatabaseDiscovery; +using DataConnection.Enums; + +namespace DataConnection.EF; + +/// +/// Opzioni per la configurazione del manager di database esistenti +/// +public class DbManagerOptions +{ + /// + /// Configuratore del DbContext + /// + public Action DbContextConfigurator { get; set; } + + /// + /// Configuratore del modello del database + /// + public Action ModelConfigurator { get; set; } + + /// + /// Flag che indica se la scoperta automatica delle entità è abilitata + /// + public bool EnableAutoDiscovery { get; set; } + + /// + /// Assembly da cui caricare automaticamente le entità (se EnableAutoDiscovery = true) + /// + public System.Reflection.Assembly EntityAssembly { get; set; } + + /// + /// Namespace in cui cercare le entità (se EnableAutoDiscovery = true) + /// + public string EntityNamespace { get; set; } + + /// + /// Timeout per le operazioni del database (in secondi) + /// + public int CommandTimeout { get; set; } = 30; + + /// + /// Strategia di mappatura dei nomi (CamelCase, PascalCase, SnakeCase) + /// + public NamingStrategy NamingStrategy { get; set; } = NamingStrategy.Default; + + /// + /// Servizio per la scoperta dei database disponibili sul server + /// + public IDatabaseDiscovery DatabaseDiscoveryService { get; set; } + + /// + /// Stringa di connessione a livello di server (senza specificare il database) + /// + public string ServerConnectionString { get; set; } + + /// + /// Nome del database a cui connettersi + /// + public string DatabaseName { get; set; } + + /// + /// Configura automaticamente il servizio di scoperta database in base al tipo di database + /// + /// Tipo di database + public void ConfigureDatabaseDiscovery(DatabaseType databaseType) + { + switch (databaseType) + { + case DatabaseType.SqlServer: + DatabaseDiscoveryService = new SqlServerDatabaseDiscovery(); + break; + // case DatabaseType.MySql: + // DatabaseDiscoveryService = new MySqlDatabaseDiscovery(); + // break; + // Altri tipi di database possono essere aggiunti qui + default: + throw new NotSupportedException($"Tipo di database non supportato: {databaseType}"); + } + } + + /// + /// Costruisce una stringa di connessione completa includendo il database selezionato + /// + public string BuildFullConnectionString() + { + if (string.IsNullOrEmpty(ServerConnectionString)) + { + throw new InvalidOperationException("La stringa di connessione al server non è stata specificata"); + } + + if (string.IsNullOrEmpty(DatabaseName)) + { + return ServerConnectionString; + } + + // Per SQL Server + if (ServerConnectionString.Contains("Initial Catalog=") || ServerConnectionString.Contains("Database=")) + { + // Sostituisci il database esistente + var modifiedString = System.Text.RegularExpressions.Regex.Replace( + ServerConnectionString, + @"(Initial Catalog|Database)=([^;]*)", + $"$1={DatabaseName}"); + + return modifiedString; + } + else + { + // Aggiungi il database + return ServerConnectionString + $";Database={DatabaseName}"; + } + } +} diff --git a/DataConnection/EF/EFCoreDatabaseManager.cs b/DataConnection/EF/EFCoreDatabaseManager.cs new file mode 100644 index 0000000..54e06f6 --- /dev/null +++ b/DataConnection/EF/EFCoreDatabaseManager.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using DataConnection.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace DataConnection.EF; + +/// +/// Implementazione di IExistingDatabaseManager basata su Entity Framework Core +/// +public class EFCoreDatabaseManager : IDatabaseManager +{ + private readonly DbManagerOptions _options; + private ExistingDatabaseContext _context; + + public EFCoreDatabaseManager(DbManagerOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + InitializeContext(); + } + + private void InitializeContext() + { + var optionsBuilder = new DbContextOptionsBuilder(); + _options.DbContextConfigurator(optionsBuilder); + + _context = new ExistingDatabaseContext( + optionsBuilder.Options, + _options.ModelConfigurator, + _options.EnableAutoDiscovery, + _options.EntityAssembly, + _options.EntityNamespace, + _options.NamingStrategy); + } + + public async Task TestConnectionAsync() + { + try + { + return await _context.Database.CanConnectAsync(); + } + catch + { + return false; + } + } + + public async Task> GetAsync( + Expression> filter = null, + Func, IOrderedQueryable> orderBy = null, + string includeProperties = "", + int? skip = null, + int? take = null) where T : class + { + IQueryable query = _context.Set(); + + if (filter != null) + { + query = query.Where(filter); + } + + foreach (var includeProperty in includeProperties.Split + (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + query = query.Include(includeProperty); + } + + if (orderBy != null) + { + query = orderBy(query); + } + + if (skip.HasValue) + { + query = query.Skip(skip.Value); + } + + if (take.HasValue) + { + query = query.Take(take.Value); + } + + return await query.ToListAsync(); + } + + public async Task GetByIdAsync(object id) where T : class + { + return await _context.Set().FindAsync(id); + } + + public async Task> ExecuteQueryAsync(string sql, params object[] parameters) where T : class + { + return await _context.Set().FromSqlRaw(sql, parameters).ToListAsync(); + } + + public async Task ExecuteCommandAsync(string sql, params object[] parameters) + { + return await _context.Database.ExecuteSqlRawAsync(sql, parameters); + } + + public async Task>> GetDatabaseSchemaAsync() + { + // Assicurarsi che il contesto sia connesso + await _context.Database.OpenConnectionAsync(); + + var result = new Dictionary>(); + + // Ottiene tutte le entità dal modello + var model = _context.Model; + var entityTypes = model.GetEntityTypes().ToList(); + + foreach (var entityType in entityTypes) + { + var tableName = entityType.GetTableName(); + var schemaName = entityType.GetSchema(); + var fullTableName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}"; + + var columns = new List(); + + foreach (var property in entityType.GetProperties()) + { + var columnInfo = new DbColumnInfo + { + Name = property.GetColumnName(), + DataType = property.GetColumnType(), + IsNullable = property.IsNullable, + IsPrimaryKey = property.IsPrimaryKey() + }; + + // Verifica se è una foreign key + var foreignKeys = entityType.GetForeignKeys().Where(fk => fk.Properties.Contains(property)); + if (foreignKeys.Any()) + { + var fk = foreignKeys.First(); + columnInfo.IsForeignKey = true; + columnInfo.ReferencedTable = fk.PrincipalEntityType.GetTableName(); + columnInfo.ReferencedColumn = fk.PrincipalKey.Properties.First().GetColumnName(); + } + + columns.Add(columnInfo); + } + + result.Add(fullTableName, columns); + } + + return result; + } + + public void Dispose() + { + _context?.Dispose(); + } +} diff --git a/DataConnection/EF/ExistingDatabaseContext.cs b/DataConnection/EF/ExistingDatabaseContext.cs new file mode 100644 index 0000000..e2401a3 --- /dev/null +++ b/DataConnection/EF/ExistingDatabaseContext.cs @@ -0,0 +1,124 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Reflection; +using DataConnection.Enums; + +namespace DataConnection.EF; + +/// +/// DbContext per gestione database esistenti +/// +public class ExistingDatabaseContext : DbContext +{ + private readonly Action _modelConfigurator; + private readonly bool _enableAutoDiscovery; + private readonly Assembly _entityAssembly; + private readonly string _entityNamespace; + private readonly NamingStrategy _namingStrategy; + + public ExistingDatabaseContext( + DbContextOptions options, + Action modelConfigurator = null, + bool enableAutoDiscovery = false, + Assembly entityAssembly = null, + string entityNamespace = null, + NamingStrategy namingStrategy = NamingStrategy.Default) + : base(options) + { + _modelConfigurator = modelConfigurator; + _enableAutoDiscovery = enableAutoDiscovery; + _entityAssembly = entityAssembly; + _entityNamespace = entityNamespace; + _namingStrategy = namingStrategy; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Applica la strategia di mappatura dei nomi + ApplyNamingStrategy(modelBuilder); + + // Applica la configurazione personalizzata se fornita + _modelConfigurator?.Invoke(modelBuilder); + + // Scoperta automatica delle entità + if (_enableAutoDiscovery && _entityAssembly != null) + { + DiscoverEntities(modelBuilder); + } + } + + private void ApplyNamingStrategy(ModelBuilder modelBuilder) + { + switch (_namingStrategy) + { + case NamingStrategy.CamelCase: + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + // Converti il nome della tabella in camelCase + string tableName = entity.GetTableName(); + if (!string.IsNullOrEmpty(tableName) && char.IsUpper(tableName[0])) + entity.SetTableName(char.ToLower(tableName[0]) + tableName.Substring(1)); + + // Converti i nomi delle proprietà in camelCase + foreach (var property in entity.GetProperties()) + { + string propertyName = property.GetColumnName(); + if (!string.IsNullOrEmpty(propertyName) && char.IsUpper(propertyName[0])) + property.SetColumnName(char.ToLower(propertyName[0]) + propertyName.Substring(1)); + } + } + break; + + case NamingStrategy.SnakeCase: + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + // Converti il nome della tabella in snake_case + string tableName = entity.GetTableName(); + if (!string.IsNullOrEmpty(tableName)) + entity.SetTableName(ConvertToSnakeCase(tableName)); + + // Converti i nomi delle proprietà in snake_case + foreach (var property in entity.GetProperties()) + { + string propertyName = property.GetColumnName(); + if (!string.IsNullOrEmpty(propertyName)) + property.SetColumnName(ConvertToSnakeCase(propertyName)); + } + } + break; + } + } + + private string ConvertToSnakeCase(string input) + { + if (string.IsNullOrEmpty(input)) + return input; + + return string.Concat(input.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower(); + } + + private void DiscoverEntities(ModelBuilder modelBuilder) + { + // Trova tutte le classi nel namespace specificato che potrebbero essere entità + var entityTypes = _entityAssembly.GetTypes() + .Where(t => !string.IsNullOrEmpty(_entityNamespace) + ? t.Namespace == _entityNamespace + : true) + .Where(t => t.IsClass && !t.IsAbstract && t.GetConstructor(Type.EmptyTypes) != null) + .ToList(); + + // Metodo generico per aggiungere entità al modello + var entityMethod = typeof(ModelBuilder).GetMethod("Entity", new Type[0]); + + foreach (var entityType in entityTypes) + { + // Usa reflection per chiamare il metodo generico Entity() + var genericMethod = entityMethod.MakeGenericMethod(entityType); + genericMethod.Invoke(modelBuilder, null); + } + } +} diff --git a/DataConnection/Enums/DatabaseType.cs b/DataConnection/Enums/DatabaseType.cs new file mode 100644 index 0000000..a958036 --- /dev/null +++ b/DataConnection/Enums/DatabaseType.cs @@ -0,0 +1,15 @@ +namespace DataConnection.Enums; + +/// +/// Tipo di database supportati +/// +public enum DatabaseType +{ + SqlServer, + MySql, + PostgreSql, + Oracle, + Sqlite, + DB2, + SapHana +} diff --git a/DataConnection/Enums/NamingStrategy.cs b/DataConnection/Enums/NamingStrategy.cs new file mode 100644 index 0000000..e938015 --- /dev/null +++ b/DataConnection/Enums/NamingStrategy.cs @@ -0,0 +1,12 @@ +namespace DataConnection.Enums; + +/// +/// Strategia di mappatura dei nomi delle tabelle e colonne +/// +public enum NamingStrategy +{ + Default, + CamelCase, + PascalCase, + SnakeCase +} \ No newline at end of file diff --git a/DataConnection/Interfaces/IDatabaseDiscovery.cs b/DataConnection/Interfaces/IDatabaseDiscovery.cs new file mode 100644 index 0000000..de725a4 --- /dev/null +++ b/DataConnection/Interfaces/IDatabaseDiscovery.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace DataConnection.Interfaces; + +/// +/// Interfaccia per la scoperta dei database disponibili sul server +/// +public interface IDatabaseDiscovery +{ + /// + /// Ottiene l'elenco di tutti i database disponibili sul server + /// + /// Stringa di connessione al server (senza specificare il database) + /// Se true, esclude i database di sistema + /// Lista di nomi dei database disponibili + Task> GetAvailableDatabasesAsync(string serverConnectionString, bool excludeSystemDatabases = true); + + /// + /// Ottiene informazioni dettagliate sui database disponibili + /// + /// Stringa di connessione al server + /// Se true, esclude i database di sistema + /// Dizionario con nome database e metadati + Task> GetDatabasesInfoAsync(string serverConnectionString, bool excludeSystemDatabases = true); +} + +/// +/// Informazioni sul database +/// +public class DatabaseInfo +{ + /// + /// Nome del database + /// + public string Name { get; set; } + + /// + /// Dimensione del database in MB + /// + public double SizeMB { get; set; } + + /// + /// Data di creazione + /// + public System.DateTime CreationDate { get; set; } + + /// + /// Stato del database (online, offline, ecc.) + /// + public string Status { get; set; } + + /// + /// Proprietario del database + /// + public string Owner { get; set; } + + /// + /// Indica se è un database di sistema + /// + public bool IsSystemDatabase { get; set; } +} diff --git a/DataConnection/Interfaces/IDatabaseManager.cs b/DataConnection/Interfaces/IDatabaseManager.cs new file mode 100644 index 0000000..f5e16f8 --- /dev/null +++ b/DataConnection/Interfaces/IDatabaseManager.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace DataConnection.Interfaces; + +/// +/// Interfaccia per la gestione di database preesistenti tramite EF Core +/// +public interface IDatabaseManager : IDisposable +{ + /// + /// Verifica la connessione al database + /// + Task TestConnectionAsync(); + + /// + /// Ottiene entità dal database in base ai criteri specificati + /// + /// Tipo di entità + /// Espressione di filtro + /// Espressione di ordinamento + /// Proprietà di navigazione da includere + /// Numero di elementi da saltare + /// Numero di elementi da prendere + Task> GetAsync( + Expression> filter = null, + Func, IOrderedQueryable> orderBy = null, + string includeProperties = "", + int? skip = null, + int? take = null) where T : class; + + /// + /// Ottiene un'entità singola in base alla chiave primaria + /// + Task GetByIdAsync(object id) where T : class; + + /// + /// Esegue una query SQL raw + /// + Task> ExecuteQueryAsync(string sql, params object[] parameters) where T : class; + + /// + /// Esegue un comando SQL che non restituisce risultati + /// + Task ExecuteCommandAsync(string sql, params object[] parameters); + + /// + /// Ottiene i metadati delle tabelle nel database + /// + Task>> GetDatabaseSchemaAsync(); +} + +/// +/// Informazioni su una colonna del database +/// +public class DbColumnInfo +{ + public string Name { get; set; } + public string DataType { get; set; } + public bool IsNullable { get; set; } + public bool IsPrimaryKey { get; set; } + public bool IsForeignKey { get; set; } + public string ReferencedTable { get; set; } + public string ReferencedColumn { get; set; } +} diff --git a/DataConnection/nuget.config b/DataConnection/nuget.config new file mode 100644 index 0000000..6ce9759 --- /dev/null +++ b/DataConnection/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Data_Coupler.sln b/Data_Coupler.sln index a110948..4c90304 100644 --- a/Data_Coupler.sln +++ b/Data_Coupler.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Data_Coupler", "Data_Coupler\Data_Coupler.csproj", "{899D735C-16BB-497B-AE81-A0C3845FD669}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataConnection", "DataConnection\DataConnection.csproj", "{27A4090F-2D6D-49F2-B0C7-6D33E90235BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {899D735C-16BB-497B-AE81-A0C3845FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU {899D735C-16BB-497B-AE81-A0C3845FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU {899D735C-16BB-497B-AE81-A0C3845FD669}.Release|Any CPU.Build.0 = Release|Any CPU + {27A4090F-2D6D-49F2-B0C7-6D33E90235BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27A4090F-2D6D-49F2-B0C7-6D33E90235BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27A4090F-2D6D-49F2-B0C7-6D33E90235BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27A4090F-2D6D-49F2-B0C7-6D33E90235BA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Data_Coupler/Data/DatabaseConnectionService.cs b/Data_Coupler/Data/DatabaseConnectionService.cs new file mode 100644 index 0000000..177b352 --- /dev/null +++ b/Data_Coupler/Data/DatabaseConnectionService.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DataConnection.Interfaces; +using DataConnection.EF; +using DataConnection.EF.DatabaseDiscovery; +using DataConnection.Enums; + +namespace Data_Coupler.Data +{ + public class DatabaseConnectionService + { + private readonly IDatabaseDiscovery _sqlServerDiscovery; + private readonly IDatabaseDiscovery _mySqlDiscovery; + + public DatabaseType SelectedDatabaseType { get; set; } = DatabaseType.SqlServer; + public string ConnectionString { get; set; } + public List AvailableDatabases { get; private set; } = new List(); + public Dictionary DatabasesInfo { get; private set; } = new Dictionary(); + public string SelectedDatabase { get; set; } + public bool IsConnected { get; private set; } + public string ErrorMessage { get; private set; } + + public DatabaseConnectionService() + { + _sqlServerDiscovery = new SqlServerDatabaseDiscovery(); + // Se in futuro verrà implementata la discovery MySQL, potremmo aggiungerla qui + // _mySqlDiscovery = new MySqlDatabaseDiscovery(); + } + + public IDatabaseDiscovery GetDatabaseDiscovery() + { + return SelectedDatabaseType switch + { + DatabaseType.SqlServer => _sqlServerDiscovery, + // DatabaseType.MySql => _mySqlDiscovery, + _ => throw new NotSupportedException($"Tipo di database non supportato: {SelectedDatabaseType}") + }; + } + + public async Task TestConnectionAsync() + { + try + { + var discovery = GetDatabaseDiscovery(); + AvailableDatabases = await discovery.GetAvailableDatabasesAsync(ConnectionString, true); + IsConnected = AvailableDatabases.Any(); + ErrorMessage = IsConnected ? null : "Nessun database trovato sul server."; + return IsConnected; + } + catch (Exception ex) + { + IsConnected = false; + ErrorMessage = $"Errore di connessione: {ex.Message}"; + if (ex.InnerException != null) + { + ErrorMessage += $" - {ex.InnerException.Message}"; + } + return false; + } + } + + public async Task> GetDatabasesInfoAsync() + { + try + { + var discovery = GetDatabaseDiscovery(); + DatabasesInfo = await discovery.GetDatabasesInfoAsync(ConnectionString, false); + return DatabasesInfo; + } + catch (Exception ex) + { + ErrorMessage = $"Errore nel recupero delle informazioni: {ex.Message}"; + return new Dictionary(); + } + } + + public string BuildConnectionStringWithDatabase() + { + if (string.IsNullOrEmpty(SelectedDatabase)) + return ConnectionString; + + // Per SQL Server + if (SelectedDatabaseType == DatabaseType.SqlServer) + { + if (ConnectionString.Contains("Initial Catalog=") || ConnectionString.Contains("Database=")) + { + // Sostituisci il database esistente + var modifiedString = System.Text.RegularExpressions.Regex.Replace( + ConnectionString, + @"(Initial Catalog|Database)=([^;]*)", + $"$1={SelectedDatabase}"); + + return modifiedString; + } + else + { + // Aggiungi il database + return ConnectionString + $";Database={SelectedDatabase}"; + } + } + + // Per altri tipi di database, implementare la logica appropriata + return ConnectionString; + } + } +} \ No newline at end of file diff --git a/Data_Coupler/Data_Coupler.csproj b/Data_Coupler/Data_Coupler.csproj index 6568b3d..b7559f2 100644 --- a/Data_Coupler/Data_Coupler.csproj +++ b/Data_Coupler/Data_Coupler.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/Data_Coupler/Pages/DatabaseConnection.razor b/Data_Coupler/Pages/DatabaseConnection.razor new file mode 100644 index 0000000..dbc3ca7 --- /dev/null +++ b/Data_Coupler/Pages/DatabaseConnection.razor @@ -0,0 +1,296 @@ +@page "/database-connection" +@using DataConnection.Enums +@using DataConnection.Interfaces +@inject Data.DatabaseConnectionService DatabaseService +@inject IJSRuntime JSRuntime + +Connessione al Database + +

Connessione al Database

+ +
+
+
+
+ Configurazione connessione +
+
+
+ + +
+ +
+ + +
+ +
+ +
+ + @if (DatabaseService.ErrorMessage != null) + { +
+ @DatabaseService.ErrorMessage +
+ } +
+
+
+ + @if (DatabaseService.IsConnected) + { +
+
+
+ Database Disponibili +
+
+
+ + +
+ + @if (!string.IsNullOrEmpty(DatabaseService.SelectedDatabase) && databaseInfo != null) + { +
+
Dettagli Database
+ + + + + + + + + + + + + + + + + + + + + + + +
Nome@databaseInfo?.Name
Dimensione@databaseInfo?.SizeMB MB
Data Creazione@databaseInfo?.CreationDate.ToString("dd/MM/yyyy HH:mm")
Stato@databaseInfo?.Status
Proprietario@databaseInfo?.Owner
+ +
+ +
+
+ } +
+
+
+ } +
+ +@if (showSchemaInfo) +{ +
+
+
+
+ Schema del Database: @DatabaseService.SelectedDatabase +
+
+ @if (schemaInfo != null && schemaInfo.Count > 0) + { +
+ + + + + + + + + @foreach (var table in schemaInfo) + { + + + + + } + +
TabellaColonne
@table.Key +
    + @foreach (var column in table.Value) + { +
  • + @column.Name + (@column.DataType) + @if (column.IsPrimaryKey) + { + PK + } + @if (column.IsForeignKey) + { + FK → @column.ReferencedTable + } +
  • + } +
+
+
+ } + else + { +
+ Nessuna informazione sullo schema disponibile. +
+ } +
+
+
+
+} + +@code { + private bool isLoading = false; + private bool isConnecting = false; + private bool showSchemaInfo = false; + private DatabaseInfo databaseInfo; + private IDictionary> schemaInfo; + + protected override async Task OnInitializedAsync() + { + // Puoi impostare valori predefiniti, magari da configurazione + DatabaseService.ConnectionString = "Server=localhost;User Id=sa;Password=password;TrustServerCertificate=True"; + } + + private async Task TestConnection() + { + try + { + isLoading = true; + showSchemaInfo = false; + databaseInfo = null; + + await DatabaseService.TestConnectionAsync(); + + if (DatabaseService.IsConnected) + { + await DatabaseService.GetDatabasesInfoAsync(); + } + } + catch (Exception ex) + { + await JSRuntime.InvokeVoidAsync("console.error", "Errore connessione:", ex.Message); + } + finally + { + isLoading = false; + } + } + + private async Task ConnectToDatabase() + { + if (string.IsNullOrEmpty(DatabaseService.SelectedDatabase)) + return; + + try + { + isConnecting = true; + showSchemaInfo = false; + + // Ottieni informazioni sul database selezionato + if (DatabaseService.DatabasesInfo.TryGetValue(DatabaseService.SelectedDatabase, out var dbInfo)) + { + databaseInfo = dbInfo; + } + + // Qui dovresti implementare la connessione al database selezionato + // e recuperare le informazioni sullo schema + // Per questo esempio, simuliamo che sia avvenuto con successo + await Task.Delay(1000); // Simulazione operazione asincrona + + showSchemaInfo = true; + // In un'implementazione reale, qui dovresti eseguire: + // schemaInfo = await databaseManager.GetDatabaseSchemaAsync(); + + // Per ora creiamo dati di esempio + schemaInfo = CreateSampleSchemaInfo(); + } + catch (Exception ex) + { + await JSRuntime.InvokeVoidAsync("console.error", "Errore connessione database:", ex.Message); + //DatabaseService.ErrorMessage = $"Errore nella connessione al database: {ex.Message}"; + showSchemaInfo = false; + } + finally + { + isConnecting = false; + } + } + + // Metodo temporaneo per generare dati di esempio - da sostituire con dati reali + private IDictionary> CreateSampleSchemaInfo() + { + var result = new Dictionary>(); + + // Tabella Customers + var customerColumns = new List + { + new DbColumnInfo { Name = "Id", DataType = "int", IsPrimaryKey = true }, + new DbColumnInfo { Name = "Name", DataType = "nvarchar(100)" }, + new DbColumnInfo { Name = "Email", DataType = "nvarchar(255)" }, + new DbColumnInfo { Name = "Created", DataType = "datetime" } + }; + + // Tabella Orders + var orderColumns = new List + { + new DbColumnInfo { Name = "Id", DataType = "int", IsPrimaryKey = true }, + new DbColumnInfo { Name = "CustomerId", DataType = "int", IsForeignKey = true, ReferencedTable = "Customers", ReferencedColumn = "Id" }, + new DbColumnInfo { Name = "OrderDate", DataType = "datetime" }, + new DbColumnInfo { Name = "TotalAmount", DataType = "decimal(18,2)" } + }; + + result.Add("Customers", customerColumns); + result.Add("Orders", orderColumns); + + return result; + } +} \ No newline at end of file diff --git a/Data_Coupler/Program.cs b/Data_Coupler/Program.cs index c9b4013..f22e0a6 100644 --- a/Data_Coupler/Program.cs +++ b/Data_Coupler/Program.cs @@ -1,6 +1,13 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Data_Coupler.Data; +using DataConnection; +using DataConnection.EF; +using DataConnection.Interfaces; +using DataConnection.EF.DatabaseDiscovery; +using DataConnection.Enums; +using System; +using System.Threading.Tasks; var builder = WebApplication.CreateBuilder(args); @@ -9,8 +16,16 @@ builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddSingleton(); +// Registra il servizio di connessione al database +builder.Services.AddScoped(); + +// Registriamo i servizi di database discovery +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + var app = builder.Build(); + // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { @@ -29,3 +44,4 @@ app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); app.Run(); + diff --git a/Data_Coupler/Shared/NavMenu.razor b/Data_Coupler/Shared/NavMenu.razor index 745c3c6..fa0baee 100644 --- a/Data_Coupler/Shared/NavMenu.razor +++ b/Data_Coupler/Shared/NavMenu.razor @@ -24,6 +24,11 @@ Fetch data + diff --git a/Data_Coupler/nuget.config b/Data_Coupler/nuget.config new file mode 100644 index 0000000..6ce9759 --- /dev/null +++ b/Data_Coupler/nuget.config @@ -0,0 +1,8 @@ + + + + + + + +