feat: ottimizza interfaccia query personalizzate rimuovendo messaggi verbosi
- Rimossi messaggi di successo per validazione query per ridurre ingombro visivo - Eliminati alert informativi delle colonne rilevate dalla query - Rimossa notificazione "Query validata!" nella sezione mapping - Mantenuti solo i messaggi di errore quando necessario - Migliorata UX con interfaccia più pulita e focalizzata sull'essenziale - Funzionalità di estrazione colonne e mapping completamente preservata
This commit is contained in:
@@ -19,7 +19,7 @@ namespace DataConnection.EF;
|
|||||||
public class EFCoreDatabaseManager : IDatabaseManager
|
public class EFCoreDatabaseManager : IDatabaseManager
|
||||||
{
|
{
|
||||||
private readonly DbManagerOptions _options;
|
private readonly DbManagerOptions _options;
|
||||||
private ExistingDatabaseContext _context;
|
private ExistingDatabaseContext _context = null!;
|
||||||
|
|
||||||
public EFCoreDatabaseManager(DbManagerOptions options)
|
public EFCoreDatabaseManager(DbManagerOptions options)
|
||||||
{
|
{
|
||||||
@@ -54,8 +54,8 @@ public class EFCoreDatabaseManager : IDatabaseManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<T>> GetAsync<T>(
|
public async Task<IEnumerable<T>> GetAsync<T>(
|
||||||
Expression<Func<T, bool>> filter = null,
|
Expression<Func<T, bool>>? filter = null,
|
||||||
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
|
Func<IQueryable<T>, IOrderedQueryable<T>>? orderBy = null,
|
||||||
string includeProperties = "",
|
string includeProperties = "",
|
||||||
int? skip = null,
|
int? skip = null,
|
||||||
int? take = null) where T : class
|
int? take = null) where T : class
|
||||||
@@ -91,7 +91,7 @@ public class EFCoreDatabaseManager : IDatabaseManager
|
|||||||
return await query.ToListAsync();
|
return await query.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<T> GetByIdAsync<T>(object id) where T : class
|
public async Task<T?> GetByIdAsync<T>(object id) where T : class
|
||||||
{
|
{
|
||||||
return await _context.Set<T>().FindAsync(id);
|
return await _context.Set<T>().FindAsync(id);
|
||||||
}
|
}
|
||||||
@@ -101,6 +101,43 @@ public class EFCoreDatabaseManager : IDatabaseManager
|
|||||||
return await _context.Set<T>().FromSqlRaw(sql, parameters).ToListAsync();
|
return await _context.Set<T>().FromSqlRaw(sql, parameters).ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<Dictionary<string, object>>> ExecuteRawQueryAsync(string sql, params object[] parameters)
|
||||||
|
{
|
||||||
|
using var command = _context.Database.GetDbConnection().CreateCommand();
|
||||||
|
command.CommandText = sql;
|
||||||
|
|
||||||
|
// Aggiungi i parametri
|
||||||
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
var parameter = command.CreateParameter();
|
||||||
|
parameter.ParameterName = $"@p{i}";
|
||||||
|
parameter.Value = parameters[i] ?? DBNull.Value;
|
||||||
|
command.Parameters.Add(parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.Database.OpenConnectionAsync();
|
||||||
|
|
||||||
|
var results = new List<Dictionary<string, object>>();
|
||||||
|
|
||||||
|
using var reader = await command.ExecuteReaderAsync();
|
||||||
|
|
||||||
|
while (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
var row = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
for (int i = 0; i < reader.FieldCount; i++)
|
||||||
|
{
|
||||||
|
var columnName = reader.GetName(i);
|
||||||
|
var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
|
||||||
|
row[columnName] = value ?? ""; // Usa stringa vuota invece di null
|
||||||
|
}
|
||||||
|
|
||||||
|
results.Add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<int> ExecuteCommandAsync(string sql, params object[] parameters)
|
public async Task<int> ExecuteCommandAsync(string sql, params object[] parameters)
|
||||||
{
|
{
|
||||||
return await _context.Database.ExecuteSqlRawAsync(sql, parameters);
|
return await _context.Database.ExecuteSqlRawAsync(sql, parameters);
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ public interface IDatabaseManager : IDisposable
|
|||||||
/// <param name="skip">Numero di elementi da saltare</param>
|
/// <param name="skip">Numero di elementi da saltare</param>
|
||||||
/// <param name="take">Numero di elementi da prendere</param>
|
/// <param name="take">Numero di elementi da prendere</param>
|
||||||
Task<IEnumerable<T>> GetAsync<T>(
|
Task<IEnumerable<T>> GetAsync<T>(
|
||||||
Expression<Func<T, bool>> filter = null,
|
Expression<Func<T, bool>>? filter = null,
|
||||||
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
|
Func<IQueryable<T>, IOrderedQueryable<T>>? orderBy = null,
|
||||||
string includeProperties = "",
|
string includeProperties = "",
|
||||||
int? skip = null,
|
int? skip = null,
|
||||||
int? take = null) where T : class;
|
int? take = null) where T : class;
|
||||||
@@ -34,13 +34,18 @@ public interface IDatabaseManager : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ottiene un'entità singola in base alla chiave primaria
|
/// Ottiene un'entità singola in base alla chiave primaria
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<T> GetByIdAsync<T>(object id) where T : class;
|
Task<T?> GetByIdAsync<T>(object id) where T : class;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Esegue una query SQL raw
|
/// Esegue una query SQL raw
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, params object[] parameters) where T : class;
|
Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, params object[] parameters) where T : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Esegue una query SQL raw e restituisce i risultati come dictionary
|
||||||
|
/// </summary>
|
||||||
|
Task<List<Dictionary<string, object>>> ExecuteRawQueryAsync(string sql, params object[] parameters);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Esegue un comando SQL che non restituisce risultati
|
/// Esegue un comando SQL che non restituisce risultati
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -80,58 +80,183 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<!-- Lista Tabelle -->
|
<!-- Lista Tabelle -->
|
||||||
@if (databaseTables.Any())
|
@if (isDatabaseConnected)
|
||||||
{
|
{
|
||||||
|
<!-- Selezione modalità: Tabelle o Query Custom -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<h6>Tabelle Database (@databaseTables.Count disponibili):</h6>
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="useCustomQuerySwitch"
|
||||||
|
@onchange="OnQueryModeChanged" checked="@useCustomQuery">
|
||||||
|
<label class="form-check-label" for="useCustomQuerySwitch">
|
||||||
|
<i class="fas fa-code"></i> Usa Query SQL Custom
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">
|
||||||
|
Scegli se selezionare una tabella o scrivere una query SQL personalizzata
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Campo di ricerca -->
|
@if (useCustomQuery)
|
||||||
<div class="mb-2">
|
{
|
||||||
<div class="input-group">
|
<!-- Sezione Query Custom -->
|
||||||
<span class="input-group-text">
|
<div class="mb-3">
|
||||||
<i class="fas fa-search"></i>
|
<h6>Query SQL Custom:</h6>
|
||||||
</span>
|
|
||||||
<input type="text" class="form-control" placeholder="Cerca tabelle..."
|
<div class="mb-2">
|
||||||
@bind="databaseSearchTerm" @oninput="FilterDatabaseTables" />
|
<label class="form-label">Scrivi la tua query SELECT:</label>
|
||||||
@if (!string.IsNullOrEmpty(databaseSearchTerm))
|
<textarea class="form-control" rows="6" placeholder="SELECT * FROM your_table WHERE condition..."
|
||||||
|
@bind="customQuery" @bind:event="oninput"></textarea>
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
Scrivi una query SELECT personalizzata. La query verrà automaticamente ottimizzata per il trasferimento dati.
|
||||||
|
</small>
|
||||||
|
</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-outline-secondary" @onclick="ClearDatabaseSearch">
|
<button class="btn btn-info btn-sm me-2" @onclick="LoadQueryPreview"
|
||||||
<i class="fas fa-times"></i>
|
disabled="@isLoadingPreview">
|
||||||
|
@if (isLoadingPreview)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
||||||
|
}
|
||||||
|
<i class="fas fa-eye"></i> Anteprima Risultati
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@if (showQueryPreview)
|
||||||
|
{
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" @onclick="HideQueryPreview">
|
||||||
|
<i class="fas fa-eye-slash"></i> Nascondi Anteprima
|
||||||
|
</button>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Lista tabelle filtrate -->
|
@if (!string.IsNullOrEmpty(queryValidationMessage) && !isQueryValid)
|
||||||
<div class="list-group" style="max-height: 300px; overflow-y: auto;">
|
|
||||||
@foreach (var table in GetFilteredDatabaseTables())
|
|
||||||
{
|
{
|
||||||
<a class="list-group-item list-group-item-action @(selectedTable == table ? "active" : "")"
|
<div class="alert alert-danger" role="alert">
|
||||||
@onclick="@(() => SelectTable(table))">
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
@queryValidationMessage
|
||||||
<div>
|
</div>
|
||||||
<i class="fas fa-table"></i> @table
|
}
|
||||||
@if (databaseTables.ContainsKey(table))
|
|
||||||
{
|
<!-- Anteprima risultati query -->
|
||||||
<small class="text-muted d-block">@databaseTables[table].Count() campi</small>
|
@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 column in queryColumns)
|
||||||
|
{
|
||||||
|
<th>@column</th>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var row in queryPreviewData.Take(20))
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
@foreach (var column in queryColumns)
|
||||||
|
{
|
||||||
|
<td>
|
||||||
|
@(row.ContainsKey(column) ? row[column]?.ToString() ?? "" : "")
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@if (selectedTable == table)
|
@if (queryPreviewData.Count > 20)
|
||||||
{
|
{
|
||||||
<span class="badge bg-primary">Selezionata</span>
|
<div class="card-footer text-muted">
|
||||||
|
<small>
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
Mostrate prime 20 righe di @queryPreviewData.Count risultati totali
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
else if (databaseTables.Any())
|
||||||
|
{
|
||||||
|
<!-- Sezione Tabelle (modalità standard) -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<h6>Tabelle Database (@databaseTables.Count disponibili):</h6>
|
||||||
|
|
||||||
@if (!GetFilteredDatabaseTables().Any())
|
<!-- Campo di ricerca -->
|
||||||
{
|
<div class="mb-2">
|
||||||
<div class="alert alert-info mt-2">
|
<div class="input-group">
|
||||||
<i class="fas fa-info-circle"></i> Nessuna tabella trovata con il termine di ricerca "@databaseSearchTerm"
|
<span class="input-group-text">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" class="form-control" placeholder="Cerca tabelle..."
|
||||||
|
@bind="databaseSearchTerm" @oninput="FilterDatabaseTables" />
|
||||||
|
@if (!string.IsNullOrEmpty(databaseSearchTerm))
|
||||||
|
{
|
||||||
|
<button class="btn btn-outline-secondary" @onclick="ClearDatabaseSearch">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</div>
|
<!-- Lista tabelle filtrate -->
|
||||||
|
<div class="list-group" style="max-height: 300px; overflow-y: auto;">
|
||||||
|
@foreach (var table in GetFilteredDatabaseTables())
|
||||||
|
{
|
||||||
|
<a class="list-group-item list-group-item-action @(selectedTable == table ? "active" : "")"
|
||||||
|
@onclick="@(() => SelectTable(table))">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<i class="fas fa-table"></i> @table
|
||||||
|
@if (databaseTables.ContainsKey(table))
|
||||||
|
{
|
||||||
|
<small class="text-muted d-block">@databaseTables[table].Count() campi</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (selectedTable == table)
|
||||||
|
{
|
||||||
|
<span class="badge bg-primary">Selezionata</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!GetFilteredDatabaseTables().Any())
|
||||||
|
{
|
||||||
|
<div class="alert alert-info mt-2">
|
||||||
|
<i class="fas fa-info-circle"></i> Nessuna tabella trovata con il termine di ricerca "@databaseSearchTerm"
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +607,8 @@
|
|||||||
</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 && !string.IsNullOrEmpty(selectedTable)) ||
|
var isSourceReady = (selectedSourceType == "database" && 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)
|
||||||
@@ -503,30 +629,60 @@
|
|||||||
<!-- Colonna Sinistra: Campi Fonte -->
|
<!-- Colonna Sinistra: Campi Fonte -->
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<h6>Campi @sourceTypeName (@sourceDisplayName)</h6>
|
<h6>Campi @sourceTypeName (@sourceDisplayName)</h6>
|
||||||
|
@if (selectedSourceType == "database" && useCustomQuery && queryColumns.Any())
|
||||||
|
{
|
||||||
|
<!-- Le colonne della query validata sono disponibili per il mapping -->
|
||||||
|
}
|
||||||
<div class="list-group" style="max-height: 400px; overflow-y: auto;">
|
<div class="list-group" style="max-height: 400px; overflow-y: auto;">
|
||||||
@if (selectedSourceType == "database" && databaseTables.ContainsKey(selectedTable))
|
@if (selectedSourceType == "database")
|
||||||
{
|
{
|
||||||
@foreach (var column in databaseTables[selectedTable])
|
@if (useCustomQuery && queryColumns.Any())
|
||||||
{
|
{
|
||||||
<a class="list-group-item list-group-item-action @(selectedDbColumn == column.Name ? "active" : "")"
|
<!-- Colonne da query custom -->
|
||||||
@onclick="@(() => SelectDbColumn(column.Name))">
|
@foreach (var column in queryColumns)
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
{
|
||||||
<div>
|
<a class="list-group-item list-group-item-action @(selectedDbColumn == column ? "active" : "")"
|
||||||
<strong>@column.Name</strong>
|
@onclick="@(() => SelectDbColumn(column))">
|
||||||
<small class="text-muted d-block">@column.DataType</small>
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<strong>@column</strong>
|
||||||
|
<small class="text-muted d-block">Query Column</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (fieldMappings.ContainsKey(column))
|
||||||
|
{
|
||||||
|
<span class="badge bg-success">Mapped</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</a>
|
||||||
@if (column.IsPrimaryKey)
|
}
|
||||||
{
|
}
|
||||||
<span class="badge bg-primary">PK</span>
|
else if (!useCustomQuery && databaseTables.ContainsKey(selectedTable))
|
||||||
}
|
{
|
||||||
@if (fieldMappings.ContainsKey(column.Name))
|
<!-- Colonne da tabella -->
|
||||||
{
|
@foreach (var column in databaseTables[selectedTable])
|
||||||
<span class="badge bg-success">Mapped</span>
|
{
|
||||||
}
|
<a class="list-group-item list-group-item-action @(selectedDbColumn == column.Name ? "active" : "")"
|
||||||
|
@onclick="@(() => SelectDbColumn(column.Name))">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<strong>@column.Name</strong>
|
||||||
|
<small class="text-muted d-block">@column.DataType</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (column.IsPrimaryKey)
|
||||||
|
{
|
||||||
|
<span class="badge bg-primary">PK</span>
|
||||||
|
}
|
||||||
|
@if (fieldMappings.ContainsKey(column.Name))
|
||||||
|
{
|
||||||
|
<span class="badge bg-success">Mapped</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (selectedSourceType == "file" && fileSheets.ContainsKey(selectedSheet))
|
else if (selectedSourceType == "file" && fileSheets.ContainsKey(selectedSheet))
|
||||||
@@ -694,11 +850,21 @@
|
|||||||
{
|
{
|
||||||
<option value="@suggestedPrimaryKey">@suggestedPrimaryKey (Primary Key - Consigliato)</option>
|
<option value="@suggestedPrimaryKey">@suggestedPrimaryKey (Primary Key - Consigliato)</option>
|
||||||
}
|
}
|
||||||
@if (selectedSourceType == "database" && databaseTables.ContainsKey(selectedTable))
|
@if (selectedSourceType == "database")
|
||||||
{
|
{
|
||||||
@foreach (var column in databaseTables[selectedTable].Where(c => c.Name != suggestedPrimaryKey))
|
@if (useCustomQuery && queryColumns.Any())
|
||||||
{
|
{
|
||||||
<option value="@column.Name">@column.Name (@column.DataType)</option>
|
@foreach (var column in queryColumns)
|
||||||
|
{
|
||||||
|
<option value="@column">@column (Query Column)</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!useCustomQuery && databaseTables.ContainsKey(selectedTable))
|
||||||
|
{
|
||||||
|
@foreach (var column in databaseTables[selectedTable].Where(c => c.Name != suggestedPrimaryKey))
|
||||||
|
{
|
||||||
|
<option value="@column.Name">@column.Name (@column.DataType)</option>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (selectedSourceType == "file" && fileSheets.ContainsKey(selectedSheet))
|
else if (selectedSourceType == "file" && fileSheets.ContainsKey(selectedSheet))
|
||||||
@@ -1010,7 +1176,18 @@
|
|||||||
private string selectedDatabase = "";
|
private string selectedDatabase = "";
|
||||||
private bool showDatabaseSelection = false;
|
private bool showDatabaseSelection = false;
|
||||||
private bool showDatabaseSelectionModal = false;
|
private bool showDatabaseSelectionModal = false;
|
||||||
private bool isLoadingDatabases = false; // File handling
|
private bool isLoadingDatabases = 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 string selectedFileName = "";
|
||||||
private bool isProcessingFile = false;
|
private bool isProcessingFile = false;
|
||||||
private string fileErrorMessage = "";
|
private string fileErrorMessage = "";
|
||||||
@@ -1431,6 +1608,18 @@
|
|||||||
selectedTable = "";
|
selectedTable = "";
|
||||||
databaseSearchTerm = "";
|
databaseSearchTerm = "";
|
||||||
databaseErrorMessage = "";
|
databaseErrorMessage = "";
|
||||||
|
|
||||||
|
// Reset custom query state
|
||||||
|
useCustomQuery = false;
|
||||||
|
customQuery = "";
|
||||||
|
isValidatingQuery = false;
|
||||||
|
isQueryValid = false;
|
||||||
|
queryValidationMessage = "";
|
||||||
|
queryPreviewData.Clear();
|
||||||
|
queryColumns.Clear();
|
||||||
|
showQueryPreview = false;
|
||||||
|
isLoadingPreview = false;
|
||||||
|
|
||||||
currentDatabaseManager?.Dispose();
|
currentDatabaseManager?.Dispose();
|
||||||
currentDatabaseManager = null;
|
currentDatabaseManager = null;
|
||||||
|
|
||||||
@@ -1583,6 +1772,16 @@
|
|||||||
} private async void SelectTable(string tableName)
|
} private async void SelectTable(string tableName)
|
||||||
{
|
{
|
||||||
selectedTable = 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
|
// Clear mappings when changing table
|
||||||
ClearAllMappings();
|
ClearAllMappings();
|
||||||
|
|
||||||
@@ -1753,29 +1952,52 @@
|
|||||||
|
|
||||||
private void AutoMapFields()
|
private void AutoMapFields()
|
||||||
{
|
{
|
||||||
if (!databaseTables.ContainsKey(selectedTable) || restEntityDetails == null)
|
if (restEntityDetails == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var dbColumns = databaseTables[selectedTable];
|
IEnumerable<string> sourceColumns = new List<string>();
|
||||||
var restProperties = restEntityDetails.Properties;
|
|
||||||
|
|
||||||
|
// 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;
|
int mappingsCreated = 0;
|
||||||
|
|
||||||
foreach (var dbColumn in dbColumns)
|
foreach (var sourceColumn in sourceColumns)
|
||||||
{
|
{
|
||||||
// Trova una proprietà REST con nome simile
|
// Trova una proprietà REST con nome simile
|
||||||
var matchingProperty = restProperties.FirstOrDefault(p =>
|
var matchingProperty = restProperties.FirstOrDefault(p =>
|
||||||
string.Equals(p.Name, dbColumn.Name, StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(p.Name, sourceColumn, StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(p.Name.Replace("_", ""), dbColumn.Name.Replace("_", ""), StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(p.Name.Replace("_", ""), sourceColumn.Replace("_", ""), StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(p.Name.Replace("Id", ""), dbColumn.Name.Replace("Id", ""), StringComparison.OrdinalIgnoreCase)
|
string.Equals(p.Name.Replace("Id", ""), sourceColumn.Replace("Id", ""), StringComparison.OrdinalIgnoreCase)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (matchingProperty != null && !fieldMappings.ContainsKey(dbColumn.Name))
|
if (matchingProperty != null && !fieldMappings.ContainsKey(sourceColumn))
|
||||||
{
|
{
|
||||||
fieldMappings[dbColumn.Name] = matchingProperty.Name;
|
fieldMappings[sourceColumn] = matchingProperty.Name;
|
||||||
mappingsCreated++;
|
mappingsCreated++;
|
||||||
}
|
}
|
||||||
} Logger.LogInformation("Auto-mapping completato. Creati {Count} mapping automatici", mappingsCreated);
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Auto-mapping completato. Creati {Count} mapping automatici da {SourceType}",
|
||||||
|
mappingsCreated, useCustomQuery ? "query custom" : selectedSourceType);
|
||||||
} private async Task ShowMappingSummary()
|
} private async Task ShowMappingSummary()
|
||||||
{
|
{
|
||||||
var summary = "Riepilogo Configurazione:\n\n";
|
var summary = "Riepilogo Configurazione:\n\n";
|
||||||
@@ -1803,11 +2025,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check source-specific requirements
|
// Check source-specific requirements
|
||||||
if (selectedSourceType == "database" && (currentDatabaseManager == null || string.IsNullOrEmpty(selectedTable)))
|
if (selectedSourceType == "database")
|
||||||
{
|
{
|
||||||
transferMessage = "Database non connesso o tabella non selezionata.";
|
if (currentDatabaseManager == null)
|
||||||
transferMessageType = "error";
|
{
|
||||||
return;
|
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))
|
if (selectedSourceType == "file" && string.IsNullOrEmpty(selectedSheet))
|
||||||
@@ -1832,7 +2073,9 @@
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sourceName = selectedSourceType == "database" ? selectedTable : selectedSheet;
|
var sourceName = selectedSourceType == "database"
|
||||||
|
? (useCustomQuery ? "custom_query" : selectedTable)
|
||||||
|
: selectedSheet;
|
||||||
Logger.LogInformation("Iniziando trasferimento dati da {SourceType} {Source} a {Entity} con {MappingCount} mappature",
|
Logger.LogInformation("Iniziando trasferimento dati da {SourceType} {Source} a {Entity} con {MappingCount} mappature",
|
||||||
selectedSourceType, sourceName, selectedRestEntity.Name, fieldMappings.Count);
|
selectedSourceType, sourceName, selectedRestEntity.Name, fieldMappings.Count);
|
||||||
|
|
||||||
@@ -1883,7 +2126,9 @@
|
|||||||
|
|
||||||
// Genera la chiave sorgente per questo record
|
// Genera la chiave sorgente per questo record
|
||||||
var sourceKey = GenerateSourceKey(record);
|
var sourceKey = GenerateSourceKey(record);
|
||||||
var currentSourceName = selectedSourceType == "database" ? selectedTable : selectedSheet;
|
var currentSourceName = selectedSourceType == "database"
|
||||||
|
? (useCustomQuery ? "custom_query" : selectedTable)
|
||||||
|
: selectedSheet;
|
||||||
|
|
||||||
// NUOVA LOGICA: Cerca associazione esistente
|
// NUOVA LOGICA: Cerca associazione esistente
|
||||||
if (useRecordAssociations && !string.IsNullOrEmpty(sourceKey))
|
if (useRecordAssociations && !string.IsNullOrEmpty(sourceKey))
|
||||||
@@ -2085,18 +2330,39 @@
|
|||||||
|
|
||||||
private async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromDatabase()
|
private async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromDatabase()
|
||||||
{
|
{
|
||||||
if (currentDatabaseManager == null || string.IsNullOrEmpty(selectedTable))
|
if (currentDatabaseManager == null)
|
||||||
return new List<Dictionary<string, object>>();
|
return new List<Dictionary<string, object>>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Usa il database manager per eseguire una query che ottiene tutti i record
|
if (useCustomQuery)
|
||||||
// Questo è un esempio semplificato - potresti voler implementare paginazione per tabelle grandi
|
{
|
||||||
return await currentDatabaseManager.GetAllRecordsAsync(selectedTable);
|
// 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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Errore nell'ottenere i record dalla tabella {Table}", selectedTable);
|
Logger.LogError(ex, "Errore nell'ottenere i record dal database. UseCustomQuery: {UseCustomQuery}, Table: {Table}, Query: {Query}",
|
||||||
|
useCustomQuery, selectedTable, useCustomQuery ? customQuery : "N/A");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
} private async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromFile()
|
} private async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromFile()
|
||||||
@@ -2456,4 +2722,269 @@
|
|||||||
// Per ora usa DocEntry come default per SAP B1
|
// Per ora usa DocEntry come default per SAP B1
|
||||||
return "DocEntry";
|
return "DocEntry";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom Query Methods
|
||||||
|
private void OnQueryModeChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
useCustomQuery = bool.Parse(e.Value?.ToString() ?? "false");
|
||||||
|
|
||||||
|
if (useCustomQuery)
|
||||||
|
{
|
||||||
|
// Reset table selection when switching to custom query
|
||||||
|
selectedTable = "";
|
||||||
|
ClearAllMappings();
|
||||||
|
|
||||||
|
// Reset query-specific state
|
||||||
|
customQuery = "";
|
||||||
|
isQueryValid = false;
|
||||||
|
queryValidationMessage = "";
|
||||||
|
queryPreviewData.Clear();
|
||||||
|
queryColumns.Clear();
|
||||||
|
showQueryPreview = false;
|
||||||
|
|
||||||
|
// For custom queries, require manual key selection
|
||||||
|
sourceKeyField = "";
|
||||||
|
suggestedPrimaryKey = "";
|
||||||
|
requiresManualKeySelection = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Reset custom query when switching to table mode
|
||||||
|
customQuery = "";
|
||||||
|
isQueryValid = false;
|
||||||
|
queryValidationMessage = "";
|
||||||
|
queryPreviewData.Clear();
|
||||||
|
queryColumns.Clear();
|
||||||
|
showQueryPreview = false;
|
||||||
|
ClearAllMappings();
|
||||||
|
|
||||||
|
// Reset key field selection
|
||||||
|
sourceKeyField = "";
|
||||||
|
suggestedPrimaryKey = "";
|
||||||
|
requiresManualKeySelection = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ValidateCustomQuery()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null)
|
||||||
|
{
|
||||||
|
isQueryValid = false;
|
||||||
|
queryValidationMessage = "Query vuota o database non connesso";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidatingQuery = true;
|
||||||
|
queryValidationMessage = "";
|
||||||
|
queryColumns.Clear();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Converte la query per testare solo 1 riga
|
||||||
|
var testQuery = ConvertQueryForValidation(customQuery);
|
||||||
|
|
||||||
|
Logger.LogInformation("Validazione query: {TestQuery}", testQuery);
|
||||||
|
|
||||||
|
// Esegue la query di test
|
||||||
|
var testResults = await currentDatabaseManager.ExecuteRawQueryAsync(testQuery);
|
||||||
|
|
||||||
|
if (testResults != null && testResults.Any())
|
||||||
|
{
|
||||||
|
isQueryValid = true;
|
||||||
|
|
||||||
|
// Estrae i nomi delle colonne dal primo record
|
||||||
|
var firstRecord = testResults.First();
|
||||||
|
queryColumns = firstRecord.Keys.ToList();
|
||||||
|
|
||||||
|
// Non mostra più messaggi di successo per ridurre l'ingombro visivo
|
||||||
|
queryValidationMessage = "";
|
||||||
|
|
||||||
|
Logger.LogInformation("Query validata con successo. Colonne: {Columns}", string.Join(", ", queryColumns));
|
||||||
|
|
||||||
|
// Clear existing mappings since we have new columns
|
||||||
|
fieldMappings.Clear();
|
||||||
|
selectedDbColumn = "";
|
||||||
|
selectedRestProperty = "";
|
||||||
|
|
||||||
|
// For custom queries, always require manual key selection
|
||||||
|
sourceKeyField = "";
|
||||||
|
suggestedPrimaryKey = "";
|
||||||
|
requiresManualKeySelection = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isQueryValid = false;
|
||||||
|
queryValidationMessage = "Query valida ma non restituisce risultati";
|
||||||
|
queryColumns.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
isQueryValid = false;
|
||||||
|
queryValidationMessage = $"Errore nella validazione: {ex.Message}";
|
||||||
|
queryColumns.Clear();
|
||||||
|
Logger.LogError(ex, "Errore nella validazione della query custom");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
isValidatingQuery = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadQueryPreview()
|
||||||
|
{
|
||||||
|
if (!isQueryValid || string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingPreview = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Usa la query limitata per il preview (max 50 righe per performance)
|
||||||
|
var previewQuery = ConvertQueryForPreview(customQuery, 50);
|
||||||
|
|
||||||
|
Logger.LogInformation("Caricamento preview query: {PreviewQuery}", previewQuery);
|
||||||
|
|
||||||
|
queryPreviewData = await currentDatabaseManager.ExecuteRawQueryAsync(previewQuery);
|
||||||
|
showQueryPreview = true;
|
||||||
|
|
||||||
|
Logger.LogInformation("Preview caricato: {RowCount} righe", queryPreviewData.Count);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
queryValidationMessage = $"Errore nel caricamento preview: {ex.Message}";
|
||||||
|
Logger.LogError(ex, "Errore nel caricamento del preview della query");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
isLoadingPreview = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HideQueryPreview()
|
||||||
|
{
|
||||||
|
showQueryPreview = false;
|
||||||
|
queryPreviewData.Clear();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ConvertQueryForValidation(string originalQuery)
|
||||||
|
{
|
||||||
|
// Rimuove commenti e spazi extra
|
||||||
|
var cleanQuery = CleanQuery(originalQuery);
|
||||||
|
|
||||||
|
// Se la query ha già un LIMIT/TOP, la usa così com'è per il test
|
||||||
|
if (HasLimitClause(cleanQuery))
|
||||||
|
{
|
||||||
|
return cleanQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiunge LIMIT/TOP in base al tipo di database
|
||||||
|
return AddLimitClause(cleanQuery, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ConvertQueryForPreview(string originalQuery, int maxRows = 50)
|
||||||
|
{
|
||||||
|
var cleanQuery = CleanQuery(originalQuery);
|
||||||
|
|
||||||
|
// Se la query ha già un LIMIT/TOP con un valore minore, la mantiene
|
||||||
|
if (HasLimitClause(cleanQuery))
|
||||||
|
{
|
||||||
|
return cleanQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddLimitClause(cleanQuery, maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CleanQuery(string query)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(query))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
// Rimuove commenti SQL
|
||||||
|
var lines = query.Split('\n')
|
||||||
|
.Select(line => line.Contains("--") ? line.Substring(0, line.IndexOf("--")) : line)
|
||||||
|
.Where(line => !string.IsNullOrWhiteSpace(line));
|
||||||
|
|
||||||
|
var cleanQuery = string.Join(" ", lines).Trim();
|
||||||
|
|
||||||
|
// Rimuove il punto e virgola finale se presente
|
||||||
|
if (cleanQuery.EndsWith(";"))
|
||||||
|
{
|
||||||
|
cleanQuery = cleanQuery.Substring(0, cleanQuery.Length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasLimitClause(string query)
|
||||||
|
{
|
||||||
|
var upperQuery = query.ToUpperInvariant();
|
||||||
|
return upperQuery.Contains(" LIMIT ") ||
|
||||||
|
upperQuery.Contains(" TOP ") ||
|
||||||
|
upperQuery.Contains("ROWNUM") ||
|
||||||
|
upperQuery.Contains("FETCH FIRST");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string AddLimitClause(string query, int limit)
|
||||||
|
{
|
||||||
|
var upperQuery = query.ToUpperInvariant();
|
||||||
|
|
||||||
|
// Per SQL Server, Oracle, e altri che supportano TOP
|
||||||
|
if (upperQuery.Contains("SELECT "))
|
||||||
|
{
|
||||||
|
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
|
||||||
|
if (credential != null)
|
||||||
|
{
|
||||||
|
var dbType = credential.DatabaseType.ToString().ToLowerInvariant();
|
||||||
|
switch (dbType)
|
||||||
|
{
|
||||||
|
case "sqlserver":
|
||||||
|
case "oracle":
|
||||||
|
// Aggiunge TOP dopo SELECT
|
||||||
|
return query.Replace("SELECT ", $"SELECT TOP {limit} ", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
case "mysql":
|
||||||
|
case "postgresql":
|
||||||
|
case "sqlite":
|
||||||
|
default:
|
||||||
|
// Aggiunge LIMIT alla fine
|
||||||
|
return $"{query} LIMIT {limit}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: aggiunge LIMIT
|
||||||
|
return $"{query} LIMIT {limit}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCustomQueryChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
customQuery = e.Value?.ToString() ?? "";
|
||||||
|
|
||||||
|
// Reset validation quando la query cambia
|
||||||
|
isQueryValid = false;
|
||||||
|
queryValidationMessage = "";
|
||||||
|
queryPreviewData.Clear();
|
||||||
|
queryColumns.Clear();
|
||||||
|
showQueryPreview = false;
|
||||||
|
|
||||||
|
// Clear mappings quando la query cambia
|
||||||
|
ClearAllMappings();
|
||||||
|
|
||||||
|
// Reset key field selection
|
||||||
|
sourceKeyField = "";
|
||||||
|
suggestedPrimaryKey = "";
|
||||||
|
requiresManualKeySelection = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user