Compare commits
5 Commits
f270a4a434
...
3a1c8da3cd
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a1c8da3cd | |||
| 791f2cdc1f | |||
| d25d7cfd6d | |||
| 9e48666306 | |||
| 8a8ccec170 |
@@ -67,6 +67,19 @@ public partial class DataCoupler : ComponentBase
|
|||||||
|
|
||||||
// ===== METODI DATABASE =====
|
// ===== METODI DATABASE =====
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica se la credenziale database selezionata è di tipo ODBC
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True se la credenziale è ODBC, altrimenti False</returns>
|
||||||
|
protected bool IsOdbcConnection()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(selectedDatabaseCredential))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
|
||||||
|
return credential?.DatabaseType == DatabaseType.Odbc;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gestisce il cambio di credenziale database selezionata
|
/// Gestisce il cambio di credenziale database selezionata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -74,6 +87,12 @@ public partial class DataCoupler : ComponentBase
|
|||||||
{
|
{
|
||||||
selectedDatabaseCredential = e.Value?.ToString() ?? "";
|
selectedDatabaseCredential = e.Value?.ToString() ?? "";
|
||||||
ResetDatabaseState();
|
ResetDatabaseState();
|
||||||
|
|
||||||
|
// Se è una connessione ODBC, forza l'uso di query custom
|
||||||
|
if (IsOdbcConnection())
|
||||||
|
{
|
||||||
|
useCustomQuery = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -571,14 +590,15 @@ public partial class DataCoupler : ComponentBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected async Task ValidateCustomQuery()
|
protected async Task ValidateCustomQuery()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null)
|
if (string.IsNullOrWhiteSpace(customQuery))
|
||||||
{
|
{
|
||||||
isQueryValid = false;
|
isQueryValid = false;
|
||||||
queryValidationMessage = "Query vuota o manager database non disponibile";
|
queryValidationMessage = "Query vuota";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidatingQuery = true;
|
isValidatingQuery = true;
|
||||||
|
IDatabaseManager? tempManager = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -601,13 +621,30 @@ public partial class DataCoupler : ComponentBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per ODBC, crea un database manager temporaneo se non esiste
|
||||||
|
var managerToUse = currentDatabaseManager;
|
||||||
|
if (managerToUse == null && IsOdbcConnection())
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Creando database manager temporaneo per validazione query ODBC");
|
||||||
|
tempManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential);
|
||||||
|
managerToUse = tempManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se ancora non abbiamo un manager, errore
|
||||||
|
if (managerToUse == null)
|
||||||
|
{
|
||||||
|
isQueryValid = false;
|
||||||
|
queryValidationMessage = "Manager database non disponibile. Connettersi prima di validare la query.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Crea una query di test con sintassi appropriata per il tipo di database
|
// Crea una query di test con sintassi appropriata per il tipo di database
|
||||||
var testQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 1);
|
var testQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 1);
|
||||||
|
|
||||||
Logger.LogInformation("Validando query: {Query}", testQuery);
|
Logger.LogInformation("Validando query: {Query}", testQuery);
|
||||||
|
|
||||||
// Prova a eseguire la query per validarla
|
// Prova a eseguire la query per validarla
|
||||||
var testResults = await currentDatabaseManager.ExecuteRawQueryAsync(testQuery);
|
var testResults = await managerToUse.ExecuteRawQueryAsync(testQuery);
|
||||||
|
|
||||||
if (testResults != null && testResults.Any())
|
if (testResults != null && testResults.Any())
|
||||||
{
|
{
|
||||||
@@ -623,6 +660,13 @@ public partial class DataCoupler : ComponentBase
|
|||||||
TryAutoSelectKeyForQuery(queryColumns);
|
TryAutoSelectKeyForQuery(queryColumns);
|
||||||
|
|
||||||
Logger.LogInformation("Query validata con successo: {ColumnCount} colonne", queryColumns.Count);
|
Logger.LogInformation("Query validata con successo: {ColumnCount} colonne", queryColumns.Count);
|
||||||
|
|
||||||
|
// Per ODBC, salva il manager se non era già presente
|
||||||
|
if (IsOdbcConnection() && currentDatabaseManager == null && tempManager != null)
|
||||||
|
{
|
||||||
|
currentDatabaseManager = tempManager;
|
||||||
|
tempManager = null; // Non distruggerlo nel finally
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -639,6 +683,13 @@ public partial class DataCoupler : ComponentBase
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
isValidatingQuery = false;
|
isValidatingQuery = false;
|
||||||
|
|
||||||
|
// Pulisci il manager temporaneo se non è stato salvato
|
||||||
|
if (tempManager != null)
|
||||||
|
{
|
||||||
|
try { tempManager.Dispose(); } catch { /* Ignora errori di dispose */ }
|
||||||
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,18 @@
|
|||||||
|
|
||||||
@if (!string.IsNullOrEmpty(selectedDatabaseCredential))
|
@if (!string.IsNullOrEmpty(selectedDatabaseCredential))
|
||||||
{
|
{
|
||||||
|
<!-- Per ODBC: mostra messaggio esplicativo, niente discovery -->
|
||||||
|
@if (IsOdbcConnection())
|
||||||
|
{
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
<i class="oi oi-info"></i> <strong>Connessione ODBC rilevata</strong><br>
|
||||||
|
Per le connessioni ODBC, il discovery automatico delle tabelle non è disponibile.<br>
|
||||||
|
Procedi direttamente con l'inserimento di una <strong>query SQL custom</strong> nella sezione sottostante.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<!-- Per database standard: mostra pulsante di connessione -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<button class="btn btn-success btn-sm" @onclick="ConnectToDatabase" disabled="@isConnectingDatabase">
|
<button class="btn btn-success btn-sm" @onclick="ConnectToDatabase" disabled="@isConnectingDatabase">
|
||||||
@if (isConnectingDatabase)
|
@if (isConnectingDatabase)
|
||||||
@@ -83,6 +95,7 @@
|
|||||||
<span class="badge bg-success ms-2">Connesso</span>
|
<span class="badge bg-success ms-2">Connesso</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
} @if (!string.IsNullOrEmpty(databaseErrorMessage))
|
} @if (!string.IsNullOrEmpty(databaseErrorMessage))
|
||||||
{
|
{
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
@@ -90,8 +103,126 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Lista Tabelle -->
|
<!-- Per ODBC: mostra direttamente la sezione Query Custom -->
|
||||||
@if (isDatabaseConnected)
|
@if (IsOdbcConnection())
|
||||||
|
{
|
||||||
|
<!-- Sezione Query Custom per ODBC -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<h6>Query SQL Custom:</h6>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Scrivi la tua query SELECT:</label>
|
||||||
|
<textarea class="form-control" rows="6" placeholder="SELECT * FROM your_table WHERE condition..."
|
||||||
|
@bind="customQuery" @bind:event="oninput"></textarea>
|
||||||
|
<div class="mt-2">
|
||||||
|
<div class="alert alert-warning d-flex align-items-start" role="alert">
|
||||||
|
<i class="fas fa-shield-alt me-2 mt-1"></i>
|
||||||
|
<div>
|
||||||
|
<strong>Controlli di Sicurezza Attivi:</strong><br>
|
||||||
|
<small>
|
||||||
|
• Solo query <strong>SELECT</strong> sono permesse<br>
|
||||||
|
• Operazioni come INSERT, UPDATE, DELETE, DROP sono bloccate<br>
|
||||||
|
• Query multiple separate da ; non sono consentite<br>
|
||||||
|
• La query verrà automaticamente ottimizzata per il trasferimento dati
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<button class="btn btn-primary btn-sm me-2" @onclick="ValidateCustomQuery"
|
||||||
|
disabled="@(isValidatingQuery || string.IsNullOrWhiteSpace(customQuery))">
|
||||||
|
@if (isValidatingQuery)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
||||||
|
}
|
||||||
|
<i class="fas fa-check-circle"></i> Valida Query
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@if (isQueryValid)
|
||||||
|
{
|
||||||
|
<button class="btn btn-info btn-sm me-2" @onclick="LoadQueryPreview"
|
||||||
|
disabled="@isLoadingPreview">
|
||||||
|
@if (isLoadingPreview)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
||||||
|
}
|
||||||
|
<i class="fas fa-eye"></i> Anteprima Risultati
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@if (showQueryPreview)
|
||||||
|
{
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" @onclick="HideQueryPreview">
|
||||||
|
<i class="fas fa-eye-slash"></i> Nascondi Anteprima
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(queryValidationMessage))
|
||||||
|
{
|
||||||
|
@if (isQueryValid)
|
||||||
|
{
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
<i class="fas fa-check-circle"></i>
|
||||||
|
@queryValidationMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
@queryValidationMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Anteprima risultati query -->
|
||||||
|
@if (showQueryPreview && queryPreviewData.Any())
|
||||||
|
{
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
<i class="fas fa-table"></i> Anteprima Risultati Query
|
||||||
|
<span class="badge bg-info ms-2">@queryPreviewData.Count righe</span>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive" style="max-height: 400px;">
|
||||||
|
<table class="table table-striped table-hover mb-0">
|
||||||
|
<thead class="table-dark sticky-top">
|
||||||
|
<tr>
|
||||||
|
@if (queryColumns.Any())
|
||||||
|
{
|
||||||
|
@foreach (var col in queryColumns)
|
||||||
|
{
|
||||||
|
<th>@col</th>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var row in queryPreviewData)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
@foreach (var col in queryColumns)
|
||||||
|
{
|
||||||
|
<td>@row.GetValueOrDefault(col)?.ToString()</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Lista Tabelle (solo per database NON ODBC) -->
|
||||||
|
@if (isDatabaseConnected && !IsOdbcConnection())
|
||||||
{
|
{
|
||||||
<!-- Selezione modalità: Tabelle o Query Custom -->
|
<!-- Selezione modalità: Tabelle o Query Custom -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -681,8 +812,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div> <!-- Sezione Mapping (quando la fonte è selezionata e REST è connesso) -->
|
</div> <!-- Sezione Mapping (quando la fonte è selezionata e REST è connesso) -->
|
||||||
@{
|
@{
|
||||||
var isSourceReady = (selectedSourceType == "database" && isDatabaseConnected &&
|
// Per ODBC: non richiede isDatabaseConnected, basta query validata
|
||||||
((useCustomQuery && isQueryValid) || (!useCustomQuery && !string.IsNullOrEmpty(selectedTable)))) ||
|
// Per altri database: richiede connessione + (query validata OR tabella selezionata)
|
||||||
|
var isSourceReady = (selectedSourceType == "database" &&
|
||||||
|
((IsOdbcConnection() && useCustomQuery && isQueryValid) ||
|
||||||
|
(!IsOdbcConnection() && isDatabaseConnected && ((useCustomQuery && isQueryValid) || (!useCustomQuery && !string.IsNullOrEmpty(selectedTable)))))) ||
|
||||||
(selectedSourceType == "file" && !string.IsNullOrEmpty(selectedSheet));
|
(selectedSourceType == "file" && !string.IsNullOrEmpty(selectedSheet));
|
||||||
}
|
}
|
||||||
@if (isSourceReady && isRestConnected && selectedRestEntity != null)
|
@if (isSourceReady && isRestConnected && selectedRestEntity != null)
|
||||||
|
|||||||
@@ -0,0 +1,352 @@
|
|||||||
|
# Implementazione ODBC Query Custom Only
|
||||||
|
|
||||||
|
## 📋 Panoramica
|
||||||
|
|
||||||
|
Data la natura generica dei driver ODBC e le limitazioni del discovery automatico delle tabelle, è stato implementato un comportamento speciale per le connessioni ODBC nel DataCoupler: **le connessioni ODBC utilizzano esclusivamente query SQL custom**, bypassando completamente il sistema di discovery delle tabelle.
|
||||||
|
|
||||||
|
## 🎯 Motivazione
|
||||||
|
|
||||||
|
I driver ODBC sono estremamente eterogenei e spesso:
|
||||||
|
- Non supportano query standard di discovery delle tabelle
|
||||||
|
- Hanno sintassi SQL non standardizzate
|
||||||
|
- Richiedono permessi specifici per accedere ai metadati del database
|
||||||
|
- Possono avere limitazioni sulla lettura dello schema
|
||||||
|
|
||||||
|
Per questi motivi, è più sicuro e affidabile richiedere all'utente di specificare direttamente la query SQL da eseguire.
|
||||||
|
|
||||||
|
## 🔧 Modifiche Implementate
|
||||||
|
|
||||||
|
### 1. **DatabaseMethod.cs**
|
||||||
|
|
||||||
|
#### Nuovo Metodo Helper: `IsOdbcConnection()`
|
||||||
|
```csharp
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica se la credenziale database selezionata è di tipo ODBC
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True se la credenziale è ODBC, altrimenti False</returns>
|
||||||
|
protected bool IsOdbcConnection()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(selectedDatabaseCredential))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
|
||||||
|
return credential?.DatabaseType == DatabaseType.Odbc;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Funzionalità:**
|
||||||
|
- Verifica rapidamente se la credenziale corrente è ODBC
|
||||||
|
- Utilizzato in tutta l'UI per condizionare la visualizzazione degli elementi
|
||||||
|
|
||||||
|
#### Modificato: `OnDatabaseCredentialChanged()`
|
||||||
|
```csharp
|
||||||
|
protected void OnDatabaseCredentialChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
selectedDatabaseCredential = e.Value?.ToString() ?? "";
|
||||||
|
ResetDatabaseState();
|
||||||
|
|
||||||
|
// Se è una connessione ODBC, forza l'uso di query custom
|
||||||
|
if (IsOdbcConnection())
|
||||||
|
{
|
||||||
|
useCustomQuery = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Comportamento:**
|
||||||
|
- Quando l'utente seleziona una credenziale ODBC, `useCustomQuery` viene automaticamente impostato a `true`
|
||||||
|
- Questo forza l'applicazione a mostrare solo la sezione query custom
|
||||||
|
|
||||||
|
#### Modificato: `ValidateCustomQuery()`
|
||||||
|
|
||||||
|
**Problema originale:** Il metodo richiedeva `currentDatabaseManager` già creato, ma per ODBC non si fa connessione preliminare.
|
||||||
|
|
||||||
|
**Soluzione implementata:**
|
||||||
|
```csharp
|
||||||
|
protected async Task ValidateCustomQuery()
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
IDatabaseManager? tempManager = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Per ODBC, crea un database manager temporaneo se non esiste
|
||||||
|
var managerToUse = currentDatabaseManager;
|
||||||
|
if (managerToUse == null && IsOdbcConnection())
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Creando database manager temporaneo per validazione query ODBC");
|
||||||
|
tempManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential);
|
||||||
|
managerToUse = tempManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valida la query con il manager
|
||||||
|
var testResults = await managerToUse.ExecuteRawQueryAsync(testQuery);
|
||||||
|
|
||||||
|
// Se validazione OK, salva il manager per ODBC
|
||||||
|
if (IsOdbcConnection() && currentDatabaseManager == null && tempManager != null)
|
||||||
|
{
|
||||||
|
currentDatabaseManager = tempManager;
|
||||||
|
tempManager = null; // Non distruggerlo nel finally
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Pulisci il manager temporaneo se non è stato salvato
|
||||||
|
if (tempManager != null)
|
||||||
|
{
|
||||||
|
try { tempManager.Dispose(); } catch { /* Ignora errori di dispose */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Funzionalità:**
|
||||||
|
- Crea temporaneamente un `OdbcDatabaseManager` se non esiste
|
||||||
|
- Usa questo manager per testare la query
|
||||||
|
- Se la validazione ha successo, salva il manager in `currentDatabaseManager` per riutilizzarlo
|
||||||
|
- Gestisce correttamente il dispose del manager temporaneo in caso di errore
|
||||||
|
|
||||||
|
### 2. **DataCoupler.razor**
|
||||||
|
|
||||||
|
#### Modificata: Sezione Pulsante Connessione
|
||||||
|
|
||||||
|
**Prima:**
|
||||||
|
```razor
|
||||||
|
@if (!string.IsNullOrEmpty(selectedDatabaseCredential))
|
||||||
|
{
|
||||||
|
<div class="mb-3">
|
||||||
|
<button class="btn btn-success btn-sm" @onclick="ConnectToDatabase">
|
||||||
|
<i class="fas fa-plug"></i> Connetti e Scopri Schema
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dopo:**
|
||||||
|
```razor
|
||||||
|
@if (!string.IsNullOrEmpty(selectedDatabaseCredential))
|
||||||
|
{
|
||||||
|
<!-- Per ODBC: mostra messaggio esplicativo, niente discovery -->
|
||||||
|
@if (IsOdbcConnection())
|
||||||
|
{
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
<i class="oi oi-info"></i> <strong>Connessione ODBC rilevata</strong><br>
|
||||||
|
Per le connessioni ODBC, il discovery automatico delle tabelle non è disponibile.<br>
|
||||||
|
Procedi direttamente con l'inserimento di una <strong>query SQL custom</strong> nella sezione sottostante.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<!-- Per database standard: mostra pulsante di connessione -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<button class="btn btn-success btn-sm" @onclick="ConnectToDatabase">
|
||||||
|
<i class="fas fa-plug"></i> Connetti e Scopri Schema
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Funzionalità:**
|
||||||
|
- Per ODBC: mostra un messaggio informativo che spiega la situazione
|
||||||
|
- Per altri database: mostra il pulsante di connessione standard
|
||||||
|
- L'utente comprende immediatamente che deve usare query custom
|
||||||
|
|
||||||
|
#### Aggiunta: Sezione Query Custom per ODBC (sempre visibile)
|
||||||
|
|
||||||
|
```razor
|
||||||
|
<!-- Per ODBC: mostra direttamente la sezione Query Custom -->
|
||||||
|
@if (IsOdbcConnection())
|
||||||
|
{
|
||||||
|
<!-- Sezione Query Custom per ODBC -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<h6>Query SQL Custom:</h6>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Scrivi la tua query SELECT:</label>
|
||||||
|
<textarea class="form-control" rows="6"
|
||||||
|
placeholder="SELECT * FROM your_table WHERE condition..."
|
||||||
|
@bind="customQuery" @bind:event="oninput"></textarea>
|
||||||
|
<!-- Alert sicurezza -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<button class="btn btn-primary btn-sm me-2" @onclick="ValidateCustomQuery">
|
||||||
|
<i class="fas fa-check-circle"></i> Valida Query
|
||||||
|
</button>
|
||||||
|
<!-- Altri pulsanti preview, ecc. -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Funzionalità:**
|
||||||
|
- Sezione query custom **sempre visibile** quando si seleziona ODBC
|
||||||
|
- Non richiede connessione preliminare
|
||||||
|
- Include tutti i controlli per validazione, preview, ecc.
|
||||||
|
|
||||||
|
#### Modificata: Condizione Lista Tabelle
|
||||||
|
|
||||||
|
**Prima:**
|
||||||
|
```razor
|
||||||
|
@if (isDatabaseConnected)
|
||||||
|
{
|
||||||
|
<!-- Lista tabelle e query custom switch -->
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dopo:**
|
||||||
|
```razor
|
||||||
|
<!-- Lista Tabelle (solo per database NON ODBC) -->
|
||||||
|
@if (isDatabaseConnected && !IsOdbcConnection())
|
||||||
|
{
|
||||||
|
<!-- Selezione modalità: Tabelle o Query Custom -->
|
||||||
|
<!-- Lista tabelle -->
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Funzionalità:**
|
||||||
|
- La sezione lista tabelle **non viene mai mostrata** per ODBC
|
||||||
|
- Anche se `isDatabaseConnected` è `true` (non dovrebbe mai succedere per ODBC), la sezione resta nascosta
|
||||||
|
|
||||||
|
## 🔄 Flusso Utente ODBC
|
||||||
|
|
||||||
|
### Prima dell'implementazione:
|
||||||
|
1. Seleziona credenziale ODBC
|
||||||
|
2. Clicca "Connetti e Scopri Schema"
|
||||||
|
3. **Errore**: discovery tabelle fallisce
|
||||||
|
4. User frustrato, deve capire come fare
|
||||||
|
|
||||||
|
### Dopo l'implementazione:
|
||||||
|
1. ✅ Seleziona credenziale ODBC
|
||||||
|
2. ✅ Vede immediatamente messaggio informativo
|
||||||
|
3. ✅ Vede la sezione query custom già pronta
|
||||||
|
4. ✅ Scrive la query SQL
|
||||||
|
5. ✅ Clicca "Valida Query" (crea automaticamente `OdbcDatabaseManager`)
|
||||||
|
6. ✅ Vede preview dei dati
|
||||||
|
7. ✅ Procede con il mapping
|
||||||
|
|
||||||
|
**Nessun pulsante di connessione, nessun discovery, solo query diretta.**
|
||||||
|
|
||||||
|
## 🎨 Esperienza Utente
|
||||||
|
|
||||||
|
### Per Database Standard (SQL Server, MySQL, ecc.)
|
||||||
|
- **Mostra:** Pulsante "Connetti e Scopri Schema"
|
||||||
|
- **Discovery:** Automatico con lista tabelle
|
||||||
|
- **Query Custom:** Opzionale, via switch
|
||||||
|
|
||||||
|
### Per Database ODBC
|
||||||
|
- **Mostra:** Messaggio informativo + textarea query
|
||||||
|
- **Discovery:** Disabilitato completamente
|
||||||
|
- **Query Custom:** Obbligatoria, sempre visibile
|
||||||
|
|
||||||
|
## 📊 Vantaggi dell'Implementazione
|
||||||
|
|
||||||
|
### 1. **Affidabilità**
|
||||||
|
- Nessun rischio di errori nel discovery delle tabelle ODBC
|
||||||
|
- L'utente ha il controllo completo della query SQL
|
||||||
|
|
||||||
|
### 2. **Semplicità**
|
||||||
|
- Flusso chiaro: seleziona ODBC → scrivi query → valida → preview
|
||||||
|
- Nessun passo intermedio confusionario
|
||||||
|
|
||||||
|
### 3. **Performance**
|
||||||
|
- Nessun tentativo di discovery che può essere lento o fallire
|
||||||
|
- Connessione ODBC creata solo quando serve (alla validazione)
|
||||||
|
|
||||||
|
### 4. **Flessibilità**
|
||||||
|
- L'utente può scrivere qualsiasi query SELECT
|
||||||
|
- Supporta JOIN, WHERE, GROUP BY, ecc.
|
||||||
|
- Nessuna limitazione del discovery automatico
|
||||||
|
|
||||||
|
## 🔒 Sicurezza
|
||||||
|
|
||||||
|
Tutti i controlli di sicurezza esistenti restano attivi:
|
||||||
|
|
||||||
|
- ✅ Solo query `SELECT` permesse
|
||||||
|
- ✅ Query multiple (separate da `;`) bloccate
|
||||||
|
- ✅ Operazioni `INSERT`, `UPDATE`, `DELETE`, `DROP` bloccate
|
||||||
|
- ✅ Query pulita da caratteri pericolosi
|
||||||
|
|
||||||
|
## 🧪 Test Manuali Suggeriti
|
||||||
|
|
||||||
|
### Test 1: Selezione Credenziale ODBC
|
||||||
|
1. Vai a DataCoupler
|
||||||
|
2. Seleziona sorgente Database
|
||||||
|
3. Seleziona una credenziale ODBC
|
||||||
|
4. **Verifica:**
|
||||||
|
- ✅ Nessun pulsante "Connetti e Scopri Schema"
|
||||||
|
- ✅ Messaggio informativo visibile
|
||||||
|
- ✅ Sezione query custom visibile
|
||||||
|
- ✅ Textarea query pronta per input
|
||||||
|
|
||||||
|
### Test 2: Validazione Query ODBC
|
||||||
|
1. Seleziona credenziale ODBC
|
||||||
|
2. Scrivi query: `SELECT * FROM MyTable`
|
||||||
|
3. Clicca "Valida Query"
|
||||||
|
4. **Verifica:**
|
||||||
|
- ✅ Creazione automatica `OdbcDatabaseManager`
|
||||||
|
- ✅ Query eseguita con successo
|
||||||
|
- ✅ Colonne rilevate mostrate
|
||||||
|
- ✅ Messaggio "Query valida - N colonne rilevate"
|
||||||
|
|
||||||
|
### Test 3: Preview Dati ODBC
|
||||||
|
1. Dopo validazione query (Test 2)
|
||||||
|
2. Clicca "Anteprima Risultati"
|
||||||
|
3. **Verifica:**
|
||||||
|
- ✅ Preview tabella con 10 righe
|
||||||
|
- ✅ Colonne corrette
|
||||||
|
- ✅ Dati visualizzati correttamente
|
||||||
|
|
||||||
|
### Test 4: Mapping e Trasferimento ODBC
|
||||||
|
1. Dopo validazione e preview (Test 2-3)
|
||||||
|
2. Procedi con configurazione destinazione
|
||||||
|
3. Crea mapping campi
|
||||||
|
4. Esegui trasferimento
|
||||||
|
5. **Verifica:**
|
||||||
|
- ✅ Trasferimento dati completato
|
||||||
|
- ✅ Record copiati correttamente
|
||||||
|
|
||||||
|
### Test 5: Confronto con Database Standard
|
||||||
|
1. Seleziona credenziale SQL Server
|
||||||
|
2. **Verifica:**
|
||||||
|
- ✅ Pulsante "Connetti e Scopri Schema" visibile
|
||||||
|
- ✅ Discovery tabelle funziona
|
||||||
|
- ✅ Switch query custom disponibile
|
||||||
|
- ✅ Nessun messaggio ODBC
|
||||||
|
|
||||||
|
## 📝 Note Tecniche
|
||||||
|
|
||||||
|
### Manager ODBC Temporaneo
|
||||||
|
- Creato **on-demand** durante la validazione query
|
||||||
|
- Salvato in `currentDatabaseManager` se validazione OK
|
||||||
|
- Riutilizzato per preview e trasferimento dati
|
||||||
|
- Disposto correttamente in caso di errore
|
||||||
|
|
||||||
|
### Compatibilità con Profili Esistenti
|
||||||
|
- Profili ODBC con query custom salvate continuano a funzionare
|
||||||
|
- Al caricamento profilo, se ODBC + query custom → valida automaticamente
|
||||||
|
- Nessuna breaking change per profili esistenti
|
||||||
|
|
||||||
|
### Dipendenze
|
||||||
|
- `OdbcDatabaseManager` (già implementato)
|
||||||
|
- `DataConnectionFactory` con supporto ODBC (già implementato)
|
||||||
|
- `DatabaseType.Odbc` enum (già implementato)
|
||||||
|
|
||||||
|
## 🚀 Future Improvements
|
||||||
|
|
||||||
|
Possibili miglioramenti futuri (non implementati ora):
|
||||||
|
|
||||||
|
1. **Syntax Highlighting** per query SQL nella textarea
|
||||||
|
2. **Query Templates** predefiniti per ODBC comuni (SAP HANA, DB2, ecc.)
|
||||||
|
3. **Salvataggio Query Recenti** per riutilizzo rapido
|
||||||
|
4. **Auto-complete Tabelle** (se driver ODBC lo supporta)
|
||||||
|
5. **Explain Plan** per query complesse
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Versione**: 2.2.0
|
||||||
|
**Data Implementazione**: 2 Febbraio 2026
|
||||||
|
**Commit**: `8a8ccec`
|
||||||
|
**Branch**: `development`
|
||||||
|
**Sviluppatore**: Alessio Dalsanto
|
||||||
Reference in New Issue
Block a user