using System; using System.Data; using System.Text; using CredentialManager.Models; using CredentialManager.Services; using DataConnection.Interfaces; using DataConnection.REST.Interfaces; using DataConnection.REST.Models; using DataConnection.CredentialManagement.Interfaces; using ExcelDataReader; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using Microsoft.JSInterop; using Microsoft.Extensions.Logging; using Data_Coupler.Services; namespace Data_Coupler.Pages; public partial class DataCoupler : ComponentBase { // Proprietà iniettate [Inject] public IDataConnectionCredentialService CredentialService { get; set; } = default!; [Inject] public IDataConnectionFactory ConnectionFactory { get; set; } = default!; [Inject] public IJSRuntime JSRuntime { get; set; } = default!; [Inject] public ILogger Logger { get; set; } = default!; [Inject] public IDataCouplerProfileService ProfileService { get; set; } = default!; // Classe per i risultati del trasferimento public class TransferResult { public int RecordNumber { get; set; } public string Status { get; set; } = ""; // "success", "error", "updated", "duplicate" public string Message { get; set; } = ""; public string? EntityId { get; set; } public Dictionary RecordData { get; set; } = new(); } // Stato delle credenziali private List databaseCredentials = new(); private List restApiCredentials = new(); // Selezione tipo fonte private string selectedSourceType = ""; // Credenziali selezionate private string selectedDatabaseCredential = ""; private string selectedRestCredential = ""; // Stato connessioni private bool isConnectingDatabase = false; private bool isConnectingRest = false; private bool isDatabaseConnected = false; private bool isRestConnected = false; // Messaggi di errore private string databaseErrorMessage = ""; private string restErrorMessage = ""; // Database discovery private List availableTableNames = new(); // Solo nomi delle tabelle private Dictionary> databaseTables = new(); // Schema dettagliato per tabelle caricate private string selectedTable = ""; private string databaseSearchTerm = ""; // Database selection - per gestire la selezione del database quando non specificato nella connection string private List availableDatabases = new(); private string selectedDatabase = ""; private bool showDatabaseSelectionModal = false; private bool isLoadingDatabases = false; // Database selection (schemas only) private List availableSchemas = new(); private string selectedSchema = ""; private bool showSchemaSelectionModal = false; private bool isLoadingSchemas = false; // Custom query functionality private bool useCustomQuery = false; private string customQuery = ""; private bool isValidatingQuery = false; private bool isQueryValid = false; private string queryValidationMessage = ""; private List> queryPreviewData = new(); private List queryColumns = new(); private bool showQueryPreview = false; private bool isLoadingPreview = false; // File handling private string selectedFileName = ""; private bool isProcessingFile = false; private string fileErrorMessage = ""; private Dictionary> fileSheets = new(); // SheetName -> Columns private Dictionary>> fileData = new(); // SheetName -> Data rows private string selectedSheet = ""; // File preview pagination private int currentPage = 1; private int pageSize = 20; private int GetTotalPages(string sheetName) => fileData.ContainsKey(sheetName) ? (int)Math.Ceiling((double)fileData[sheetName].Count / pageSize) : 0; // REST discovery private List restEntities = new(); private RestEntitySummary? selectedRestEntity = null; private RestEntityInfo? restEntityDetails = null; private string restSearchTerm = ""; // Mapping campi private Dictionary fieldMappings = new(); // DbColumn -> RestProperty private HashSet keyFields = new(); // REST properties marked as keys private string selectedDbColumn = ""; private string selectedRestProperty = ""; // Gestione chiavi sorgente e associazioni private string sourceKeyField = ""; // Campo che identifica univocamente il record sorgente private string suggestedPrimaryKey = ""; // Campo PK suggerito per database private bool requiresManualKeySelection = false; // Flag per indicare se è richiesta selezione manuale private bool useRecordAssociations = true; // Se utilizzare il sistema di associazioni // Trasferimento dati private bool isTransferringData = false; private string transferMessage = ""; private string transferMessageType = ""; private List transferResults = new(); private bool showDetailedResults = false; // Servizi private IDatabaseManager? currentDatabaseManager = null; private IRestMetadataDiscovery? currentRestDiscovery = null; private IRestServiceClient? currentRestClient = null; // Gestione Profili private List availableProfiles = new(); private bool isLoadingProfiles = false; private bool showProfileManagement = false; protected override async Task OnInitializedAsync() { await LoadCredentials(); } private async Task LoadCredentials() { try { databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync(); restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync(); // Carica anche i profili disponibili await LoadProfiles(); } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento delle credenziali"); await JSRuntime.InvokeVoidAsync("alert", $"Errore nel caricamento delle credenziali: {ex.Message}"); } } private async Task LoadProfiles() { try { isLoadingProfiles = true; var profiles = await ProfileService.GetAllProfilesAsync(); availableProfiles = profiles.ToList(); } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento dei profili"); } finally { isLoadingProfiles = false; StateHasChanged(); } } private async Task OnProfileLoaded(DataCouplerProfile profile) { try { // Aggiorna la data di ultimo utilizzo await ProfileService.UpdateLastUsedAsync(profile.Id); // Applica la configurazione del profilo await ApplyProfileConfiguration(profile); // Ricarica i profili per aggiornare la data di ultimo utilizzo await LoadProfiles(); } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento del profilo"); await JSRuntime.InvokeVoidAsync("alert", $"Errore nel caricamento del profilo: {ex.Message}"); } } private async Task ApplyProfileConfiguration(DataCouplerProfile profile) { // Reset dello stato corrente ResetAllState(); // Applica configurazione sorgente selectedSourceType = profile.SourceType; if (profile.SourceCredentialId.HasValue) { // Per ora, uso il nome della credenziale come identificatore // TODO: Implementare risoluzione corretta tramite ID quando disponibile // In alternativa, potremmo aggiungere il nome della credenziale al profilo // Se c'è uno schema salvato nel profilo, utilizziamolo per la connessione if (!string.IsNullOrEmpty(profile.SourceSchema)) { Logger.LogInformation("Applicando schema dal profilo: {Schema}", profile.SourceSchema); // Prima verifichiamo che ci sia una credenziale selezionata if (!string.IsNullOrEmpty(selectedDatabaseCredential)) { await ConnectToDatabaseWithSchema(profile.SourceSchema); } else { Logger.LogWarning("Nessuna credenziale database selezionata per applicare lo schema"); } } else if (!string.IsNullOrEmpty(selectedDatabaseCredential)) { // Connetti al database senza schema specifico await ConnectToDatabase(); } // Seleziona la tabella se specificata if (!string.IsNullOrEmpty(profile.SourceTable)) { selectedTable = profile.SourceTable; } } // Applica configurazione destinazione if (profile.DestinationCredentialId.HasValue) { // Similmente, per ora gestiamo senza risoluzione diretta dell'ID // TODO: Implementare risoluzione corretta tramite ID quando disponibile if (!string.IsNullOrEmpty(selectedRestCredential)) { // Connetti al servizio REST await ConnectToRestApi(); // Trova e seleziona l'entità REST if (!string.IsNullOrEmpty(profile.DestinationEndpoint)) { var entity = restEntities.FirstOrDefault(e => e.Name == profile.DestinationEndpoint); if (entity != null) { await SelectRestEntity(entity); } else { Logger.LogWarning("Entità REST con endpoint {Endpoint} non trovata", profile.DestinationEndpoint); } } } } // Applica mapping dei campi se disponibile if (!string.IsNullOrEmpty(profile.FieldMappingJson)) { try { var service = new DataCouplerProfileService(null!); // Temporaneo per deserializzazione var mappings = service.DeserializeFieldMappings(profile.FieldMappingJson); // Applica i mapping fieldMappings.Clear(); keyFields.Clear(); foreach (var mapping in mappings) { fieldMappings[mapping.SourceField] = mapping.DestinationField; if (mapping.IsKey) { keyFields.Add(mapping.DestinationField); } } Logger.LogInformation("Applicati {MappingCount} mapping dei campi dal profilo", mappings.Count); } catch (Exception ex) { Logger.LogWarning(ex, "Errore nel caricamento dei mapping dei campi dal profilo"); } } StateHasChanged(); } private async Task OnProfileSaved(DataCouplerProfileDto profileDto) { try { var profileService = new DataCouplerProfileService(null!); // Usa il service di conversione var profile = profileService.FromDto(profileDto, "System"); // TODO: Usa utente corrente await ProfileService.SaveProfileAsync(profile); await LoadProfiles(); // Ricarica la lista await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' salvato con successo!"); } catch (Exception ex) { Logger.LogError(ex, "Errore nel salvataggio del profilo"); await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvataggio del profilo: {ex.Message}"); } } private async Task OnProfileDeleted(int profileId) { try { var deleted = await ProfileService.DeleteProfileAsync(profileId); if (deleted) { await LoadProfiles(); // Ricarica la lista } } catch (Exception ex) { Logger.LogError(ex, "Errore nell'eliminazione del profilo"); throw; // Rilancia per gestire nell'UI } } private void OnManageProfiles() { showProfileManagement = true; } private void OnCloseProfileManagement() { showProfileManagement = false; } private bool CanSaveProfile() { return !string.IsNullOrEmpty(selectedSourceType) && (!string.IsNullOrEmpty(selectedDatabaseCredential) || !string.IsNullOrEmpty(selectedRestCredential)) && (!string.IsNullOrEmpty(selectedRestCredential) || !string.IsNullOrEmpty(selectedTable)); } private List GetCurrentFieldMappings() { var mappings = new List(); foreach (var mapping in fieldMappings) { mappings.Add(new FieldMappingDto { SourceField = mapping.Key, DestinationField = mapping.Value, IsKey = keyFields.Contains(mapping.Value), IsRequired = false, // TODO: Determina dai metadati DataType = "", // TODO: Determina dai metadati }); } return mappings; } private void ResetAllState() { ResetSourceState(); ResetDestinationState(); fieldMappings.Clear(); keyFields.Clear(); transferResults.Clear(); transferMessage = ""; } private void ResetDestinationState() { selectedRestCredential = ""; isConnectingRest = false; isRestConnected = false; restErrorMessage = ""; restEntities.Clear(); selectedRestEntity = null; restEntityDetails = null; restSearchTerm = ""; currentRestDiscovery = null; currentRestClient = null; } private void OnSourceTypeChanged(ChangeEventArgs e) { selectedSourceType = e.Value?.ToString() ?? ""; // Reset state when changing source type ResetSourceState(); } private void ResetSourceState() { // Reset database state ResetDatabaseState(); // Reset file state selectedFileName = ""; isProcessingFile = false; fileErrorMessage = ""; fileSheets.Clear(); fileData.Clear(); selectedSheet = ""; // Reset pagination currentPage = 1; // Reset mappings ClearAllMappings(); } private async Task OnFileSelected(InputFileChangeEventArgs e) { try { isProcessingFile = true; fileErrorMessage = ""; fileSheets.Clear(); fileData.Clear(); selectedSheet = ""; var file = e.File; selectedFileName = file.Name; // Validate file type var extension = Path.GetExtension(file.Name).ToLowerInvariant(); if (extension != ".xlsx" && extension != ".xls" && extension != ".csv") { fileErrorMessage = "Formato file non supportato. Utilizzare Excel (.xlsx, .xls) o CSV (.csv)"; return; } // Process file based on type if (extension == ".csv") { await ProcessCsvFile(file); } else { await ProcessExcelFile(file); } } catch (Exception ex) { Logger.LogError(ex, "Errore nell'elaborazione del file"); fileErrorMessage = $"Errore nell'elaborazione del file: {ex.Message}"; } finally { isProcessingFile = false; StateHasChanged(); } } private async Task ProcessCsvFile(IBrowserFile file) { using var stream = file.OpenReadStream(maxAllowedSize: 50 * 1024 * 1024); // Aumentato a 50MB using var reader = new StreamReader(stream); var firstLine = await reader.ReadLineAsync(); if (string.IsNullOrEmpty(firstLine)) { fileErrorMessage = "Il file CSV è vuoto"; return; } Logger.LogInformation("CSV first line: {FirstLine}", firstLine); // Detect separator automatically var separator = DetectCsvSeparator(firstLine); Logger.LogInformation("CSV separator detected: '{Separator}'", separator); // Parse headers (first row) - gestisce meglio i separatori var headers = ParseCsvLine(firstLine, separator); Logger.LogInformation("CSV headers parsed: {Headers}", string.Join(" | ", headers)); // For CSV, we create a single "sheet" with the filename var sheetName = Path.GetFileNameWithoutExtension(file.Name); fileSheets[sheetName] = headers; // Read data rows - rimuovo il limite di 1000 righe var dataRows = new List>(); string? line; int rowNumber = 2; // Starting from row 2 (after header) while ((line = await reader.ReadLineAsync()) != null) { if (string.IsNullOrWhiteSpace(line)) continue; var values = ParseCsvLine(line, separator); var row = new Dictionary(); for (int i = 0; i < headers.Count; i++) { var value = i < values.Count ? values[i] : ""; row[headers[i]] = string.IsNullOrEmpty(value) ? "" : value; } dataRows.Add(row); rowNumber++; // Log delle prime 3 righe per debug if (rowNumber <= 5) { Logger.LogInformation("CSV row {RowNumber}: {Values}", rowNumber - 1, string.Join(" | ", values)); } } fileData[sheetName] = dataRows; // Auto-seleziona il foglio per i CSV dato che ce n'è solo uno selectedSheet = sheetName; Logger.LogInformation("CSV file processed: {FileName}, Headers: {HeaderCount} ({Headers}), Rows: {RowCount}, Auto-selected sheet: {SheetName}", file.Name, headers.Count, string.Join(", ", headers), dataRows.Count, selectedSheet); } private List ParseCsvLine(string line, char separator = ',') { var result = new List(); var current = new StringBuilder(); bool inQuotes = false; for (int i = 0; i < line.Length; i++) { char c = line[i]; if (c == '"') { if (inQuotes && i + 1 < line.Length && line[i + 1] == '"') { // Double quote - escaped quote current.Append('"'); i++; // Skip next quote } else { // Toggle quote mode inQuotes = !inQuotes; } } else if (c == separator && !inQuotes) { // End of field result.Add(current.ToString().Trim()); current.Clear(); } else { current.Append(c); } } // Add the last field result.Add(current.ToString().Trim()); return result; }private async Task ProcessExcelFile(IBrowserFile file) { try { using var stream = file.OpenReadStream(maxAllowedSize: 50 * 1024 * 1024); // 50MB max // Crea il reader Excel basato sull'estensione IExcelDataReader reader; var extension = Path.GetExtension(file.Name).ToLowerInvariant(); if (extension == ".xlsx") { reader = ExcelReaderFactory.CreateOpenXmlReader(stream); } else if (extension == ".xls") { reader = ExcelReaderFactory.CreateBinaryReader(stream); } else { fileErrorMessage = "Formato Excel non supportato. Utilizzare .xlsx o .xls"; return; } using (reader) { // Configura per utilizzare la prima riga come header var configuration = new ExcelDataSetConfiguration() { ConfigureDataTable = (_) => new ExcelDataTableConfiguration() { UseHeaderRow = true // Prima riga come header } }; // Converti in DataSet var dataSet = reader.AsDataSet(configuration); Logger.LogInformation("Excel file processed: {FileName}, Sheets: {SheetCount}", file.Name, dataSet.Tables.Count); // Processa ogni foglio foreach (DataTable table in dataSet.Tables) { var sheetName = table.TableName; var headers = new List(); var dataRows = new List>(); // Estrai i nomi delle colonne (headers) foreach (DataColumn column in table.Columns) { headers.Add(column.ColumnName); } Logger.LogInformation("Processing Excel sheet: {SheetName}, Columns: {ColumnCount}, Rows: {RowCount}", sheetName, headers.Count, table.Rows.Count); // Processa le righe di dati for (int i = 0; i < table.Rows.Count; i++) { var row = table.Rows[i]; var rowData = new Dictionary(); for (int j = 0; j < headers.Count; j++) { var cellValue = row[j]?.ToString() ?? ""; rowData[headers[j]] = string.IsNullOrEmpty(cellValue) ? "" : cellValue; } dataRows.Add(rowData); // Log delle prime 3 righe per debug if (i < 3) { Logger.LogInformation("Excel row {RowNumber} in {Sheet}: {Values}", i + 1, sheetName, string.Join(" | ", rowData.Values)); } } // Salva i dati del foglio fileSheets[sheetName] = headers; fileData[sheetName] = dataRows; Logger.LogInformation("Excel sheet completed: {SheetName}, Headers: {Headers}, Rows: {RowCount}", sheetName, string.Join(", ", headers), dataRows.Count); } // Auto-seleziona il primo foglio se non c'è una selezione if (fileSheets.Any() && string.IsNullOrEmpty(selectedSheet)) { selectedSheet = fileSheets.First().Key; Logger.LogInformation("Auto-selected first sheet: {SheetName}", selectedSheet); } Logger.LogInformation("Excel file processing completed: {FileName}, Total sheets: {SheetCount}, Selected: {SelectedSheet}", file.Name, fileSheets.Count, selectedSheet); } } catch (Exception ex) { Logger.LogError(ex, "Errore nell'elaborazione del file Excel: {FileName}", file.Name); fileErrorMessage = $"Errore nell'elaborazione del file Excel: {ex.Message}"; } await Task.CompletedTask; } private void SelectSheet(string sheetName) { selectedSheet = sheetName; // Reset pagination when changing sheet currentPage = 1; // Clear mappings when changing sheet ClearAllMappings(); // For file sources, always require manual key selection sourceKeyField = ""; suggestedPrimaryKey = ""; requiresManualKeySelection = true; StateHasChanged(); } // File preview pagination methods private void GoToPage(int page) { if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) return; var totalPages = GetTotalPages(selectedSheet); if (page >= 1 && page <= totalPages) { currentPage = page; StateHasChanged(); } } private void FirstPage() => GoToPage(1); private void PreviousPage() => GoToPage(currentPage - 1); private void NextPage() => GoToPage(currentPage + 1); private void LastPage() => GoToPage(GetTotalPages(selectedSheet)); private List> GetCurrentPageData() { if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) return new List>(); var allData = fileData[selectedSheet]; var skip = (currentPage - 1) * pageSize; return allData.Skip(skip).Take(pageSize).ToList(); } private int GetStartRecord() { if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) return 0; return (currentPage - 1) * pageSize + 1; } private int GetEndRecord() { if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) return 0; var totalRecords = fileData[selectedSheet].Count; var endRecord = currentPage * pageSize; return Math.Min(endRecord, totalRecords); } private void OnPageSizeChanged(ChangeEventArgs e) { if (int.TryParse(e.Value?.ToString(), out int newPageSize)) { pageSize = newPageSize; currentPage = 1; // Reset to first page when changing page size StateHasChanged(); } }private void OnDatabaseCredentialChanged(ChangeEventArgs e) { selectedDatabaseCredential = e.Value?.ToString() ?? ""; ResetDatabaseState(); } private void OnRestCredentialChanged(ChangeEventArgs e) { var newCredential = e.Value?.ToString() ?? ""; // Clear the cache if we're switching to a different credential if (!string.IsNullOrEmpty(selectedRestCredential) && selectedRestCredential != newCredential) { ConnectionFactory.ClearRestClientCache(selectedRestCredential); Logger.LogInformation("Cleared REST client cache for credential: {CredentialName}", selectedRestCredential); } selectedRestCredential = newCredential; ResetRestState(); } private void ResetDatabaseState() { isDatabaseConnected = false; databaseTables.Clear(); selectedTable = ""; databaseSearchTerm = ""; databaseErrorMessage = ""; // Reset database selection availableDatabases.Clear(); selectedDatabase = ""; showDatabaseSelectionModal = false; isLoadingDatabases = false; // Reset custom query state useCustomQuery = false; customQuery = ""; isValidatingQuery = false; isQueryValid = false; queryValidationMessage = ""; queryPreviewData.Clear(); queryColumns.Clear(); showQueryPreview = false; isLoadingPreview = false; currentDatabaseManager?.Dispose(); currentDatabaseManager = null; // Clear mappings when resetting database state ClearAllMappings(); } private void ResetRestState() { isRestConnected = false; restEntities.Clear(); selectedRestEntity = null; restEntityDetails = null; restSearchTerm = ""; restErrorMessage = ""; currentRestDiscovery = null; currentRestClient = null; // Clear mappings when resetting REST state ClearAllMappings(); } private async Task ConnectToDatabase() { if (string.IsNullOrEmpty(selectedDatabaseCredential)) return; isConnectingDatabase = true; databaseErrorMessage = ""; try { // Trova la credenziale var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (credential == null) { databaseErrorMessage = "Credenziale database non trovata"; return; } // Test della connessione var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name); if (!success) { databaseErrorMessage = $"Connessione fallita: {message}"; return; } // Crea il database manager usando il factory con le credenziali complete Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential); currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential); Logger.LogInformation("Database manager creato con successo"); // Verifica se il database è specificato nella connection string bool isDatabaseSpecified = await IsDatabaseSpecifiedInConnectionString(credential); if (isDatabaseSpecified) { Logger.LogInformation("Database specificato nella connection string. Procedendo con discovery tabelle."); try { await LoadTablesFromConnectedDatabase(); isDatabaseConnected = true; Logger.LogInformation("Tabelle caricate con successo, database connesso"); return; // Importante: usciamo qui se tutto va bene } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento tabelle dal database specificato"); databaseErrorMessage = $"Errore nel caricamento tabelle: {ex.Message}"; return; } } else { Logger.LogInformation("Database non specificato nella connection string. Caricando database disponibili."); await LoadAvailableDatabases(); if (availableDatabases.Any()) { Logger.LogInformation("Trovati {DatabaseCount} database disponibili", availableDatabases.Count); showDatabaseSelectionModal = true; StateHasChanged(); return; // Non procediamo fino alla selezione del database } else { databaseErrorMessage = "Nessun database disponibile trovato"; return; } } } catch (Exception ex) { Logger.LogError(ex, "Errore nella connessione al database"); databaseErrorMessage = $"Errore: {ex.Message}"; } finally { isConnectingDatabase = false; StateHasChanged(); } }private async Task ConnectToRestApi() { if (string.IsNullOrEmpty(selectedRestCredential)) return; isConnectingRest = true; restErrorMessage = ""; try { // Trova la credenziale var credential = restApiCredentials.FirstOrDefault(c => c.Name == selectedRestCredential); if (credential == null) { restErrorMessage = "Credenziale REST API non trovata"; return; } // Test della connessione var (success, message) = await CredentialService.TestRestApiConnectionAsync(credential.Name); if (!success) { restErrorMessage = $"Connessione fallita: {message}"; return; } // Crea i client REST usando il factory con le credenziali complete currentRestClient = await ConnectionFactory.CreateRestServiceClientAsync(selectedRestCredential); currentRestDiscovery = await ConnectionFactory.CreateRestMetadataDiscoveryAsync(selectedRestCredential); Logger.LogInformation("Iniziando autenticazione per il servizio REST {ServiceType} con credenziale: {CredentialName}", credential.ServiceType, selectedRestCredential); // Autenticazione prima del discovery var authResult = await currentRestClient.AuthenticateAsync(); if (!authResult) { Logger.LogWarning("Autenticazione fallita per il servizio REST {ServiceType}", credential.ServiceType); restErrorMessage = "Autenticazione fallita per il servizio REST"; return; } Logger.LogInformation("Autenticazione completata. Iniziando discovery delle entità REST per {ServiceType}", credential.ServiceType); // Discovery delle entità disponibili restEntities = await currentRestDiscovery.DiscoverEntitySummariesAsync(); Logger.LogInformation("Discovery completato. Trovate {Count} entità", restEntities?.Count ?? 0); if (restEntities == null || !restEntities.Any()) { Logger.LogWarning("Nessuna entità trovata dal servizio REST"); restErrorMessage = "Nessuna entità disponibile dal servizio REST"; return; } isRestConnected = true; } catch (Exception ex) { Logger.LogError(ex, "Errore nella connessione al servizio REST"); restErrorMessage = $"Errore: {ex.Message}"; } finally { isConnectingRest = false; } } private async void SelectTable(string tableName) { selectedTable = tableName; // Clear custom query state when selecting a table useCustomQuery = false; customQuery = ""; isQueryValid = false; queryValidationMessage = ""; queryPreviewData.Clear(); queryColumns.Clear(); showQueryPreview = false; // Clear mappings when changing table ClearAllMappings(); // Reset key field logic sourceKeyField = ""; suggestedPrimaryKey = ""; requiresManualKeySelection = false; // Carica i dettagli della tabella se non sono già stati caricati if (!databaseTables.ContainsKey(tableName) && currentDatabaseManager != null) { try { var tableSchema = await currentDatabaseManager.GetTableSchemaAsync(tableName); databaseTables[tableName] = tableSchema; } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento dello schema della tabella {TableName}", tableName); databaseErrorMessage = $"Errore nel caricamento della tabella: {ex.Message}"; } } // If it's a database source, try to detect the primary key if (selectedSourceType == "database" && currentDatabaseManager != null) { try { var primaryKey = await currentDatabaseManager.GetPrimaryKeyFieldAsync(tableName); if (!string.IsNullOrEmpty(primaryKey)) { suggestedPrimaryKey = primaryKey; // Suggest the primary key but don't auto-select it Logger.LogInformation("Primary key detected for table {TableName}: {PrimaryKey}", tableName, primaryKey); } else { // No primary key found, require manual selection requiresManualKeySelection = true; Logger.LogInformation("No primary key found for table {TableName}, manual selection required", tableName); } } catch (Exception ex) { Logger.LogError(ex, "Error detecting primary key for table {TableName}", tableName); requiresManualKeySelection = true; } } else { // For non-database sources, always require manual selection requiresManualKeySelection = true; } StateHasChanged(); } private async Task SelectRestEntity(RestEntitySummary entity) { selectedRestEntity = entity; // Clear mappings when changing entity ClearAllMappings(); try { if (currentRestDiscovery != null) { // Discovery dei dettagli dell'entità restEntityDetails = await currentRestDiscovery.DiscoverEntityDetailsAsync(entity.Name); } else { restErrorMessage = "Servizio di discovery REST non disponibile"; return; } } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento dettagli entità {EntityName}", entity.Name); restErrorMessage = $"Errore nel caricamento dettagli entità: {ex.Message}"; } } // Metodi per la ricerca e il filtraggio private IEnumerable GetFilteredDatabaseTables() { if (string.IsNullOrEmpty(databaseSearchTerm)) return availableTableNames; return availableTableNames.Where(table => table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase)); } private IEnumerable GetFilteredRestEntities() { if (string.IsNullOrEmpty(restSearchTerm)) return restEntities; return restEntities.Where(entity => entity.Name.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase) || (!string.IsNullOrEmpty(entity.Label) && entity.Label.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase))); } private async Task FilterDatabaseTables(ChangeEventArgs e) { databaseSearchTerm = e.Value?.ToString() ?? ""; await InvokeAsync(StateHasChanged); } private async Task FilterRestEntities(ChangeEventArgs e) { restSearchTerm = e.Value?.ToString() ?? ""; await InvokeAsync(StateHasChanged); } private async Task ClearDatabaseSearch() { databaseSearchTerm = ""; await InvokeAsync(StateHasChanged); } private async Task ClearRestSearch() { restSearchTerm = ""; await InvokeAsync(StateHasChanged); } // Metodi per il mapping dei campi private void SelectDbColumn(string columnName) { selectedDbColumn = columnName; } private void SelectRestProperty(string propertyName) { selectedRestProperty = propertyName; } private void CreateMapping() { if (string.IsNullOrEmpty(selectedDbColumn) || string.IsNullOrEmpty(selectedRestProperty)) return; // Rimuovi eventuali mapping esistenti per questo campo database if (fieldMappings.ContainsKey(selectedDbColumn)) { fieldMappings.Remove(selectedDbColumn); } // Crea il nuovo mapping fieldMappings[selectedDbColumn] = selectedRestProperty; Logger.LogInformation("Creato mapping: {DbColumn} -> {RestProperty}", selectedDbColumn, selectedRestProperty); // Deseleziona i campi selectedDbColumn = ""; selectedRestProperty = ""; } private void RemoveMapping() { if (string.IsNullOrEmpty(selectedDbColumn) || !fieldMappings.ContainsKey(selectedDbColumn)) return; fieldMappings.Remove(selectedDbColumn); Logger.LogInformation("Rimosso mapping per campo: {DbColumn}", selectedDbColumn); } private void RemoveSpecificMapping(string dbColumn) { if (fieldMappings.ContainsKey(dbColumn)) { fieldMappings.Remove(dbColumn); Logger.LogInformation("Rimosso mapping specifico per campo: {DbColumn}", dbColumn); } } private void ClearAllMappings() { fieldMappings.Clear(); selectedDbColumn = ""; selectedRestProperty = ""; sourceKeyField = ""; transferMessage = ""; transferMessageType = ""; Logger.LogInformation("Tutti i mapping e le configurazioni sono stati cancellati"); } private void AutoMapFields() { if (restEntityDetails == null) return; IEnumerable sourceColumns = new List(); // Ottiene le colonne in base al tipo di sorgente if (selectedSourceType == "database") { if (useCustomQuery && queryColumns.Any()) { sourceColumns = queryColumns; } else if (!useCustomQuery && databaseTables.ContainsKey(selectedTable)) { sourceColumns = databaseTables[selectedTable].Select(c => c.Name); } } else if (selectedSourceType == "file" && fileSheets.ContainsKey(selectedSheet)) { sourceColumns = fileSheets[selectedSheet]; } if (!sourceColumns.Any()) return; var restProperties = restEntityDetails.Properties; int mappingsCreated = 0; foreach (var sourceColumn in sourceColumns) { // Trova una proprietà REST con nome simile var matchingProperty = restProperties.FirstOrDefault(p => string.Equals(p.Name, sourceColumn, StringComparison.OrdinalIgnoreCase) || string.Equals(p.Name.Replace("_", ""), sourceColumn.Replace("_", ""), StringComparison.OrdinalIgnoreCase) || string.Equals(p.Name.Replace("Id", ""), sourceColumn.Replace("Id", ""), StringComparison.OrdinalIgnoreCase) ); if (matchingProperty != null && !fieldMappings.ContainsKey(sourceColumn)) { fieldMappings[sourceColumn] = matchingProperty.Name; mappingsCreated++; } } Logger.LogInformation("Auto-mapping completato. Creati {Count} mapping automatici da {SourceType}", mappingsCreated, useCustomQuery ? "query custom" : selectedSourceType); } private async Task ShowMappingSummary() { var summary = "Riepilogo Configurazione:\n\n"; summary += "=== MAPPING CAMPI ===\n"; foreach (var mapping in fieldMappings) { summary += $"• {mapping.Key} → {mapping.Value}\n"; } summary += "\n=== CONFIGURAZIONE ASSOCIAZIONI ===\n"; summary += $"• Sistema associazioni: {(useRecordAssociations ? "Abilitato" : "Disabilitato")}\n"; if (useRecordAssociations) { summary += $"• Campo chiave sorgente: {(!string.IsNullOrEmpty(sourceKeyField) ? sourceKeyField : "Rilevamento automatico")}\n"; } await JSRuntime.InvokeVoidAsync("alert", summary); } private async Task StartDataTransfer() { if (!fieldMappings.Any() || currentRestClient == null || selectedRestEntity == null) { transferMessage = "Configurazione incompleta. Assicurati di aver selezionato la fonte dati, entità e configurato almeno una mappatura."; transferMessageType = "error"; return; } // Check source-specific requirements if (selectedSourceType == "database") { if (currentDatabaseManager == null) { transferMessage = "Database non connesso."; transferMessageType = "error"; return; } if (useCustomQuery) { if (!isQueryValid || string.IsNullOrWhiteSpace(customQuery)) { transferMessage = "Query custom non valida. Validare la query prima di procedere."; transferMessageType = "error"; return; } } else if (string.IsNullOrEmpty(selectedTable)) { transferMessage = "Tabella non selezionata."; transferMessageType = "error"; return; } } if (selectedSourceType == "file" && string.IsNullOrEmpty(selectedSheet)) { transferMessage = "File non caricato o foglio non selezionato."; transferMessageType = "error"; return; } // Validate source key field when using record associations if (useRecordAssociations && string.IsNullOrEmpty(sourceKeyField)) { transferMessage = "Campo chiave sorgente richiesto. Seleziona un campo che identifichi univocamente ogni record per utilizzare il sistema di associazioni."; transferMessageType = "error"; return; } isTransferringData = true; transferMessage = ""; transferMessageType = ""; transferResults.Clear(); try { var sourceName = selectedSourceType == "database" ? (useCustomQuery ? "custom_query" : selectedTable) : selectedSheet; Logger.LogInformation("Iniziando trasferimento dati da {SourceType} {Source} a {Entity} con {MappingCount} mappature", selectedSourceType, sourceName, selectedRestEntity.Name, fieldMappings.Count); // 1. Ottieni tutti i record dalla fonte dati var records = await GetAllRecordsFromSource(); Logger.LogInformation("Ottenuti {RecordCount} record da {SourceType} {Source}", records.Count(), selectedSourceType, sourceName); if (!records.Any()) { transferMessage = "Nessun record trovato nella fonte dati selezionata."; transferMessageType = "error"; return; } // 2. Ottieni i campi obbligatori dell'entità REST (se non ci sono campi chiave) var requiredFields = new HashSet(); if (!keyFields.Any() && restEntityDetails != null) { requiredFields = restEntityDetails.Properties .Where(p => p.IsRequired && fieldMappings.ContainsValue(p.Name)) .Select(p => p.Name) .ToHashSet(); Logger.LogInformation("Nessun campo chiave definito. Utilizzo {RequiredFieldsCount} campi obbligatori per controllo duplicati: {RequiredFields}", requiredFields.Count, string.Join(", ", requiredFields)); } // 3. Trasforma e trasferisci ogni record int successCount = 0; int errorCount = 0; int updatedCount = 0; int duplicateCount = 0; var errors = new List(); int recordNumber = 1; foreach (var record in records) { var transferResult = new TransferResult { RecordNumber = recordNumber, RecordData = new Dictionary(record) }; try { // Trasforma il record in base ai mapping var restData = TransformRecordToRestEntity(record); // Genera la chiave sorgente per questo record var sourceKey = GenerateSourceKey(record); // NUOVO SISTEMA: Cerca associazione esistente basata sul valore della chiave if (useRecordAssociations && !string.IsNullOrEmpty(sourceKey)) { Logger.LogInformation("ASSOCIATION DEBUG: Cerco associazione - KeyValue: '{KeyValue}', Entity: '{Entity}', Credential: '{Credential}'", sourceKey, selectedRestEntity.Name, selectedRestCredential); // Cerca se esiste già un'associazione per questo valore chiave var existingAssociation = await CredentialService.FindKeyAssociationByValueAsync( sourceKey, selectedRestEntity.Name, selectedRestCredential); // FALLBACK: Se non troviamo l'associazione con tutti i parametri, proviamo solo con il KeyValue if (existingAssociation == null) { Logger.LogWarning("ASSOCIATION DEBUG: Associazione non trovata con parametri specifici, provo solo con KeyValue: '{KeyValue}'", sourceKey); existingAssociation = await CredentialService.FindKeyAssociationByValueAsync(sourceKey); if (existingAssociation != null) { Logger.LogWarning("ASSOCIATION DEBUG: Trovata associazione con fallback - ID: {AssociationId}, Entity: '{Entity}', Credential: '{Credential}'", existingAssociation.Id, existingAssociation.DestinationEntity, existingAssociation.RestCredentialName); // Verifica se l'associazione trovata è compatibile if (existingAssociation.DestinationEntity != selectedRestEntity.Name || existingAssociation.RestCredentialName != selectedRestCredential) { Logger.LogWarning("ASSOCIATION DEBUG: Associazione non compatibile - Entity: '{FoundEntity}' vs '{ExpectedEntity}', Credential: '{FoundCredential}' vs '{ExpectedCredential}'", existingAssociation.DestinationEntity, selectedRestEntity.Name, existingAssociation.RestCredentialName, selectedRestCredential); existingAssociation = null; } } } Logger.LogInformation("ASSOCIATION DEBUG: Associazione finale: {Found}. ID: {AssociationId}, DestinationId: '{DestinationId}', IsActive: {IsActive}", existingAssociation != null, existingAssociation?.Id, existingAssociation?.DestinationId, existingAssociation?.IsActive); if (existingAssociation != null && existingAssociation.IsActive) { // Prova direttamente l'aggiornamento - più efficiente che verificare prima l'esistenza Logger.LogInformation("ASSOCIATION DEBUG: Tentativo aggiornamento record esistente - DestinationId: '{DestinationId}'", existingAssociation.DestinationId); try { var updateResult = await currentRestClient.UpdateEntityAsync( selectedRestEntity.Name, existingAssociation.DestinationId, restData); if (updateResult != null) { updatedCount++; transferResult.Status = "updated"; transferResult.Message = $"Record aggiornato con successo tramite associazione (ID: {existingAssociation.DestinationId})"; transferResult.EntityId = existingAssociation.DestinationId; // Aggiorna l'associazione con la data di ultimo aggiornamento e verifica existingAssociation.UpdatedAt = DateTime.UtcNow; existingAssociation.LastVerifiedAt = DateTime.UtcNow; await CredentialService.UpdateKeyAssociationAsync(existingAssociation); Logger.LogInformation("ASSOCIATION DEBUG: Record aggiornato con successo tramite associazione: {EntityId} per valore chiave {KeyValue}", existingAssociation.DestinationId, sourceKey); transferResults.Add(transferResult); recordNumber++; continue; } else { // Update fallito ma senza eccezione - probabilmente l'entità non esiste più Logger.LogWarning("ASSOCIATION DEBUG: Aggiornamento fallito (result null) per associazione {AssociationId} - elimino associazione e creo nuovo record", existingAssociation.Id); goto HandleInvalidAssociation; } } catch (Exception updateEx) { // Update fallito con eccezione - probabilmente l'entità non esiste più Logger.LogWarning(updateEx, "ASSOCIATION DEBUG: Aggiornamento fallito per associazione {AssociationId} - elimino associazione e creo nuovo record", existingAssociation.Id); goto HandleInvalidAssociation; } HandleInvalidAssociation: // L'ID di destinazione non esiste più o l'update è fallito - elimina l'associazione non valida try { await CredentialService.DeleteKeyAssociationAsync(existingAssociation.Id); Logger.LogInformation("ASSOCIATION DEBUG: Associazione non valida eliminata: {AssociationId}", existingAssociation.Id); } catch (Exception delEx) { Logger.LogWarning(delEx, "Errore nell'eliminazione dell'associazione non valida {AssociationId}", existingAssociation.Id); } transferResult.Status = "info"; transferResult.Message = $"Associazione non valida eliminata (aggiornamento fallito) - creazione nuovo record"; // Procedi con la creazione di un nuovo record (non aggiungere il result qui, sarà aggiunto dopo CreateNewRecord) } } // Crea un nuovo record var result = await currentRestClient.CreateEntityAsync(selectedRestEntity.Name, restData); if (result != null) { successCount++; transferResult.Status = "success"; transferResult.Message = "Record inserito con successo"; transferResult.EntityId = result.ContainsKey("id") ? result["id"]?.ToString() : result.ContainsKey("Id") ? result["Id"]?.ToString() : result.ContainsKey("DocEntry") ? result["DocEntry"]?.ToString() : null; // Crea associazione solo se abbiamo una chiave sorgente e un ID destinazione if (useRecordAssociations && !string.IsNullOrEmpty(sourceKey) && !string.IsNullOrEmpty(transferResult.EntityId)) { try { // Determina i campi chiave automaticamente var destinationKeyField = GetEntityIdField(); // Campo chiave nella destinazione var association = new KeyAssociation { KeyValue = sourceKey, SourceKeyField = sourceKeyField, DestinationKeyField = destinationKeyField, DestinationEntity = selectedRestEntity.Name, DestinationId = transferResult.EntityId, RestCredentialName = selectedRestCredential, CreatedAt = DateTime.UtcNow, LastVerifiedAt = DateTime.UtcNow, AdditionalInfo = System.Text.Json.JsonSerializer.Serialize(new { TransferDate = DateTime.UtcNow, RecordNumber = recordNumber, MappingCount = fieldMappings.Count, SourceType = selectedSourceType }) }; Logger.LogInformation("ASSOCIATION DEBUG: Creazione nuova associazione - KeyValue: '{KeyValue}', Entity: '{Entity}', DestinationId: '{DestinationId}', Credential: '{Credential}'", sourceKey, selectedRestEntity.Name, transferResult.EntityId, selectedRestCredential); var associationId = await CredentialService.SaveKeyAssociationAsync(association); Logger.LogInformation("DEBUG: Associazione salvata con ID: {AssociationId}", associationId); } catch (Exception assocEx) { Logger.LogWarning(assocEx, "Errore nella creazione dell'associazione per record {RecordNumber}", recordNumber); // Non interrompiamo il trasferimento per errori di associazione } } Logger.LogDebug("Record trasferito con successo: {Data}", string.Join(", ", restData.Select(kvp => $"{kvp.Key}={kvp.Value}"))); } else { errorCount++; transferResult.Status = "error"; transferResult.Message = "Errore nel trasferimento del record (result null)"; errors.Add($"Errore nel trasferimento del record {recordNumber}"); } } catch (Exception ex) { errorCount++; transferResult.Status = "error"; transferResult.Message = $"Errore: {ex.Message}"; errors.Add($"Errore nel trasferimento del record {recordNumber}: {ex.Message}"); Logger.LogError(ex, "Errore nel trasferimento del record {RecordNumber}", recordNumber); } transferResults.Add(transferResult); recordNumber++; } // 4. Mostra risultati if (errorCount == 0) { var message = $"Trasferimento completato con successo! "; var messageParts = new List(); if (successCount > 0) messageParts.Add($"{successCount} record inseriti"); if (updatedCount > 0) messageParts.Add($"{updatedCount} record aggiornati"); if (duplicateCount > 0) messageParts.Add($"{duplicateCount} duplicati rilevati (warning)"); message += string.Join(", ", messageParts) + "."; transferMessage = message; transferMessageType = "success"; } else { var message = $"Trasferimento completato con {(duplicateCount > 0 ? "warning e " : "")}errori. "; var messageParts = new List(); if (successCount > 0) messageParts.Add($"Inserimenti: {successCount}"); if (updatedCount > 0) messageParts.Add($"Aggiornamenti: {updatedCount}"); if (duplicateCount > 0) messageParts.Add($"Duplicati (warning): {duplicateCount}"); messageParts.Add($"Errori: {errorCount}"); message += string.Join(", ", messageParts); if (errors.Any()) { message += $". Primi errori: {string.Join("; ", errors.Take(3))}"; } transferMessage = message; transferMessageType = errorCount > 0 ? "error" : "warning"; } Logger.LogInformation("Trasferimento completato. Inserimenti: {SuccessCount}, Aggiornamenti: {UpdatedCount}, Duplicati: {DuplicateCount}, Errori: {ErrorCount}", successCount, updatedCount, duplicateCount, errorCount); } catch (Exception ex) { Logger.LogError(ex, "Errore generale nel trasferimento dati"); transferMessage = $"Errore nel trasferimento dati: {ex.Message}"; transferMessageType = "error"; } finally { isTransferringData = false; } } private async Task>> GetAllRecordsFromSource() { if (selectedSourceType == "database") { return await GetAllRecordsFromDatabase(); } else if (selectedSourceType == "file") { return await GetAllRecordsFromFile(); } return new List>(); } private async Task>> GetAllRecordsFromDatabase() { if (currentDatabaseManager == null) return new List>(); try { if (useCustomQuery) { // Usa la query custom per ottenere tutti i record if (!isQueryValid || string.IsNullOrWhiteSpace(customQuery)) { throw new InvalidOperationException("Query custom non valida. Validare la query prima di procedere."); } // CONTROLLO DI SICUREZZA AGGIUNTIVO: Verifica che sia ancora una SELECT if (!IsSelectQuery(customQuery)) { throw new InvalidOperationException("ERRORE DI SICUREZZA: Tentativo di eseguire una query non SELECT. Operazione bloccata per sicurezza."); } var cleanQuery = CleanQuery(customQuery); Logger.LogInformation("Esecuzione query custom per trasferimento dati: {Query}", cleanQuery); return await currentDatabaseManager.ExecuteRawQueryAsync(cleanQuery); } else { // Usa il metodo standard per tabelle if (string.IsNullOrEmpty(selectedTable)) { throw new InvalidOperationException("Nessuna tabella selezionata."); } return await currentDatabaseManager.GetAllRecordsAsync(selectedTable); } } catch (Exception ex) { Logger.LogError(ex, "Errore nell'ottenere i record dal database. UseCustomQuery: {UseCustomQuery}, Table: {Table}, Query: {Query}", useCustomQuery, selectedTable, useCustomQuery ? customQuery : "N/A"); throw; } } private async Task>> GetAllRecordsFromFile() { if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) { return new List>(); } await Task.CompletedTask; return fileData[selectedSheet]; } private Dictionary TransformRecordToRestEntity(Dictionary dbRecord) { var restData = new Dictionary(); foreach (var mapping in fieldMappings) { string dbColumn = mapping.Key; string restProperty = mapping.Value; if (dbRecord.ContainsKey(dbColumn)) { var value = dbRecord[dbColumn]; // Trasforma il valore se necessario (es. date format, null handling, etc.) var transformedValue = TransformValue(value, dbColumn, restProperty); if (transformedValue != null) { restData[restProperty] = transformedValue; } } } Logger.LogDebug("Record trasformato: {DbColumns} → {RestProperties}", string.Join(", ", dbRecord.Keys), string.Join(", ", restData.Keys)); return restData; } private object? TransformValue(object? value, string dbColumn, string restProperty) { if (value == null || value == DBNull.Value) return null; // Ottieni informazioni sui tipi per fare trasformazioni intelligenti var dbColumnInfo = databaseTables.ContainsKey(selectedTable) ? databaseTables[selectedTable].FirstOrDefault(c => c.Name == dbColumn) : null; var restPropertyInfo = restEntityDetails?.Properties.FirstOrDefault(p => p.Name == restProperty); // Trasformazioni specifiche per tipo if (restPropertyInfo != null) { switch (restPropertyInfo.Type.ToLower()) { case "edm.string": return value.ToString(); case "edm.int32": case "edm.int64": if (int.TryParse(value.ToString(), out int intVal)) return intVal; break; case "edm.decimal": case "edm.double": if (decimal.TryParse(value.ToString(), out decimal decVal)) return decVal; break; case "edm.boolean": if (bool.TryParse(value.ToString(), out bool boolVal)) return boolVal; // Gestisci anche valori numerici (0/1) come boolean if (value.ToString() == "1") return true; if (value.ToString() == "0") return false; break; case "edm.datetime": case "edm.datetimeoffset": if (DateTime.TryParse(value.ToString(), out DateTime dateVal)) return dateVal.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); break; } } // Fallback: restituisci il valore convertito a stringa return value.ToString(); } private string GetPropertyPlaceholder(RestPropertyInfo property) { return property.Type switch { "Edm.String" => $"Inserisci {property.Name}" + (property.MaxLength.HasValue ? $" (max {property.MaxLength})" : ""), "Edm.Int32" => "Numero intero", "Edm.Decimal" => "Numero decimale", "Edm.DateTime" => "Data/Ora (YYYY-MM-DD)", "Edm.Boolean" => "true/false", _ => $"Valore per {property.Name}" }; } public void Dispose() { currentDatabaseManager?.Dispose(); } private char DetectCsvSeparator(string line) { // Common separators to check var separators = new[] { ',', ';', '\t', '|' }; var counts = new Dictionary(); bool inQuotes = false; // Count separators outside of quotes foreach (char c in line) { if (c == '"') { inQuotes = !inQuotes; } else if (!inQuotes && separators.Contains(c)) { counts[c] = counts.GetValueOrDefault(c, 0) + 1; } } // Return the separator with the highest count, default to comma if (counts.Any()) { var mostCommon = counts.OrderByDescending(x => x.Value).First(); // Make sure we have at least one occurrence to avoid single-column files if (mostCommon.Value > 0) { return mostCommon.Key; } } return ','; // Default fallback } /// /// Verifica se il pulsante di trasferimento può essere abilitato /// private bool IsTransferButtonEnabled() { // Base requirements if (!fieldMappings.Any()) return false; // Se il sistema di associazioni è abilitato, il campo chiave sorgente è obbligatorio if (useRecordAssociations && string.IsNullOrEmpty(sourceKeyField)) return false; return true; } // Helper methods per UI risultati private string GetResultRowClass(string status) { return status switch { "success" => "", "updated" => "table-info", "duplicate" => "table-warning", "error" => "table-danger", _ => "" }; } private string GetResultBadgeClass(string status) { return status switch { "success" => "bg-success", "updated" => "bg-info", "duplicate" => "bg-warning text-dark", "error" => "bg-danger", _ => "bg-secondary" }; } private string GetResultIcon(string status) { return status switch { "success" => "fa-check-circle", "updated" => "fa-edit", "duplicate" => "fa-exclamation-triangle", "error" => "fa-times-circle", _ => "fa-question-circle" }; } private string GetResultStatusText(string status) { return status switch { "success" => "Inserito", "updated" => "Aggiornato", "duplicate" => "Duplicato", "error" => "Errore", _ => "Sconosciuto" }; } /// /// Genera una chiave univoca per il record sorgente /// private string GenerateSourceKey(Dictionary record) { try { // Il campo chiave sorgente deve essere sempre specificato if (string.IsNullOrEmpty(sourceKeyField)) { throw new InvalidOperationException("Campo chiave sorgente non specificato. La selezione del campo chiave è obbligatoria."); } if (!record.ContainsKey(sourceKeyField)) { throw new InvalidOperationException($"Il campo chiave '{sourceKeyField}' non è presente nel record sorgente."); } var keyValue = record[sourceKeyField]?.ToString(); if (string.IsNullOrEmpty(keyValue)) { throw new InvalidOperationException($"Il valore del campo chiave '{sourceKeyField}' è vuoto o null per questo record."); } // Normalizza il valore della chiave (trim e gestione case-sensitive) return keyValue.Trim(); } catch (Exception ex) { Logger.LogError(ex, "Errore nella generazione della chiave sorgente per il campo {SourceKeyField}", sourceKeyField); throw; } } /// /// Gestisce la connessione al database con schema specifico /// private async Task ConnectToDatabaseWithSchema(string? specificSchema = null) { if (string.IsNullOrEmpty(selectedDatabaseCredential)) return; isConnectingDatabase = true; databaseErrorMessage = ""; try { // Trova la credenziale var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (credential == null) { databaseErrorMessage = "Credenziale database non trovata"; return; } // Test della connessione var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name); if (!success) { databaseErrorMessage = $"Connessione fallita: {message}"; return; } // Crea il database manager Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential); currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential); Logger.LogInformation("Database manager creato con successo"); // Se è specificato uno schema, utilizzalo direttamente if (!string.IsNullOrEmpty(specificSchema)) { Logger.LogInformation("Utilizzando schema specifico: {Schema}", specificSchema); await LoadSchemaForDatabase(specificSchema); } else { // Prova il discovery automatico dello schema await DiscoverDatabaseSchema(); } if (databaseTables.Count > 0) { isDatabaseConnected = true; Logger.LogInformation("Connessione database completata con {TableCount} tabelle", databaseTables.Count); } } catch (Exception ex) { Logger.LogError(ex, "Errore nella connessione al database"); databaseErrorMessage = $"Errore: {ex.Message}"; } finally { isConnectingDatabase = false; StateHasChanged(); } } /// /// Scopre automaticamente lo schema del database /// private async Task DiscoverDatabaseSchema() { try { Logger.LogInformation("Iniziando discovery automatico dello schema"); var schema = await currentDatabaseManager!.GetDatabaseSchemaAsync(); Logger.LogInformation("Schema discovery completato. Numero elementi: {Count}", schema?.Count() ?? 0); databaseTables = schema as Dictionary> ?? (schema != null ? new Dictionary>(schema) : new Dictionary>()); if (databaseTables.Count == 0) { // Se non ci sono tabelle, potrebbe essere necessario selezionare un database specifico // Schema discovery completato senza successo databaseErrorMessage = "Impossibile rilevare le tabelle del database. Verificare le credenziali di connessione."; } else { // Rileva e salva lo schema corrente se presente nelle chiavi delle tabelle var firstTableKey = databaseTables.Keys.FirstOrDefault(); if (!string.IsNullOrEmpty(firstTableKey) && firstTableKey.Contains('.')) { var detectedSchema = firstTableKey.Split('.')[0]; Logger.LogInformation("Schema rilevato automaticamente: {Schema}", detectedSchema); } } } catch (Exception ex) { Logger.LogError(ex, "Errore durante il discovery automatico dello schema"); throw; } } /// /// Carica lo schema per un database specifico /// private async Task LoadSchemaForDatabase(string schemaName) { try { // TODO: Implementare la logica specifica per il caricamento di uno schema // Per ora utilizziamo il discovery standard e filtriamo i risultati var schema = await currentDatabaseManager!.GetDatabaseSchemaAsync(); databaseTables = schema as Dictionary> ?? new Dictionary>(); // Filtra le tabelle per lo schema specificato if (!string.IsNullOrEmpty(schemaName)) { var filteredTables = databaseTables .Where(kvp => kvp.Key.StartsWith($"{schemaName}.", StringComparison.OrdinalIgnoreCase)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); if (filteredTables.Any()) { databaseTables = filteredTables; Logger.LogInformation("Caricate {TableCount} tabelle per lo schema {Schema}", filteredTables.Count, schemaName); } else { Logger.LogWarning("Nessuna tabella trovata per lo schema {Schema}", schemaName); } } } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento dello schema {Schema}", schemaName); throw; } } /// /// Estrae lo schema dal nome completo di una tabella /// private string? ExtractSchemaFromTableName(string fullTableName) { if (string.IsNullOrEmpty(fullTableName) || !fullTableName.Contains('.')) return null; var parts = fullTableName.Split('.'); return parts.Length > 1 ? parts[0] : null; } /// /// Ottiene lo schema correntemente utilizzato dal database connesso /// private string? GetCurrentDatabaseSchema() { if (!databaseTables.Any()) return null; var firstTable = databaseTables.Keys.FirstOrDefault(); return !string.IsNullOrEmpty(firstTable) ? ExtractSchemaFromTableName(firstTable) : null; } /// /// Ottiene il campo ID dell'entità REST selezionata /// private string GetEntityIdField() { if (restEntityDetails?.Properties != null) { // Cerca il campo ID (tipicamente "Id", "ID", "id", o il primo campo che contiene "id") var idProperty = restEntityDetails.Properties.FirstOrDefault(p => p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase) || p.Name.Equals("ID", StringComparison.OrdinalIgnoreCase) || p.Name.Contains("id", StringComparison.OrdinalIgnoreCase)); return idProperty?.Name ?? "Id"; // Default a "Id" se non trovato } return "Id"; } /// /// Verifica se una query è una SELECT query sicura /// private bool IsSelectQuery(string query) { if (string.IsNullOrWhiteSpace(query)) return false; var trimmedQuery = query.Trim(); return trimmedQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase); } /// /// Pulisce una query SQL rimuovendo caratteri pericolosi /// private string CleanQuery(string query) { if (string.IsNullOrWhiteSpace(query)) return ""; // Rimuove caratteri potenzialmente pericolosi var cleanQuery = query.Trim(); // Rimuove eventuali terminatori multipli while (cleanQuery.EndsWith(";")) { cleanQuery = cleanQuery.Substring(0, cleanQuery.Length - 1).Trim(); } return cleanQuery; } /// /// Gestisce il cambio di modalità tra tabelle e query custom /// private void OnQueryModeChanged(ChangeEventArgs e) { useCustomQuery = (bool)(e.Value ?? false); // Reset stato quando cambia modalità if (useCustomQuery) { // Reset selezione tabella selectedTable = ""; ClearAllMappings(); } else { // Reset query custom customQuery = ""; isQueryValid = false; queryValidationMessage = ""; queryPreviewData.Clear(); queryColumns.Clear(); showQueryPreview = false; } StateHasChanged(); } /// /// Valida la query SQL custom /// private async Task ValidateCustomQuery() { if (string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null) { isQueryValid = false; queryValidationMessage = "Query vuota o manager database non disponibile"; return; } isValidatingQuery = true; try { // Controllo di sicurezza: verifica che sia una SELECT if (!IsSelectQuery(customQuery)) { isQueryValid = false; queryValidationMessage = "Solo query SELECT sono permesse per sicurezza"; return; } var cleanQuery = CleanQuery(customQuery); // Trova la credenziale per determinare il tipo di database var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (credential == null) { isQueryValid = false; queryValidationMessage = "Credenziale database non trovata"; return; } // Crea una query di test con sintassi appropriata per il tipo di database var testQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 1); Logger.LogInformation("Validando query: {Query}", testQuery); // Prova a eseguire la query per validarla var testResults = await currentDatabaseManager.ExecuteRawQueryAsync(testQuery); if (testResults != null && testResults.Any()) { var firstRow = testResults.First(); queryColumns = firstRow.Keys.ToList(); isQueryValid = true; queryValidationMessage = $"Query valida - {queryColumns.Count} colonne rilevate"; // Clear mappings quando cambia la query ClearAllMappings(); Logger.LogInformation("Query validata con successo: {ColumnCount} colonne", queryColumns.Count); } else { isQueryValid = false; queryValidationMessage = "La query non ha restituito risultati o colonne"; } } catch (Exception ex) { Logger.LogError(ex, "Errore nella validazione della query"); isQueryValid = false; queryValidationMessage = $"Errore nella validazione: {ex.Message}"; } finally { isValidatingQuery = false; StateHasChanged(); } } /// /// Carica un'anteprima dei dati della query /// private async Task LoadQueryPreview() { if (!isQueryValid || string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null) return; isLoadingPreview = true; try { var cleanQuery = CleanQuery(customQuery); // Trova la credenziale per determinare il tipo di database var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (credential == null) { queryValidationMessage = "Credenziale database non trovata"; return; } // Crea una query di anteprima con sintassi appropriata per il tipo di database var previewQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 10); Logger.LogInformation("Caricando anteprima con query: {Query}", previewQuery); var previewResults = await currentDatabaseManager.ExecuteRawQueryAsync(previewQuery); queryPreviewData = previewResults.ToList(); showQueryPreview = true; Logger.LogInformation("Caricata anteprima query con {RecordCount} record", queryPreviewData.Count); } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento anteprima query"); queryValidationMessage = $"Errore anteprima: {ex.Message}"; } finally { isLoadingPreview = false; StateHasChanged(); } } /// /// Crea una query limitata in base al tipo di database /// private string CreateLimitedQuery(string baseQuery, DatabaseType databaseType, int limit) { return databaseType switch { DatabaseType.SqlServer => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery", DatabaseType.Oracle => $"SELECT * FROM ({baseQuery}) WHERE ROWNUM <= {limit}", DatabaseType.MySql => $"{baseQuery} LIMIT {limit}", DatabaseType.PostgreSql => $"{baseQuery} LIMIT {limit}", DatabaseType.Sqlite => $"{baseQuery} LIMIT {limit}", DatabaseType.DB2 => $"SELECT * FROM ({baseQuery}) FETCH FIRST {limit} ROWS ONLY", DatabaseType.SapHana => $"{baseQuery} LIMIT {limit}", _ => $"{baseQuery} LIMIT {limit}" // Default a LIMIT per database non riconosciuti }; } /// /// Nasconde l'anteprima della query /// private void HideQueryPreview() { showQueryPreview = false; StateHasChanged(); } /// /// Ottiene l'ID della credenziale sorgente corrente /// private int? GetCurrentSourceCredentialId() { // TODO: Implementare logica per ottenere l'ID dalla credenziale selezionata // Per ora ritorniamo null dato che i DTO non hanno ID return null; } /// /// Ottiene l'ID della credenziale destinazione corrente /// private int? GetCurrentDestinationCredentialId() { // TODO: Implementare logica per ottenere l'ID dalla credenziale selezionata // Per ora ritorniamo null dato che i DTO non hanno ID return null; } /// /// Annulla la selezione dello schema /// private void CancelSchemaSelection() { showSchemaSelectionModal = false; selectedSchema = ""; StateHasChanged(); } /// /// Ottiene le colonne di una tabella specifica /// private async Task> GetTableColumns(string fullTableName, string databaseName, string tableName) { var columns = new List(); try { var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (credential == null) return columns; var columnsQuery = credential.DatabaseType switch { DatabaseType.SqlServer => $@" SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, CASE WHEN CHARACTER_MAXIMUM_LENGTH IS NOT NULL THEN CHARACTER_MAXIMUM_LENGTH ELSE NUMERIC_PRECISION END as MAX_LENGTH FROM {databaseName}.INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{tableName}' ORDER BY ORDINAL_POSITION", DatabaseType.MySql => $@" SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, CASE WHEN CHARACTER_MAXIMUM_LENGTH IS NOT NULL THEN CHARACTER_MAXIMUM_LENGTH ELSE NUMERIC_PRECISION END as MAX_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{databaseName}' AND TABLE_NAME = '{tableName}' ORDER BY ORDINAL_POSITION", DatabaseType.PostgreSql => $@" SELECT column_name as COLUMN_NAME, data_type as DATA_TYPE, is_nullable as IS_NULLABLE, character_maximum_length as MAX_LENGTH FROM information_schema.columns WHERE table_schema = '{databaseName}' AND table_name = '{tableName}' ORDER BY ordinal_position", DatabaseType.Oracle => $@" SELECT COLUMN_NAME, DATA_TYPE, NULLABLE as IS_NULLABLE, DATA_LENGTH as MAX_LENGTH FROM ALL_TAB_COLUMNS WHERE OWNER = '{databaseName.ToUpper()}' AND TABLE_NAME = '{tableName.ToUpper()}' ORDER BY COLUMN_ID", _ => $@" SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, CHARACTER_MAXIMUM_LENGTH as MAX_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{databaseName}' AND TABLE_NAME = '{tableName}' ORDER BY ORDINAL_POSITION" }; var columnResults = await currentDatabaseManager!.ExecuteRawQueryAsync(columnsQuery); if (columnResults != null) { foreach (var row in columnResults) { var columnName = row.GetValueOrDefault("COLUMN_NAME")?.ToString() ?? ""; var dataType = row.GetValueOrDefault("DATA_TYPE")?.ToString() ?? ""; var isNullable = row.GetValueOrDefault("IS_NULLABLE")?.ToString()?.ToUpper() == "YES"; if (int.TryParse(row.GetValueOrDefault("MAX_LENGTH")?.ToString(), out int maxLength)) { // Usa maxLength se necessario } if (!string.IsNullOrEmpty(columnName)) { columns.Add(new DbColumnInfo { Name = columnName, DataType = dataType, IsNullable = isNullable }); } } } } catch (Exception ex) { Logger.LogError(ex, "Errore nell'ottenere le colonne per la tabella {TableName}", fullTableName); } return columns; } /// /// Conferma la selezione dello schema /// private async Task OnSchemaSelected() { if (string.IsNullOrEmpty(selectedSchema)) return; showSchemaSelectionModal = false; try { Logger.LogInformation("Schema selezionato: {Schema}. Riconnessione al database...", selectedSchema); // Riconnetti al database utilizzando lo schema selezionato await ConnectToDatabaseWithSchema(selectedSchema); if (isDatabaseConnected) { Logger.LogInformation("Connessione completata con successo usando lo schema {Schema}", selectedSchema); databaseErrorMessage = ""; // Pulisci eventuali errori precedenti } } catch (Exception ex) { Logger.LogError(ex, "Errore nella connessione con lo schema selezionato"); databaseErrorMessage = $"Errore nella connessione con schema {selectedSchema}: {ex.Message}"; } StateHasChanged(); } /// /// Carica la lista degli schemi disponibili /// private async Task LoadAvailableSchemas() { if (currentDatabaseManager == null) return; isLoadingSchemas = true; availableSchemas.Clear(); try { // Prova a ottenere tutti gli schemi/database disponibili // Questo metodo potrebbe non essere disponibile su tutti i database manager // In tal caso, proveremo con una query diretta try { var allSchemas = await currentDatabaseManager.GetDatabaseSchemaAsync(); if (allSchemas != null) { // Estrai i nomi degli schemi dalle chiavi delle tabelle var schemaNames = allSchemas.Keys .Where(key => key.Contains('.')) .Select(key => key.Split('.')[0]) .Distinct() .OrderBy(schema => schema) .ToList(); if (schemaNames.Any()) { availableSchemas.AddRange(schemaNames); Logger.LogInformation("Rilevati {SchemaCount} schemi dalle tabelle: {Schemas}", schemaNames.Count, string.Join(", ", schemaNames)); return; } } } catch (Exception ex) { Logger.LogWarning(ex, "Impossibile ottenere schemi dal database manager, provo con query dirette"); } // Se il metodo sopra non funziona, prova con query SQL specifiche per database await TryLoadSchemasWithDirectQuery(); } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento degli schemi disponibili"); } finally { isLoadingSchemas = false; } } /// /// Prova a caricare gli schemi con query SQL dirette /// private async Task TryLoadSchemasWithDirectQuery() { if (currentDatabaseManager == null) return; try { // Query diverse per ogni tipo di database - focalizzate sui database/cataloghi var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (credential == null) return; string? schemaQuery = credential.DatabaseType switch { DatabaseType.SqlServer => "SELECT name FROM sys.databases WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb') AND state = 0", DatabaseType.PostgreSql => "SELECT datname FROM pg_database WHERE datistemplate = false AND datname NOT IN ('postgres', 'template0', 'template1')", DatabaseType.MySql => "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')", DatabaseType.Oracle => "SELECT DISTINCT OWNER FROM ALL_TABLES WHERE OWNER NOT IN ('SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN', 'ANONYMOUS', 'CTXSYS', 'EXFSYS', 'LBACSYS', 'MDSYS', 'MGMT_VIEW', 'OLAPSYS', 'OWBSYS', 'ORDDATA', 'ORDSYS', 'SI_INFORMTN_SCHEMA', 'WK_TEST', 'WKPROXY', 'WMSYS', 'XDB', 'APEX_040000', 'APEX_PUBLIC_USER', 'DIP', 'FLOWS_FILES', 'HR', 'IX', 'OE', 'PM', 'SCOTT', 'SH', 'BI')", _ => null }; if (!string.IsNullOrEmpty(schemaQuery)) { Logger.LogInformation("Eseguendo query per database/schemi: {Query}", schemaQuery); var results = await currentDatabaseManager.ExecuteRawQueryAsync(schemaQuery); if (results != null && results.Any()) { var schemas = results.Select(row => { var firstValue = row.Values.FirstOrDefault(); return firstValue?.ToString() ?? ""; }) .Where(schema => !string.IsNullOrEmpty(schema)) .OrderBy(schema => schema) .ToList(); if (schemas.Any()) { availableSchemas.AddRange(schemas); Logger.LogInformation("Caricati {SchemaCount} database/schemi via query diretta per {DatabaseType}: {Schemas}", schemas.Count, credential.DatabaseType, string.Join(", ", schemas)); } } } } catch (Exception ex) { Logger.LogWarning(ex, "Errore nel caricamento database/schemi via query diretta"); } } // Gestione selezione database quando la discovery restituisce dizionario vuoto private async Task HandleDatabaseSelectionRequired() { isLoadingDatabases = true; showDatabaseSelectionModal = true; availableDatabases.Clear(); selectedDatabase = ""; try { if (currentDatabaseManager != null) { var dbs = await currentDatabaseManager.GetAvailableDatabasesAsync(); availableDatabases = dbs ?? new List(); } } catch (Exception ex) { databaseErrorMessage = $"Errore nel caricamento dei database: {ex.Message}"; } finally { isLoadingDatabases = false; } } private async Task OnDatabaseSelected() { try { if (string.IsNullOrEmpty(selectedDatabase)) { databaseErrorMessage = "Nessun database selezionato"; return; } showDatabaseSelectionModal = false; Logger.LogInformation("Database selezionato: {DatabaseName}. Riconnessione in corso...", selectedDatabase); // Riconnessione al database selezionato await ConnectToDatabaseWithSpecificDatabase(selectedDatabase); } catch (Exception ex) { Logger.LogError(ex, "Errore nella selezione del database: {DatabaseName}", selectedDatabase); databaseErrorMessage = $"Errore nella connessione al database {selectedDatabase}: {ex.Message}"; } } private void CancelDatabaseSelection() { showDatabaseSelectionModal = false; selectedDatabase = ""; databaseErrorMessage = "Selezione database annullata"; Logger.LogInformation("Selezione database annullata dall'utente"); } // Metodi helper per la connessione database private Task IsDatabaseSpecifiedInConnectionString(DatabaseCredential credential) { try { Logger.LogInformation("Verifica database specificato - Tipo: {DatabaseType}, DatabaseName: '{DatabaseName}', Connection: {ConnectionString}", credential.DatabaseType, credential.DatabaseName, credential.ConnectionString?.Substring(0, Math.Min(100, credential.ConnectionString?.Length ?? 0))); // Prima verifica se c'è un database specificato nel campo DatabaseName della credenziale if (!string.IsNullOrEmpty(credential.DatabaseName)) { Logger.LogInformation("Database specificato nel campo DatabaseName: '{DatabaseName}' - RESULT: TRUE", credential.DatabaseName); return Task.FromResult(true); } // Per SQL Server verifica se Initial Catalog o Database è specificato nella connection string if (credential.DatabaseType == DatabaseType.SqlServer) { var connectionString = credential.ConnectionString ?? ""; var hasInitialCatalog = connectionString.Contains("Initial Catalog=", StringComparison.OrdinalIgnoreCase); var hasDatabase = connectionString.Contains("Database=", StringComparison.OrdinalIgnoreCase); var result = hasInitialCatalog || hasDatabase; Logger.LogInformation("SQL Server - HasInitialCatalog: {HasInitialCatalog}, HasDatabase: {HasDatabase}, Result: {Result}", hasInitialCatalog, hasDatabase, result); return Task.FromResult(result); } // TODO: Implementare per altri tipi di database // MySQL: Database= // PostgreSQL: Database= // Oracle: più complesso con SID/Service Name Logger.LogWarning("Verifica database specificato non implementata per tipo database: {DatabaseType}", credential.DatabaseType); return Task.FromResult(true); // Default: assume database specificato per tipi non implementati } catch (Exception ex) { Logger.LogError(ex, "Errore nella verifica database specificato in connection string"); return Task.FromResult(true); // Default: assume database specificato in caso di errore } } private async Task LoadTablesFromConnectedDatabase() { try { if (currentDatabaseManager == null) { databaseErrorMessage = "Database manager non disponibile"; return; } Logger.LogInformation("Caricando tabelle dal database connesso"); var tableNames = await currentDatabaseManager.GetTableNamesAsync(); availableTableNames = tableNames.ToList(); Logger.LogInformation("Caricate {Count} tabelle dal database", availableTableNames.Count); // Resetta i dettagli delle tabelle - verranno caricati solo quando selezionati databaseTables.Clear(); } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento delle tabelle dal database connesso"); databaseErrorMessage = $"Errore nel caricamento tabelle: {ex.Message}"; throw; } } private async Task LoadAvailableDatabases() { try { if (currentDatabaseManager == null) { databaseErrorMessage = "Database manager non disponibile"; return; } isLoadingDatabases = true; Logger.LogInformation("Caricando database disponibili"); // Usa il metodo corretto dell'interfaccia IDatabaseManager var allDatabases = await currentDatabaseManager.GetAvailableDatabasesAsync(); Logger.LogInformation("Ottenuti {DatabaseCount} database dal server", allDatabases.Count); // Filtra i database di sistema availableDatabases = FilterSystemDatabases(allDatabases).ToList(); Logger.LogInformation("Trovati {TotalDatabases} database, filtrati a {FilteredDatabases} (esclusi quelli di sistema)", allDatabases.Count, availableDatabases.Count); } catch (Exception ex) { Logger.LogError(ex, "Errore nel caricamento dei database disponibili"); databaseErrorMessage = $"Errore nel caricamento database: {ex.Message}"; throw; } finally { isLoadingDatabases = false; } } private IEnumerable FilterSystemDatabases(List allDatabases) { // Trova la credenziale per determinare il tipo di database var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); if (credential == null) { Logger.LogWarning("Credenziale non trovata per filtraggio database di sistema"); return allDatabases; // Restituisce tutti se non riesce a determinare il tipo } var databaseType = credential.DatabaseType; // Filtri per SQL Server if (databaseType == DatabaseType.SqlServer) { var sqlServerSystemDatabases = new HashSet(StringComparer.OrdinalIgnoreCase) { "master", "tempdb", "model", "msdb", "Resource", "mssqlsystemresource", "ReportServer", "ReportServerTempDB", "SSISDB", "distribution" }; return allDatabases.Where(db => !sqlServerSystemDatabases.Contains(db)); } // TODO: Implementare filtri per altri tipi di database if (databaseType == DatabaseType.MySql) { Logger.LogInformation("Filtro database di sistema MySQL - DA IMPLEMENTARE"); return allDatabases; // Per ora restituisce tutti } if (databaseType == DatabaseType.PostgreSql) { Logger.LogInformation("Filtro database di sistema PostgreSQL - DA IMPLEMENTARE"); return allDatabases; // Per ora restituisce tutti } if (databaseType == DatabaseType.Oracle) { Logger.LogInformation("Filtro database di sistema Oracle - DA IMPLEMENTARE"); return allDatabases; // Per ora restituisce tutti } Logger.LogWarning("Tipo database non riconosciuto per filtraggio: {DatabaseType}", databaseType); return allDatabases; // Restituisce tutti per tipi non riconosciuti } // Metodo per gestire la selezione di un database dal modale private async Task ConnectToDatabaseWithSpecificDatabase(string databaseName) { try { if (currentDatabaseManager == null) { databaseErrorMessage = "Database manager non disponibile"; return; } Logger.LogInformation("Cambiando database a: {DatabaseName}", databaseName); // Usa il metodo dell'interfaccia per cambiare database await currentDatabaseManager.ChangeDatabaseAsync(databaseName); Logger.LogInformation("Database cambiato con successo a: {DatabaseName}", databaseName); // Carica le tabelle dal database selezionato await LoadTablesFromConnectedDatabase(); isDatabaseConnected = true; Logger.LogInformation("Connessione completata per database: {DatabaseName}", databaseName); } catch (Exception ex) { Logger.LogError(ex, "Errore nella connessione al database specifico: {DatabaseName}", databaseName); databaseErrorMessage = $"Errore nella connessione al database {databaseName}: {ex.Message}"; throw; } } }