Files
Data-Coupler/Data_Coupler/Pages/DataCoupler.razor.cs
T
Alessio Dal Santo 77efe986a0 feat: Corregge la logica di rilevamento database specificato nella connection string
- Modifica IsDatabaseSpecifiedInConnectionString per verificare prima il campo DatabaseName della credenziale
- Aggiunge logging dettagliato per debugging del processo di connessione database
- Corregge il flusso di connessione per evitare il modale quando il database è già specificato
- Migliora la gestione degli errori nel caricamento tabelle dal database specificato
- Rimuove codice non raggiungibile nella logica di connessione database

Il bug precedente mostrava sempre il modale di selezione database anche quando
il database era specificato nel campo DatabaseName della credenziale, ora la
verifica segue la logica corretta:
1. Controlla se DatabaseName è valorizzato nella credenziale
2. Solo se vuoto, verifica i parametri Database=/Initial Catalog= nella connection string
2025-07-02 15:32:11 +02:00

2712 lines
106 KiB
C#

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<DataCoupler> 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<string, object> RecordData { get; set; } = new();
}
// Stato delle credenziali
private List<DatabaseCredential> databaseCredentials = new();
private List<RestApiCredential> 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<string> availableTableNames = new(); // Solo nomi delle tabelle
private Dictionary<string, IEnumerable<DbColumnInfo>> 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<string> availableDatabases = new();
private string selectedDatabase = "";
private bool showDatabaseSelectionModal = false;
private bool isLoadingDatabases = false;
// Database selection (schemas only)
private List<string> 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<Dictionary<string, object>> queryPreviewData = new();
private List<string> queryColumns = new();
private bool showQueryPreview = false;
private bool isLoadingPreview = false; // File handling
private string selectedFileName = "";
private bool isProcessingFile = false;
private string fileErrorMessage = "";
private Dictionary<string, IEnumerable<string>> fileSheets = new(); // SheetName -> Columns
private Dictionary<string, List<Dictionary<string, object>>> 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<RestEntitySummary> restEntities = new();
private RestEntitySummary? selectedRestEntity = null;
private RestEntityInfo? restEntityDetails = null;
private string restSearchTerm = "";
// Mapping campi
private Dictionary<string, string> fieldMappings = new(); // DbColumn -> RestProperty
private HashSet<string> 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<TransferResult> transferResults = new();
private bool showDetailedResults = false;
// Servizi
private IDatabaseManager? currentDatabaseManager = null;
private IRestMetadataDiscovery? currentRestDiscovery = null;
private IRestServiceClient? currentRestClient = null;
// Gestione Profili
private List<DataCouplerProfile> 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<FieldMappingDto> GetCurrentFieldMappings()
{
var mappings = new List<FieldMappingDto>();
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<Dictionary<string, object>>();
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<string, object>();
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<string> ParseCsvLine(string line, char separator = ',')
{
var result = new List<string>();
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<string>();
var dataRows = new List<Dictionary<string, object>>();
// 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<string, object>();
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<Dictionary<string, object>> GetCurrentPageData()
{
if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet))
return new List<Dictionary<string, object>>();
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<string> GetFilteredDatabaseTables()
{
if (string.IsNullOrEmpty(databaseSearchTerm))
return availableTableNames;
return availableTableNames.Where(table =>
table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase));
}
private IEnumerable<RestEntitySummary> 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<string> sourceColumns = new List<string>();
// 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<string>();
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<string>();
int recordNumber = 1;
foreach (var record in records)
{
var transferResult = new TransferResult
{
RecordNumber = recordNumber,
RecordData = new Dictionary<string, object>(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<string>();
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<string>();
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<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromSource()
{
if (selectedSourceType == "database")
{
return await GetAllRecordsFromDatabase();
}
else if (selectedSourceType == "file")
{
return await GetAllRecordsFromFile();
}
return new List<Dictionary<string, object>>();
}
private async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromDatabase()
{
if (currentDatabaseManager == null)
return new List<Dictionary<string, object>>();
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<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromFile()
{
if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet))
{
return new List<Dictionary<string, object>>();
}
await Task.CompletedTask;
return fileData[selectedSheet];
}
private Dictionary<string, object> TransformRecordToRestEntity(Dictionary<string, object> dbRecord)
{
var restData = new Dictionary<string, object>();
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<char, int>();
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
}
/// <summary>
/// Verifica se il pulsante di trasferimento può essere abilitato
/// </summary>
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"
};
}
/// <summary>
/// Genera una chiave univoca per il record sorgente
/// </summary>
private string GenerateSourceKey(Dictionary<string, object> 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;
}
}
/// <summary>
/// Gestisce la connessione al database con schema specifico
/// </summary>
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();
}
}
/// <summary>
/// Scopre automaticamente lo schema del database
/// </summary>
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<string, IEnumerable<DbColumnInfo>> ??
(schema != null ? new Dictionary<string, IEnumerable<DbColumnInfo>>(schema) : new Dictionary<string, IEnumerable<DbColumnInfo>>());
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;
}
}
/// <summary>
/// Carica lo schema per un database specifico
/// </summary>
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<string, IEnumerable<DbColumnInfo>> ??
new Dictionary<string, IEnumerable<DbColumnInfo>>();
// 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;
}
}
/// <summary>
/// Estrae lo schema dal nome completo di una tabella
/// </summary>
private string? ExtractSchemaFromTableName(string fullTableName)
{
if (string.IsNullOrEmpty(fullTableName) || !fullTableName.Contains('.'))
return null;
var parts = fullTableName.Split('.');
return parts.Length > 1 ? parts[0] : null;
}
/// <summary>
/// Ottiene lo schema correntemente utilizzato dal database connesso
/// </summary>
private string? GetCurrentDatabaseSchema()
{
if (!databaseTables.Any())
return null;
var firstTable = databaseTables.Keys.FirstOrDefault();
return !string.IsNullOrEmpty(firstTable) ? ExtractSchemaFromTableName(firstTable) : null;
}
/// <summary>
/// Ottiene il campo ID dell'entità REST selezionata
/// </summary>
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";
}
/// <summary>
/// Verifica se una query è una SELECT query sicura
/// </summary>
private bool IsSelectQuery(string query)
{
if (string.IsNullOrWhiteSpace(query))
return false;
var trimmedQuery = query.Trim();
return trimmedQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Pulisce una query SQL rimuovendo caratteri pericolosi
/// </summary>
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;
}
/// <summary>
/// Gestisce il cambio di modalità tra tabelle e query custom
/// </summary>
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();
}
/// <summary>
/// Valida la query SQL custom
/// </summary>
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();
}
}
/// <summary>
/// Carica un'anteprima dei dati della query
/// </summary>
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();
}
}
/// <summary>
/// Crea una query limitata in base al tipo di database
/// </summary>
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
};
}
/// <summary>
/// Nasconde l'anteprima della query
/// </summary>
private void HideQueryPreview()
{
showQueryPreview = false;
StateHasChanged();
}
/// <summary>
/// Ottiene l'ID della credenziale sorgente corrente
/// </summary>
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;
}
/// <summary>
/// Ottiene l'ID della credenziale destinazione corrente
/// </summary>
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;
}
/// <summary>
/// Annulla la selezione dello schema
/// </summary>
private void CancelSchemaSelection()
{
showSchemaSelectionModal = false;
selectedSchema = "";
StateHasChanged();
}
/// <summary>
/// Ottiene le colonne di una tabella specifica
/// </summary>
private async Task<List<DbColumnInfo>> GetTableColumns(string fullTableName, string databaseName, string tableName)
{
var columns = new List<DbColumnInfo>();
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;
}
/// <summary>
/// Conferma la selezione dello schema
/// </summary>
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();
}
/// <summary>
/// Carica la lista degli schemi disponibili
/// </summary>
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;
}
}
/// <summary>
/// Prova a caricare gli schemi con query SQL dirette
/// </summary>
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<string>();
}
}
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<bool> 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<string> FilterSystemDatabases(List<string> 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<string>(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;
}
}
}