diff --git a/Components/ProfileQuickActions.razor b/Components/ProfileQuickActions.razor
deleted file mode 100644
index e69de29..0000000
diff --git a/Components/ProfileQuickActions.razor.cs b/Components/ProfileQuickActions.razor.cs
deleted file mode 100644
index e69de29..0000000
diff --git a/Components/ProfileSaver.razor b/Components/ProfileSaver.razor
index 2699635..9eeb893 100644
--- a/Components/ProfileSaver.razor
+++ b/Components/ProfileSaver.razor
@@ -44,6 +44,22 @@
+
+
@@ -63,6 +79,10 @@
{
Credenziali: @SourceCredentialName
}
+ @if (!string.IsNullOrEmpty(SourceDatabaseName))
+ {
+
Database: @SourceDatabaseName (dalla connessione attiva)
+ }
@if (!string.IsNullOrEmpty(SourceSchema))
{
Schema: @SourceSchema
diff --git a/Components/ProfileSaver.razor.cs b/Components/ProfileSaver.razor.cs
index 2077d4a..2fd5a96 100644
--- a/Components/ProfileSaver.razor.cs
+++ b/Components/ProfileSaver.razor.cs
@@ -1,15 +1,19 @@
using Microsoft.AspNetCore.Components;
using CredentialManager.Models;
+using CredentialManager.Services;
using System.ComponentModel.DataAnnotations;
namespace Components;
public partial class ProfileSaver
{
+ [Inject] private ICredentialService CredentialService { get; set; } = default!;
+
[Parameter] public bool CanSave { get; set; }
[Parameter] public string SourceType { get; set; } = "";
[Parameter] public int? SourceCredentialId { get; set; }
[Parameter] public string? SourceCredentialName { get; set; }
+ [Parameter] public string? SourceDatabaseName { get; set; }
[Parameter] public string? SourceSchema { get; set; }
[Parameter] public string? SourceTable { get; set; }
[Parameter] public string? SourceFilePath { get; set; }
@@ -51,6 +55,9 @@ public partial class ProfileSaver
try
{
+ // Recupera automaticamente il nome del database dalla connessione attiva
+ var sourceDatabaseName = await GetSourceDatabaseNameAsync();
+
var profileDto = new DataCouplerProfileDto
{
Name = ProfileData.Name,
@@ -58,6 +65,7 @@ public partial class ProfileSaver
SourceType = SourceType,
SourceCredentialId = SourceCredentialId,
SourceCredentialName = SourceCredentialName,
+ SourceDatabaseName = sourceDatabaseName,
SourceSchema = SourceSchema,
SourceTable = SourceTable,
SourceFilePath = SourceFilePath,
@@ -119,6 +127,34 @@ public partial class ProfileSaver
SaveMessageType = type;
}
+ private async Task
GetSourceDatabaseNameAsync()
+ {
+ // Prima priorità: se SourceDatabaseName è già impostato come parametro, usa quello
+ if (!string.IsNullOrEmpty(SourceDatabaseName))
+ {
+ return SourceDatabaseName;
+ }
+
+ // Seconda priorità: se abbiamo un SourceCredentialId, recupera il database dalle credenziali
+ if (SourceCredentialId.HasValue)
+ {
+ try
+ {
+ var credential = await CredentialService.GetDatabaseCredentialAsync(SourceCredentialId.Value);
+ if (credential != null && !string.IsNullOrEmpty(credential.DatabaseName))
+ {
+ return credential.DatabaseName;
+ }
+ }
+ catch (Exception)
+ {
+ // Se non riesce a recuperare le credenziali, continua con null
+ }
+ }
+
+ return null;
+ }
+
public class ProfileFormModel
{
[Required(ErrorMessage = "Il nome del profilo è obbligatorio")]
diff --git a/CredentialManager/Migrations/20250704135720_AddSourceDatabaseNameColumn.Designer.cs b/CredentialManager/Migrations/20250704135720_AddSourceDatabaseNameColumn.Designer.cs
new file mode 100644
index 0000000..e21eb3f
--- /dev/null
+++ b/CredentialManager/Migrations/20250704135720_AddSourceDatabaseNameColumn.Designer.cs
@@ -0,0 +1,337 @@
+//
+using System;
+using CredentialManager.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace CredentialManager.Migrations
+{
+ [DbContext(typeof(CredentialDbContext))]
+ [Migration("20250704135720_AddSourceDatabaseNameColumn")]
+ partial class AddSourceDatabaseNameColumn
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
+
+ modelBuilder.Entity("CredentialManager.Models.CredentialEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AdditionalParameters")
+ .HasMaxLength(2000)
+ .HasColumnType("TEXT");
+
+ b.Property("CommandTimeout")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(30);
+
+ b.Property("ConnectionString")
+ .HasMaxLength(500)
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedBy")
+ .HasMaxLength(100)
+ .HasColumnType("TEXT");
+
+ b.Property("DatabaseName")
+ .HasMaxLength(100)
+ .HasColumnType("TEXT");
+
+ b.Property("DatabaseType")
+ .HasMaxLength(50)
+ .HasColumnType("TEXT");
+
+ b.Property("EncryptedApiKey")
+ .HasMaxLength(500)
+ .HasColumnType("TEXT");
+
+ b.Property("EncryptedAuthToken")
+ .HasMaxLength(500)
+ .HasColumnType("TEXT");
+
+ b.Property("EncryptedPassword")
+ .HasColumnType("TEXT");
+
+ b.Property("Headers")
+ .HasMaxLength(2000)
+ .HasColumnType("TEXT");
+
+ b.Property("Host")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("IgnoreSslErrors")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false);
+
+ b.Property("IsActive")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT");
+
+ b.Property("Port")
+ .HasColumnType("INTEGER");
+
+ b.Property("RestServiceType")
+ .HasMaxLength(50)
+ .HasColumnType("TEXT");
+
+ b.Property("TimeoutSeconds")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(100);
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Username")
+ .HasMaxLength(100)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DatabaseType");
+
+ b.HasIndex("IsActive");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.HasIndex("Type");
+
+ b.ToTable("Credentials", (string)null);
+ });
+
+ modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("CreatedBy")
+ .HasMaxLength(100)
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasMaxLength(500)
+ .HasColumnType("TEXT");
+
+ b.Property("DestinationCredentialId")
+ .HasColumnType("INTEGER");
+
+ b.Property("DestinationEndpoint")
+ .HasMaxLength(500)
+ .HasColumnType("TEXT");
+
+ b.Property("DestinationSchema")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("DestinationTable")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("DestinationType")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("TEXT");
+
+ b.Property("FieldMappingJson")
+ .HasMaxLength(4000)
+ .HasColumnType("TEXT");
+
+ b.Property("IsActive")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("LastUsedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT");
+
+ b.Property("SourceCredentialId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SourceDatabaseName")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("SourceFilePath")
+ .HasMaxLength(500)
+ .HasColumnType("TEXT");
+
+ b.Property("SourceKeyField")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("SourceSchema")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("SourceTable")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("SourceType")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("TEXT");
+
+ b.Property("UseRecordAssociations")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt");
+
+ b.HasIndex("DestinationCredentialId");
+
+ b.HasIndex("DestinationType");
+
+ b.HasIndex("IsActive");
+
+ b.HasIndex("LastUsedAt");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.HasIndex("SourceCredentialId");
+
+ b.HasIndex("SourceType");
+
+ b.ToTable("DataCouplerProfiles", (string)null);
+ });
+
+ modelBuilder.Entity("CredentialManager.Models.KeyAssociation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AdditionalInfo")
+ .HasMaxLength(2000)
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("DestinationEntity")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("DestinationId")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("DestinationKeyField")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("IsActive")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("KeyValue")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("TEXT");
+
+ b.Property("LastVerifiedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("RestCredentialName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT");
+
+ b.Property("SourceKeyField")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("SourcesInfo")
+ .HasMaxLength(2000)
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt");
+
+ b.HasIndex("DestinationEntity");
+
+ b.HasIndex("IsActive");
+
+ b.HasIndex("KeyValue")
+ .HasDatabaseName("IX_KeyAssociations_KeyValue");
+
+ b.HasIndex("LastVerifiedAt");
+
+ b.HasIndex("RestCredentialName");
+
+ b.HasIndex("KeyValue", "DestinationEntity", "RestCredentialName")
+ .IsUnique()
+ .HasDatabaseName("IX_KeyAssociations_Unique");
+
+ b.ToTable("KeyAssociations", (string)null);
+ });
+
+ modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
+ {
+ b.HasOne("CredentialManager.Models.CredentialEntity", "DestinationCredential")
+ .WithMany()
+ .HasForeignKey("DestinationCredentialId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("CredentialManager.Models.CredentialEntity", "SourceCredential")
+ .WithMany()
+ .HasForeignKey("SourceCredentialId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.Navigation("DestinationCredential");
+
+ b.Navigation("SourceCredential");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/CredentialManager/Migrations/20250704135720_AddSourceDatabaseNameColumn.cs b/CredentialManager/Migrations/20250704135720_AddSourceDatabaseNameColumn.cs
new file mode 100644
index 0000000..3e0b17d
--- /dev/null
+++ b/CredentialManager/Migrations/20250704135720_AddSourceDatabaseNameColumn.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace CredentialManager.Migrations
+{
+ ///
+ public partial class AddSourceDatabaseNameColumn : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "SourceDatabaseName",
+ table: "DataCouplerProfiles",
+ type: "TEXT",
+ maxLength: 200,
+ nullable: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "SourceDatabaseName",
+ table: "DataCouplerProfiles");
+ }
+ }
+}
diff --git a/CredentialManager/Migrations/CredentialDbContextModelSnapshot.cs b/CredentialManager/Migrations/CredentialDbContextModelSnapshot.cs
index 80c149e..60ff537 100644
--- a/CredentialManager/Migrations/CredentialDbContextModelSnapshot.cs
+++ b/CredentialManager/Migrations/CredentialDbContextModelSnapshot.cs
@@ -182,6 +182,10 @@ namespace CredentialManager.Migrations
b.Property("SourceCredentialId")
.HasColumnType("INTEGER");
+ b.Property("SourceDatabaseName")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
b.Property("SourceFilePath")
.HasMaxLength(500)
.HasColumnType("TEXT");
diff --git a/CredentialManager/Models/DataCouplerProfile.cs b/CredentialManager/Models/DataCouplerProfile.cs
index a188225..9c4ab8d 100644
--- a/CredentialManager/Models/DataCouplerProfile.cs
+++ b/CredentialManager/Models/DataCouplerProfile.cs
@@ -25,6 +25,9 @@ public class DataCouplerProfile
public int? SourceCredentialId { get; set; }
+ [MaxLength(200)]
+ public string? SourceDatabaseName { get; set; }
+
[MaxLength(200)]
public string? SourceSchema { get; set; }
diff --git a/CredentialManager/Models/DataCouplerProfileDto.cs b/CredentialManager/Models/DataCouplerProfileDto.cs
index e458c57..a60a043 100644
--- a/CredentialManager/Models/DataCouplerProfileDto.cs
+++ b/CredentialManager/Models/DataCouplerProfileDto.cs
@@ -13,6 +13,7 @@ public class DataCouplerProfileDto
public string SourceType { get; set; } = string.Empty;
public int? SourceCredentialId { get; set; }
public string? SourceCredentialName { get; set; }
+ public string? SourceDatabaseName { get; set; }
public string? SourceSchema { get; set; }
public string? SourceTable { get; set; }
public string? SourceFilePath { get; set; }
diff --git a/CredentialManager/Services/DataCouplerProfileService.cs b/CredentialManager/Services/DataCouplerProfileService.cs
index 98c5053..7c23422 100644
--- a/CredentialManager/Services/DataCouplerProfileService.cs
+++ b/CredentialManager/Services/DataCouplerProfileService.cs
@@ -214,6 +214,7 @@ public class DataCouplerProfileService : IDataCouplerProfileService
SourceType = profile.SourceType,
SourceCredentialId = profile.SourceCredentialId,
SourceCredentialName = profile.SourceCredential?.Name,
+ SourceDatabaseName = profile.SourceDatabaseName,
SourceSchema = profile.SourceSchema,
SourceTable = profile.SourceTable,
SourceFilePath = profile.SourceFilePath,
@@ -241,6 +242,7 @@ public class DataCouplerProfileService : IDataCouplerProfileService
Description = dto.Description,
SourceType = dto.SourceType,
SourceCredentialId = dto.SourceCredentialId,
+ SourceDatabaseName = dto.SourceDatabaseName,
SourceSchema = dto.SourceSchema,
SourceTable = dto.SourceTable,
SourceFilePath = dto.SourceFilePath,
diff --git a/CredentialManager/design_time_temp.db b/CredentialManager/design_time_temp.db
index 88eb0a9..cf8a0be 100644
Binary files a/CredentialManager/design_time_temp.db and b/CredentialManager/design_time_temp.db differ
diff --git a/Data_Coupler/Pages/DataCoupler.razor b/Data_Coupler/Pages/DataCoupler.razor
index 1712ee9..4f0cb22 100644
--- a/Data_Coupler/Pages/DataCoupler.razor
+++ b/Data_Coupler/Pages/DataCoupler.razor
@@ -1005,6 +1005,7 @@
SourceType="@selectedSourceType"
SourceCredentialId="@(GetCurrentSourceCredentialIdAsync().Result)"
SourceCredentialName="@selectedDatabaseCredential"
+ SourceDatabaseName="@selectedDatabase"
SourceSchema="@GetCurrentDatabaseSchema()"
SourceTable="@(useCustomQuery ? "custom_query" : selectedTable)"
SourceFilePath="@selectedFileName"
diff --git a/Data_Coupler/Pages/DataCoupler.razor.cs b/Data_Coupler/Pages/DataCoupler.razor.cs
index 59c64e3..6806bab 100644
--- a/Data_Coupler/Pages/DataCoupler.razor.cs
+++ b/Data_Coupler/Pages/DataCoupler.razor.cs
@@ -38,42 +38,42 @@ public partial class DataCoupler : ComponentBase
// Stato delle credenziali
private List databaseCredentials = new();
private List restApiCredentials = new();
-
+
// Selezione tipo fonte
private string selectedSourceType = "";
-
+
// Credenziali selezionate
private string selectedDatabaseCredential = "";
private string selectedRestCredential = "";
-
+
// Stato connessioni
private bool isConnectingDatabase = false;
private bool isConnectingRest = false;
private bool isDatabaseConnected = false;
private bool isRestConnected = false;
-
+
// Messaggi di errore
private string databaseErrorMessage = "";
private string restErrorMessage = "";
-
+
// Database discovery
private List availableTableNames = new(); // Solo nomi delle tabelle
private Dictionary> databaseTables = new(); // Schema dettagliato per tabelle caricate
private string selectedTable = "";
private string databaseSearchTerm = "";
-
+
// Database selection - per gestire la selezione del database quando non specificato nella connection string
private List availableDatabases = new();
private string selectedDatabase = "";
private bool showDatabaseSelectionModal = false;
private bool isLoadingDatabases = false;
-
+
// Database selection (schemas only)
private List availableSchemas = new();
private string selectedSchema = "";
private bool showSchemaSelectionModal = false;
private bool isLoadingSchemas = false;
-
+
// Custom query functionality
private bool useCustomQuery = false;
private string customQuery = "";
@@ -90,42 +90,42 @@ public partial class DataCoupler : ComponentBase
private Dictionary> fileSheets = new(); // SheetName -> Columns
private Dictionary>> fileData = new(); // SheetName -> Data rows
private string selectedSheet = "";
-
+
// File preview pagination
private int currentPage = 1;
private int pageSize = 20;
- private int GetTotalPages(string sheetName) => fileData.ContainsKey(sheetName) ?
+ private int GetTotalPages(string sheetName) => fileData.ContainsKey(sheetName) ?
(int)Math.Ceiling((double)fileData[sheetName].Count / pageSize) : 0;
-
+
// REST discovery
private List restEntities = new();
private RestEntitySummary? selectedRestEntity = null;
private RestEntityInfo? restEntityDetails = null;
private string restSearchTerm = "";
- // Mapping campi
+ // Mapping campi
private Dictionary fieldMappings = new(); // DbColumn -> RestProperty
private HashSet keyFields = new(); // REST properties marked as keys
private string selectedDbColumn = "";
private string selectedRestProperty = "";
-
+
// Gestione chiavi sorgente e associazioni
private string sourceKeyField = ""; // Campo che identifica univocamente il record sorgente
private string suggestedPrimaryKey = ""; // Campo PK suggerito per database
private bool requiresManualKeySelection = false; // Flag per indicare se è richiesta selezione manuale
private bool useRecordAssociations = true; // Se utilizzare il sistema di associazioni
-
+
// Trasferimento dati
private bool isTransferringData = false;
private string transferMessage = "";
private string transferMessageType = "";
private List transferResults = new();
private bool showDetailedResults = false;
-
+
// Servizi
private IDatabaseManager? currentDatabaseManager = null;
private IRestMetadataDiscovery? currentRestDiscovery = null;
private IRestServiceClient? currentRestClient = null;
-
+
// Gestione Profili
private List availableProfiles = new();
private bool isLoadingProfiles = false;
@@ -134,7 +134,8 @@ public partial class DataCoupler : ComponentBase
protected override async Task OnInitializedAsync()
{
await LoadCredentials();
- } private async Task LoadCredentials()
+ }
+ private async Task LoadCredentials()
{
try
{
@@ -178,7 +179,7 @@ public partial class DataCoupler : ComponentBase
// Applica la configurazione del profilo
await ApplyProfileConfiguration(profile);
-
+
// Ricarica i profili per aggiornare la data di ultimo utilizzo
await LoadProfiles();
}
@@ -193,34 +194,34 @@ public partial class DataCoupler : ComponentBase
{
Logger.LogInformation("=== INIZIO APPLICAZIONE PROFILO ===");
Logger.LogInformation("Applicando configurazione profilo: {ProfileName}", profile.Name);
- Logger.LogInformation("Profilo - SourceType: {SourceType}, SourceCredentialId: {SourceCredentialId}, DestinationCredentialId: {DestinationCredentialId}",
+ Logger.LogInformation("Profilo - SourceType: {SourceType}, SourceCredentialId: {SourceCredentialId}, DestinationCredentialId: {DestinationCredentialId}",
profile.SourceType, profile.SourceCredentialId, profile.DestinationCredentialId);
-
+
try
{
// Step 0: Log dello stato iniziale
- Logger.LogInformation("Stato iniziale - SelectedSourceType: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}",
+ Logger.LogInformation("Stato iniziale - SelectedSourceType: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}",
selectedSourceType, isDatabaseConnected, isRestConnected);
-
+
// Reset dello stato corrente
Logger.LogInformation("Resettando stato corrente...");
ResetAllState();
- Logger.LogInformation("Stato dopo reset - SelectedSourceType: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}",
+ Logger.LogInformation("Stato dopo reset - SelectedSourceType: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}",
selectedSourceType, isDatabaseConnected, isRestConnected);
-
+
// Step 1: Applica configurazione sorgente
selectedSourceType = profile.SourceType;
Logger.LogInformation("Step 1 - Tipo sorgente impostato: {SourceType}", selectedSourceType);
-
+
// Force UI update for source type change
StateHasChanged();
await Task.Delay(100); // Give UI time to react to source type change
-
+
// Step 2: Configura e connetti la sorgente
if (profile.SourceCredentialId.HasValue)
{
Logger.LogInformation("Step 2 - Configurazione sorgente con ID credenziale: {CredentialId}", profile.SourceCredentialId);
-
+
if (profile.SourceType == "database")
{
var sourceCredential = await CredentialService.GetDatabaseCredentialAsync(profile.SourceCredentialId.Value);
@@ -228,38 +229,45 @@ public partial class DataCoupler : ComponentBase
{
selectedDatabaseCredential = sourceCredential.Name;
Logger.LogInformation("Credenziale database selezionata: {Credential}", selectedDatabaseCredential);
-
+
// Force UI update for credential selection
StateHasChanged();
await Task.Delay(200);
-
+
// Connetti al database
Logger.LogInformation("Iniziando connessione database...");
- if (!string.IsNullOrEmpty(profile.SourceSchema))
+
+ // Gestione connessione con database specifico
+ if (!string.IsNullOrEmpty(profile.SourceDatabaseName))
+ {
+ Logger.LogInformation("Connessione con database specifico: {Database}", profile.SourceDatabaseName);
+ await ConnectToDatabaseWithSpecificDatabase(profile.SourceDatabaseName);
+ }
+ else if (!string.IsNullOrEmpty(profile.SourceSchema))
{
Logger.LogInformation("Connessione con schema specifico: {Schema}", profile.SourceSchema);
await ConnectToDatabaseWithSchema(profile.SourceSchema);
}
else
{
- Logger.LogInformation("Connessione senza schema specifico");
+ Logger.LogInformation("Connessione senza database/schema specifico");
await ConnectToDatabase();
}
-
- Logger.LogInformation("Stato dopo connessione database - Connected: {Connected}, Tables: {TableCount}",
+
+ Logger.LogInformation("Stato dopo connessione database - Connected: {Connected}, Tables: {TableCount}",
isDatabaseConnected, availableTableNames.Count);
-
+
// Seleziona la tabella se specificata e se la connessione è riuscita
if (!string.IsNullOrEmpty(profile.SourceTable) && isDatabaseConnected)
{
Logger.LogInformation("Selezione tabella: {Table}", profile.SourceTable);
await SelectTable(profile.SourceTable);
- Logger.LogInformation("Tabella selezionata: {SelectedTable}, Schema caricato: {SchemaLoaded}",
+ Logger.LogInformation("Tabella selezionata: {SelectedTable}, Schema caricato: {SchemaLoaded}",
selectedTable, databaseTables.ContainsKey(profile.SourceTable));
}
else
{
- Logger.LogWarning("Impossibile selezionare tabella - Table: {Table}, Connected: {Connected}",
+ Logger.LogWarning("Impossibile selezionare tabella - Table: {Table}, Connected: {Connected}",
profile.SourceTable, isDatabaseConnected);
}
}
@@ -282,32 +290,32 @@ public partial class DataCoupler : ComponentBase
{
Logger.LogInformation("Nessuna credenziale sorgente da configurare");
}
-
+
// Small delay to let source configuration settle
await Task.Delay(300);
-
+
// Step 3: Configura e connetti la destinazione
if (profile.DestinationCredentialId.HasValue)
{
Logger.LogInformation("Step 3 - Configurazione destinazione con ID credenziale: {CredentialId}", profile.DestinationCredentialId);
-
+
var destinationCredential = await CredentialService.GetRestApiCredentialAsync(profile.DestinationCredentialId.Value);
if (destinationCredential != null)
{
selectedRestCredential = destinationCredential.Name;
Logger.LogInformation("Credenziale REST selezionata: {Credential}", selectedRestCredential);
-
+
// Force UI update for REST credential selection
StateHasChanged();
await Task.Delay(200);
-
+
// Connetti al servizio REST
Logger.LogInformation("Iniziando connessione REST...");
await ConnectToRestApi();
-
- Logger.LogInformation("Stato dopo connessione REST - Connected: {Connected}, Entities: {EntityCount}",
+
+ Logger.LogInformation("Stato dopo connessione REST - Connected: {Connected}, Entities: {EntityCount}",
isRestConnected, restEntities.Count);
-
+
// Seleziona l'entità REST se la connessione è riuscita
if (!string.IsNullOrEmpty(profile.DestinationEndpoint) && isRestConnected)
{
@@ -316,18 +324,18 @@ public partial class DataCoupler : ComponentBase
{
Logger.LogInformation("Selezione entità REST: {Entity}", entity.Name);
await SelectRestEntity(entity);
- Logger.LogInformation("Entità REST selezionata: {SelectedEntity}, Dettagli caricati: {DetailsLoaded}",
+ Logger.LogInformation("Entità REST selezionata: {SelectedEntity}, Dettagli caricati: {DetailsLoaded}",
selectedRestEntity?.Name, restEntityDetails != null);
}
else
{
- Logger.LogWarning("Entità REST non trovata: {Endpoint} - Entities disponibili: {Entities}",
+ Logger.LogWarning("Entità REST non trovata: {Endpoint} - Entities disponibili: {Entities}",
profile.DestinationEndpoint, string.Join(", ", restEntities.Select(e => e.Name)));
}
}
else
{
- Logger.LogWarning("Impossibile selezionare entità REST - Endpoint: {Endpoint}, Connected: {Connected}",
+ Logger.LogWarning("Impossibile selezionare entità REST - Endpoint: {Endpoint}, Connected: {Connected}",
profile.DestinationEndpoint, isRestConnected);
}
}
@@ -352,13 +360,13 @@ public partial class DataCoupler : ComponentBase
{
var service = new DataCouplerProfileService(null!);
var mappings = service.DeserializeFieldMappings(profile.FieldMappingJson);
-
+
Logger.LogInformation("Mappings deserializzati: {Count}", mappings.Count);
-
+
// Applica i mapping
fieldMappings.Clear();
keyFields.Clear();
-
+
foreach (var mapping in mappings)
{
fieldMappings[mapping.SourceField] = mapping.DestinationField;
@@ -366,11 +374,11 @@ public partial class DataCoupler : ComponentBase
{
keyFields.Add(mapping.DestinationField);
}
- Logger.LogInformation("Mapping applicato: {Source} -> {Destination} (IsKey: {IsKey})",
+ Logger.LogInformation("Mapping applicato: {Source} -> {Destination} (IsKey: {IsKey})",
mapping.SourceField, mapping.DestinationField, mapping.IsKey);
}
-
- Logger.LogInformation("Mappings applicati - Totale: {MappingCount}, Chiavi: {KeyCount}",
+
+ Logger.LogInformation("Mappings applicati - Totale: {MappingCount}, Chiavi: {KeyCount}",
fieldMappings.Count, keyFields.Count);
}
catch (Exception ex)
@@ -399,7 +407,7 @@ public partial class DataCoupler : ComponentBase
Logger.LogInformation("Step 6 - Associazioni record configurate: {UseAssociations}", useRecordAssociations);
Logger.LogInformation("=== FINE APPLICAZIONE PROFILO ===");
- Logger.LogInformation("Stato finale - Source: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}, Mappings: {MappingCount}",
+ Logger.LogInformation("Stato finale - Source: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}, Mappings: {MappingCount}",
selectedSourceType, isDatabaseConnected, isRestConnected, fieldMappings.Count);
}
catch (Exception ex)
@@ -420,26 +428,26 @@ public partial class DataCoupler : ComponentBase
try
{
Logger.LogInformation("Tentativo di salvataggio profilo: {ProfileName}", profileDto.Name);
-
+
var profileService = new DataCouplerProfileService(null!); // Usa il service di conversione
var profile = profileService.FromDto(profileDto, "System"); // TODO: Usa utente corrente
-
+
// Controlla se esiste già un profilo con lo stesso nome (inclusi quelli inattivi)
Logger.LogInformation("Controllo esistenza profilo con nome: {ProfileName}", profileDto.Name);
var existingProfile = await ProfileService.GetProfileByNameIncludingInactiveAsync(profileDto.Name);
-
+
if (existingProfile != null)
{
- Logger.LogInformation("Trovato profilo esistente con ID: {ProfileId}, IsActive: {IsActive}",
+ Logger.LogInformation("Trovato profilo esistente con ID: {ProfileId}, IsActive: {IsActive}",
existingProfile.Id, existingProfile.IsActive);
-
+
if (!existingProfile.IsActive)
{
// Il profilo esiste ma è inattivo - riattivalo e aggiornalo
Logger.LogInformation("Riattivazione del profilo inattivo: {ProfileName}", profileDto.Name);
profile.Id = existingProfile.Id;
profile.IsActive = true;
-
+
// Aggiorna direttamente il profilo esistente invece di creare un nuovo oggetto
existingProfile.Description = profile.Description;
existingProfile.SourceType = profile.SourceType;
@@ -456,22 +464,22 @@ public partial class DataCoupler : ComponentBase
existingProfile.SourceKeyField = profile.SourceKeyField;
existingProfile.UseRecordAssociations = profile.UseRecordAssociations;
existingProfile.IsActive = true;
-
+
await ProfileService.UpdateProfileAsync(existingProfile);
await LoadProfiles();
-
+
await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' riattivato e aggiornato con successo!");
return;
}
-
+
// Il profilo esiste ed è attivo - chiedi conferma per sovrascrittura
- var confirmOverwrite = await JSRuntime.InvokeAsync("confirm",
+ var confirmOverwrite = await JSRuntime.InvokeAsync("confirm",
$"Esiste già un profilo attivo con il nome '{profileDto.Name}'. Vuoi sovrascriverlo?");
-
+
if (confirmOverwrite)
{
Logger.LogInformation("Utente ha confermato la sovrascrittura del profilo: {ProfileName}", profileDto.Name);
-
+
// Aggiorna il profilo esistente direttamente
existingProfile.Description = profile.Description;
existingProfile.SourceType = profile.SourceType;
@@ -487,40 +495,40 @@ public partial class DataCoupler : ComponentBase
existingProfile.FieldMappingJson = profile.FieldMappingJson;
existingProfile.SourceKeyField = profile.SourceKeyField;
existingProfile.UseRecordAssociations = profile.UseRecordAssociations;
-
+
await ProfileService.UpdateProfileAsync(existingProfile);
await LoadProfiles(); // Ricarica la lista
-
+
await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' aggiornato con successo!");
}
else
{
Logger.LogInformation("Utente ha annullato la sovrascrittura del profilo: {ProfileName}", profileDto.Name);
-
+
// Proponi di creare con un nome unico
- var useUniqueName = await JSRuntime.InvokeAsync("confirm",
+ var useUniqueName = await JSRuntime.InvokeAsync("confirm",
"Vuoi salvare il profilo con un nome unico automatico (es. 'Nome Profilo (1)')?");
-
+
if (useUniqueName)
{
var uniqueName = await GenerateUniqueProfileName(profileDto.Name);
profile.Name = uniqueName;
-
+
try
{
await ProfileService.SaveProfileAsync(profile);
await LoadProfiles();
-
+
await JSRuntime.InvokeVoidAsync("alert", $"Profilo salvato con nome '{uniqueName}'!");
}
catch (Exception uniqueEx)
{
Logger.LogError(uniqueEx, "Errore durante il salvataggio del profilo con nome unico: {UniqueName}", uniqueName);
-
+
// Gestisci l'errore di unique constraint anche per il nome unico
if (uniqueEx.Message.Contains("UNIQUE constraint failed"))
{
- await JSRuntime.InvokeVoidAsync("alert",
+ await JSRuntime.InvokeVoidAsync("alert",
$"Errore: Non è stato possibile generare un nome unico per il profilo. " +
"Prova a ricaricare la pagina e riprova.");
}
@@ -537,29 +545,29 @@ public partial class DataCoupler : ComponentBase
else
{
Logger.LogInformation("Nessun profilo esistente trovato, creazione nuovo profilo: {ProfileName}", profileDto.Name);
-
+
// Crea un nuovo profilo
try
{
await ProfileService.SaveProfileAsync(profile);
await LoadProfiles(); // Ricarica la lista
-
+
await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' salvato con successo!");
}
catch (Exception saveEx)
{
Logger.LogError(saveEx, "Errore durante il salvataggio del nuovo profilo: {ProfileName}", profileDto.Name);
-
+
// Possibile race condition - riprova con controllo duplicato
if (saveEx.Message.Contains("UNIQUE constraint failed"))
{
Logger.LogWarning("Race condition rilevata durante il salvataggio, gestione del duplicato...");
-
+
// Chiedi se vuole sovrascrivere o creare nome unico
- var handleDuplicate = await JSRuntime.InvokeAsync("confirm",
+ var handleDuplicate = await JSRuntime.InvokeAsync("confirm",
$"Un profilo con il nome '{profileDto.Name}' è stato creato nel frattempo. " +
"Vuoi sovrascriverlo? (Clicca 'Annulla' per salvare con un nome unico)");
-
+
if (handleDuplicate)
{
// Trova il profilo e aggiornalo
@@ -569,7 +577,7 @@ public partial class DataCoupler : ComponentBase
profile.Id = duplicateProfile.Id;
await ProfileService.UpdateProfileAsync(profile);
await LoadProfiles();
-
+
await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' aggiornato con successo!");
}
else
@@ -582,10 +590,10 @@ public partial class DataCoupler : ComponentBase
// Crea con nome unico
var uniqueName = await GenerateUniqueProfileName(profileDto.Name);
profile.Name = uniqueName;
-
+
await ProfileService.SaveProfileAsync(profile);
await LoadProfiles();
-
+
await JSRuntime.InvokeVoidAsync("alert", $"Profilo salvato con nome '{uniqueName}'!");
}
}
@@ -599,11 +607,11 @@ public partial class DataCoupler : ComponentBase
catch (Exception ex)
{
Logger.LogError(ex, "Errore generale nel salvataggio del profilo: {ProfileName}", profileDto.Name);
-
+
// Gestione generica degli errori
if (ex.Message.Contains("UNIQUE constraint failed"))
{
- await JSRuntime.InvokeVoidAsync("alert",
+ await JSRuntime.InvokeVoidAsync("alert",
$"Errore: Esiste già un profilo con il nome '{profileDto.Name}'. " +
"Questo può accadere se ci sono stati problemi di sincronizzazione. " +
"Prova a ricaricare la pagina e riprova.");
@@ -644,7 +652,7 @@ public partial class DataCoupler : ComponentBase
private bool CanSaveProfile()
{
- return !string.IsNullOrEmpty(selectedSourceType) &&
+ return !string.IsNullOrEmpty(selectedSourceType) &&
(!string.IsNullOrEmpty(selectedDatabaseCredential) || !string.IsNullOrEmpty(selectedRestCredential)) &&
(!string.IsNullOrEmpty(selectedRestCredential) || !string.IsNullOrEmpty(selectedTable));
}
@@ -652,7 +660,7 @@ public partial class DataCoupler : ComponentBase
private List GetCurrentFieldMappings()
{
var mappings = new List();
-
+
foreach (var mapping in fieldMappings)
{
mappings.Add(new FieldMappingDto
@@ -664,7 +672,7 @@ public partial class DataCoupler : ComponentBase
DataType = "", // TODO: Determina dai metadati
});
}
-
+
return mappings;
}
@@ -691,17 +699,19 @@ public partial class DataCoupler : ComponentBase
currentRestDiscovery = null;
currentRestClient = null;
}
-
+
private void OnSourceTypeChanged(ChangeEventArgs e)
{
selectedSourceType = e.Value?.ToString() ?? "";
-
+
// Reset state when changing source type
- ResetSourceState(); } private void ResetSourceState()
+ ResetSourceState();
+ }
+ private void ResetSourceState()
{
// Reset database state
ResetDatabaseState();
-
+
// Reset file state
selectedFileName = "";
isProcessingFile = false;
@@ -709,26 +719,27 @@ public partial class DataCoupler : ComponentBase
fileSheets.Clear();
fileData.Clear();
selectedSheet = "";
-
+
// Reset pagination
currentPage = 1;
-
+
// Reset mappings
ClearAllMappings();
}
private async Task OnFileSelected(InputFileChangeEventArgs e)
- { try
+ {
+ 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")
@@ -736,7 +747,7 @@ public partial class DataCoupler : ComponentBase
fileErrorMessage = "Formato file non supportato. Utilizzare Excel (.xlsx, .xls) o CSV (.csv)";
return;
}
-
+
// Process file based on type
if (extension == ".csv")
{
@@ -757,75 +768,77 @@ public partial class DataCoupler : ComponentBase
isProcessingFile = false;
StateHasChanged();
}
- } private async Task ProcessCsvFile(IBrowserFile file)
+ }
+ 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));
+
+ Logger.LogInformation("CSV headers parsed: {Headers}", string.Join(" | ", headers));
// For CSV, we create a single "sheet" with the filename
var sheetName = Path.GetFileNameWithoutExtension(file.Name);
fileSheets[sheetName] = headers;
-
+
// Read data rows - rimuovo il limite di 1000 righe
var dataRows = new List>();
string? line;
int rowNumber = 2; // Starting from row 2 (after header)
-
+
while ((line = await reader.ReadLineAsync()) != null)
{
if (string.IsNullOrWhiteSpace(line)) continue;
-
+
var values = ParseCsvLine(line, separator);
var row = new Dictionary();
- for (int i = 0; i < headers.Count; i++)
+ 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;
-
+ 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}",
+
+ Logger.LogInformation("CSV file processed: {FileName}, Headers: {HeaderCount} ({Headers}), Rows: {RowCount}, Auto-selected sheet: {SheetName}",
file.Name, headers.Count, string.Join(", ", headers), dataRows.Count, selectedSheet);
- } private List ParseCsvLine(string line, char separator = ',')
+ }
+ private List ParseCsvLine(string line, char separator = ',')
{
var result = new List();
var current = new StringBuilder();
bool inQuotes = false;
-
+
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
-
+
if (c == '"')
{
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
@@ -851,21 +864,22 @@ public partial class DataCoupler : ComponentBase
current.Append(c);
}
}
-
+
// Add the last field
result.Add(current.ToString().Trim());
-
+
return result;
- }private async Task ProcessExcelFile(IBrowserFile file)
+ }
+ 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);
@@ -893,8 +907,8 @@ public partial class DataCoupler : ComponentBase
// Converti in DataSet
var dataSet = reader.AsDataSet(configuration);
-
- Logger.LogInformation("Excel file processed: {FileName}, Sheets: {SheetCount}",
+
+ Logger.LogInformation("Excel file processed: {FileName}, Sheets: {SheetCount}",
file.Name, dataSet.Tables.Count);
// Processa ogni foglio
@@ -910,7 +924,7 @@ public partial class DataCoupler : ComponentBase
headers.Add(column.ColumnName);
}
- Logger.LogInformation("Processing Excel sheet: {SheetName}, Columns: {ColumnCount}, Rows: {RowCount}",
+ Logger.LogInformation("Processing Excel sheet: {SheetName}, Columns: {ColumnCount}, Rows: {RowCount}",
sheetName, headers.Count, table.Rows.Count);
// Processa le righe di dati
@@ -930,7 +944,7 @@ public partial class DataCoupler : ComponentBase
// Log delle prime 3 righe per debug
if (i < 3)
{
- Logger.LogInformation("Excel row {RowNumber} in {Sheet}: {Values}",
+ Logger.LogInformation("Excel row {RowNumber} in {Sheet}: {Values}",
i + 1, sheetName, string.Join(" | ", rowData.Values));
}
}
@@ -939,7 +953,7 @@ public partial class DataCoupler : ComponentBase
fileSheets[sheetName] = headers;
fileData[sheetName] = dataRows;
- Logger.LogInformation("Excel sheet completed: {SheetName}, Headers: {Headers}, Rows: {RowCount}",
+ Logger.LogInformation("Excel sheet completed: {SheetName}, Headers: {Headers}, Rows: {RowCount}",
sheetName, string.Join(", ", headers), dataRows.Count);
}
@@ -948,7 +962,8 @@ public partial class DataCoupler : ComponentBase
{
selectedSheet = fileSheets.First().Key;
Logger.LogInformation("Auto-selected first sheet: {SheetName}", selectedSheet);
- } Logger.LogInformation("Excel file processing completed: {FileName}, Total sheets: {SheetCount}, Selected: {SelectedSheet}",
+ }
+ Logger.LogInformation("Excel file processing completed: {FileName}, Total sheets: {SheetCount}, Selected: {SelectedSheet}",
file.Name, fileSheets.Count, selectedSheet);
}
}
@@ -957,30 +972,31 @@ public partial class DataCoupler : ComponentBase
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)
+ }
+ private void SelectSheet(string sheetName)
{
selectedSheet = sheetName;
-
+
// Reset pagination when changing sheet
currentPage = 1;
-
+
// Clear mappings when changing sheet
ClearAllMappings();
-
+
// For file sources, try auto-selection and then require manual key selection if not found
sourceKeyField = "";
suggestedPrimaryKey = "";
requiresManualKeySelection = true;
-
+
// AUTO-SELECT della chiave per i file
if (fileSheets.ContainsKey(sheetName))
{
var columns = fileSheets[sheetName].ToList();
TryAutoSelectKeyForFile(columns);
}
-
+
StateHasChanged();
}
@@ -1018,7 +1034,8 @@ public partial class DataCoupler : ComponentBase
if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet))
return 0;
return (currentPage - 1) * pageSize + 1;
- } private int GetEndRecord()
+ }
+ private int GetEndRecord()
{
if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet))
return 0;
@@ -1035,37 +1052,40 @@ public partial class DataCoupler : ComponentBase
currentPage = 1; // Reset to first page when changing page size
StateHasChanged();
}
- }private void OnDatabaseCredentialChanged(ChangeEventArgs e)
+ }
+ private void OnDatabaseCredentialChanged(ChangeEventArgs e)
{
selectedDatabaseCredential = e.Value?.ToString() ?? "";
ResetDatabaseState();
- } private void OnRestCredentialChanged(ChangeEventArgs e)
+ }
+ 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()
+ }
+ 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 = "";
@@ -1076,13 +1096,14 @@ public partial class DataCoupler : ComponentBase
queryColumns.Clear();
showQueryPreview = false;
isLoadingPreview = false;
-
+
currentDatabaseManager?.Dispose();
currentDatabaseManager = null;
-
+
// Clear mappings when resetting database state
ClearAllMappings();
- } private void ResetRestState()
+ }
+ private void ResetRestState()
{
isRestConnected = false;
restEntities.Clear();
@@ -1092,17 +1113,18 @@ public partial class DataCoupler : ComponentBase
restErrorMessage = "";
currentRestDiscovery = null;
currentRestClient = null;
-
+
// Clear mappings when resetting REST state
ClearAllMappings();
- } private async Task ConnectToDatabase()
+ }
+ private async Task ConnectToDatabase()
{
if (string.IsNullOrEmpty(selectedDatabaseCredential))
return;
isConnectingDatabase = true;
databaseErrorMessage = "";
-
+
try
{
// Trova la credenziale
@@ -1125,10 +1147,10 @@ public partial class DataCoupler : ComponentBase
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.");
@@ -1150,7 +1172,7 @@ public partial class DataCoupler : ComponentBase
{
Logger.LogInformation("Database non specificato nella connection string. Caricando database disponibili.");
await LoadAvailableDatabases();
-
+
if (availableDatabases.Any())
{
Logger.LogInformation("Trovati {DatabaseCount} database disponibili", availableDatabases.Count);
@@ -1175,14 +1197,15 @@ public partial class DataCoupler : ComponentBase
isConnectingDatabase = false;
StateHasChanged();
}
- }private async Task ConnectToRestApi()
+ }
+ private async Task ConnectToRestApi()
{
if (string.IsNullOrEmpty(selectedRestCredential))
return;
isConnectingRest = true;
restErrorMessage = "";
-
+
try
{
// Trova la credenziale
@@ -1201,7 +1224,7 @@ public partial class DataCoupler : ComponentBase
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);
+ 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();
@@ -1216,9 +1239,9 @@ public partial class DataCoupler : ComponentBase
// 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");
@@ -1237,10 +1260,11 @@ public partial class DataCoupler : ComponentBase
{
isConnectingRest = false;
}
- } private async Task SelectTable(string tableName)
+ }
+ private async Task SelectTable(string tableName)
{
selectedTable = tableName;
-
+
// Clear custom query state when selecting a table
useCustomQuery = false;
customQuery = "";
@@ -1249,15 +1273,15 @@ public partial class DataCoupler : ComponentBase
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)
{
@@ -1272,7 +1296,7 @@ public partial class DataCoupler : ComponentBase
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)
{
@@ -1282,11 +1306,11 @@ public partial class DataCoupler : ComponentBase
if (!string.IsNullOrEmpty(primaryKey))
{
suggestedPrimaryKey = primaryKey;
-
+
// AUTO-SELECT: Imposta automaticamente il campo chiave se rilevato
sourceKeyField = primaryKey;
requiresManualKeySelection = false;
-
+
Logger.LogInformation("Chiave primaria rilevata e auto-selezionata per la tabella {TableName}: {PrimaryKey}", tableName, primaryKey);
}
else
@@ -1310,23 +1334,24 @@ public partial class DataCoupler : ComponentBase
requiresManualKeySelection = true;
sourceKeyField = "";
}
-
+
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); }
+ restEntityDetails = await currentRestDiscovery.DiscoverEntityDetailsAsync(entity.Name);
+ }
else
{
restErrorMessage = "Servizio di discovery REST non disponibile";
@@ -1337,15 +1362,16 @@ public partial class DataCoupler : ComponentBase
{
Logger.LogError(ex, "Errore nel caricamento dettagli entità {EntityName}", entity.Name);
restErrorMessage = $"Errore nel caricamento dettagli entità: {ex.Message}";
- } }
+ }
+ }
// Metodi per la ricerca e il filtraggio
private IEnumerable GetFilteredDatabaseTables()
{
if (string.IsNullOrEmpty(databaseSearchTerm))
return availableTableNames;
-
- return availableTableNames.Where(table =>
+
+ return availableTableNames.Where(table =>
table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase));
}
@@ -1353,8 +1379,8 @@ public partial class DataCoupler : ComponentBase
{
if (string.IsNullOrEmpty(restSearchTerm))
return restEntities;
-
- return restEntities.Where(entity =>
+
+ return restEntities.Where(entity =>
entity.Name.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase) ||
(!string.IsNullOrEmpty(entity.Label) && entity.Label.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase)));
}
@@ -1407,9 +1433,9 @@ public partial class DataCoupler : ComponentBase
// Crea il nuovo mapping
fieldMappings[selectedDbColumn] = selectedRestProperty;
-
+
Logger.LogInformation("Creato mapping: {DbColumn} -> {RestProperty}", selectedDbColumn, selectedRestProperty);
-
+
// Deseleziona i campi
selectedDbColumn = "";
selectedRestProperty = "";
@@ -1422,7 +1448,8 @@ public partial class DataCoupler : ComponentBase
fieldMappings.Remove(selectedDbColumn);
Logger.LogInformation("Rimosso mapping per campo: {DbColumn}", selectedDbColumn);
- } private void RemoveSpecificMapping(string dbColumn)
+ }
+ private void RemoveSpecificMapping(string dbColumn)
{
if (fieldMappings.ContainsKey(dbColumn))
{
@@ -1448,7 +1475,7 @@ public partial class DataCoupler : ComponentBase
return;
IEnumerable sourceColumns = new List();
-
+
// Ottiene le colonne in base al tipo di sorgente
if (selectedSourceType == "database")
{
@@ -1475,7 +1502,7 @@ public partial class DataCoupler : ComponentBase
foreach (var sourceColumn in sourceColumns)
{
// Trova una proprietà REST con nome simile
- var matchingProperty = restProperties.FirstOrDefault(p =>
+ 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)
@@ -1486,11 +1513,12 @@ public partial class DataCoupler : ComponentBase
fieldMappings[sourceColumn] = matchingProperty.Name;
mappingsCreated++;
}
- }
+ }
- Logger.LogInformation("Auto-mapping completato. Creati {Count} mapping automatici da {SourceType}",
+ 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";
summary += "=== MAPPING CAMPI ===\n";
@@ -1498,16 +1526,17 @@ public partial class DataCoupler : ComponentBase
{
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()
+ }
+ private async Task StartDataTransfer()
{
if (!fieldMappings.Any() || currentRestClient == null || selectedRestEntity == null)
{
@@ -1525,7 +1554,7 @@ public partial class DataCoupler : ComponentBase
transferMessageType = "error";
return;
}
-
+
if (useCustomQuery)
{
if (!isQueryValid || string.IsNullOrWhiteSpace(customQuery))
@@ -1542,14 +1571,14 @@ public partial class DataCoupler : ComponentBase
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))
{
@@ -1565,10 +1594,10 @@ public partial class DataCoupler : ComponentBase
try
{
- var sourceName = selectedSourceType == "database"
+ 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);
// 1. Ottieni tutti i record dalla fonte dati
@@ -1590,7 +1619,7 @@ public partial class DataCoupler : ComponentBase
.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));
}
@@ -1610,38 +1639,38 @@ public partial class DataCoupler : ComponentBase
RecordNumber = recordNumber,
RecordData = new Dictionary(record)
};
-
+
try
{
// Trasforma il record in base ai mapping
var restData = TransformRecordToRestEntity(record);
-
+
// Genera la chiave sorgente per questo record
var sourceKey = GenerateSourceKey(record);
-
+
// NUOVO SISTEMA: Cerca associazione esistente basata sul valore della chiave
if (useRecordAssociations && !string.IsNullOrEmpty(sourceKey))
{
- Logger.LogInformation("ASSOCIATION DEBUG: Cerco associazione - KeyValue: '{KeyValue}', Entity: '{Entity}', Credential: '{Credential}'",
+ 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}'",
+ 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 ||
+ if (existingAssociation.DestinationEntity != selectedRestEntity.Name ||
existingAssociation.RestCredentialName != selectedRestCredential)
{
Logger.LogWarning("ASSOCIATION DEBUG: Associazione non compatibile - Entity: '{FoundEntity}' vs '{ExpectedEntity}', Credential: '{FoundCredential}' vs '{ExpectedCredential}'",
@@ -1650,35 +1679,35 @@ public partial class DataCoupler : ComponentBase
}
}
}
-
- Logger.LogInformation("ASSOCIATION DEBUG: Associazione finale: {Found}. ID: {AssociationId}, DestinationId: '{DestinationId}', IsActive: {IsActive}",
+
+ 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}",
+
+ Logger.LogInformation("ASSOCIATION DEBUG: Record aggiornato con successo tramite associazione: {EntityId} per valore chiave {KeyValue}",
existingAssociation.DestinationId, sourceKey);
-
+
transferResults.Add(transferResult);
recordNumber++;
continue;
@@ -1696,8 +1725,8 @@ public partial class DataCoupler : ComponentBase
Logger.LogWarning(updateEx, "ASSOCIATION DEBUG: Aggiornamento fallito per associazione {AssociationId} - elimino associazione e creo nuovo record", existingAssociation.Id);
goto HandleInvalidAssociation;
}
-
- HandleInvalidAssociation:
+
+ HandleInvalidAssociation:
// L'ID di destinazione non esiste più o l'update è fallito - elimina l'associazione non valida
try
{
@@ -1708,26 +1737,26 @@ public partial class DataCoupler : ComponentBase
{
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() :
+ 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))
{
@@ -1735,7 +1764,7 @@ public partial class DataCoupler : ComponentBase
{
// Determina i campi chiave automaticamente
var destinationKeyField = GetEntityIdField(); // Campo chiave nella destinazione
-
+
var association = new KeyAssociation
{
KeyValue = sourceKey,
@@ -1754,10 +1783,10 @@ public partial class DataCoupler : ComponentBase
SourceType = selectedSourceType
})
};
-
- Logger.LogInformation("ASSOCIATION DEBUG: Creazione nuova associazione - KeyValue: '{KeyValue}', Entity: '{Entity}', DestinationId: '{DestinationId}', Credential: '{Credential}'",
+
+ 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);
}
@@ -1767,7 +1796,7 @@ public partial class DataCoupler : ComponentBase
// 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
@@ -1786,7 +1815,7 @@ public partial class DataCoupler : ComponentBase
errors.Add($"Errore nel trasferimento del record {recordNumber}: {ex.Message}");
Logger.LogError(ex, "Errore nel trasferimento del record {RecordNumber}", recordNumber);
}
-
+
transferResults.Add(transferResult);
recordNumber++;
}
@@ -1796,11 +1825,11 @@ public partial class DataCoupler : ComponentBase
{
var message = $"Trasferimento completato con successo! ";
var messageParts = new List();
-
+
if (successCount > 0) messageParts.Add($"{successCount} record inseriti");
if (updatedCount > 0) messageParts.Add($"{updatedCount} record aggiornati");
if (duplicateCount > 0) messageParts.Add($"{duplicateCount} duplicati rilevati (warning)");
-
+
message += string.Join(", ", messageParts) + ".";
transferMessage = message;
transferMessageType = "success";
@@ -1809,12 +1838,12 @@ public partial class DataCoupler : ComponentBase
{
var message = $"Trasferimento completato con {(duplicateCount > 0 ? "warning e " : "")}errori. ";
var messageParts = new List();
-
+
if (successCount > 0) messageParts.Add($"Inserimenti: {successCount}");
if (updatedCount > 0) messageParts.Add($"Aggiornamenti: {updatedCount}");
if (duplicateCount > 0) messageParts.Add($"Duplicati (warning): {duplicateCount}");
messageParts.Add($"Errori: {errorCount}");
-
+
message += string.Join(", ", messageParts);
if (errors.Any())
{
@@ -1824,7 +1853,7 @@ public partial class DataCoupler : ComponentBase
transferMessageType = errorCount > 0 ? "error" : "warning";
}
- Logger.LogInformation("Trasferimento completato. Inserimenti: {SuccessCount}, Aggiornamenti: {UpdatedCount}, Duplicati: {DuplicateCount}, Errori: {ErrorCount}",
+ Logger.LogInformation("Trasferimento completato. Inserimenti: {SuccessCount}, Aggiornamenti: {UpdatedCount}, Duplicati: {DuplicateCount}, Errori: {ErrorCount}",
successCount, updatedCount, duplicateCount, errorCount);
}
catch (Exception ex)
@@ -1837,7 +1866,8 @@ public partial class DataCoupler : ComponentBase
{
isTransferringData = false;
}
- } private async Task>> GetAllRecordsFromSource()
+ }
+ private async Task>> GetAllRecordsFromSource()
{
if (selectedSourceType == "database")
{
@@ -1847,7 +1877,7 @@ public partial class DataCoupler : ComponentBase
{
return await GetAllRecordsFromFile();
}
-
+
return new List>();
}
@@ -1865,16 +1895,16 @@ public partial class DataCoupler : ComponentBase
{
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
@@ -1884,23 +1914,24 @@ public partial class DataCoupler : ComponentBase
{
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}",
+ Logger.LogError(ex, "Errore nell'ottenere i record dal database. UseCustomQuery: {UseCustomQuery}, Table: {Table}, Query: {Query}",
useCustomQuery, selectedTable, useCustomQuery ? customQuery : "N/A");
throw;
}
- } private async Task>> GetAllRecordsFromFile()
+ }
+ private async Task>> GetAllRecordsFromFile()
{
if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet))
{
return new List>();
}
-
+
await Task.CompletedTask;
return fileData[selectedSheet];
}
@@ -1917,10 +1948,10 @@ public partial class DataCoupler : ComponentBase
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;
@@ -1928,8 +1959,8 @@ public partial class DataCoupler : ComponentBase
}
}
- Logger.LogDebug("Record trasformato: {DbColumns} → {RestProperties}",
- string.Join(", ", dbRecord.Keys),
+ Logger.LogDebug("Record trasformato: {DbColumns} → {RestProperties}",
+ string.Join(", ", dbRecord.Keys),
string.Join(", ", restData.Keys));
return restData;
@@ -1941,10 +1972,10 @@ public partial class DataCoupler : ComponentBase
return null;
// Ottieni informazioni sui tipi per fare trasformazioni intelligenti
- var dbColumnInfo = databaseTables.ContainsKey(selectedTable)
+ 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
@@ -1954,19 +1985,19 @@ public partial class DataCoupler : ComponentBase
{
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;
@@ -1974,7 +2005,7 @@ public partial class DataCoupler : ComponentBase
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))
@@ -2010,9 +2041,9 @@ public partial class DataCoupler : ComponentBase
// Common separators to check
var separators = new[] { ',', ';', '\t', '|' };
var counts = new Dictionary();
-
+
bool inQuotes = false;
-
+
// Count separators outside of quotes
foreach (char c in line)
{
@@ -2025,7 +2056,7 @@ public partial class DataCoupler : ComponentBase
counts[c] = counts.GetValueOrDefault(c, 0) + 1;
}
}
-
+
// Return the separator with the highest count, default to comma
if (counts.Any())
{
@@ -2035,7 +2066,7 @@ public partial class DataCoupler : ComponentBase
{
return mostCommon.Key;
}
- }
+ }
return ','; // Default fallback
}
@@ -2047,11 +2078,11 @@ public partial class DataCoupler : ComponentBase
// 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;
}
@@ -2116,7 +2147,7 @@ public partial class DataCoupler : ComponentBase
{
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.");
@@ -2148,7 +2179,7 @@ public partial class DataCoupler : ComponentBase
isConnectingDatabase = true;
databaseErrorMessage = "";
-
+
try
{
// Trova la credenziale
@@ -2211,17 +2242,17 @@ public partial class DataCoupler : ComponentBase
{
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> ??
+ databaseTables = schema as Dictionary> ??
(schema != null ? new Dictionary>(schema) : new Dictionary>());
if (databaseTables.Count == 0)
{
// Se non ci sono tabelle, potrebbe essere necessario selezionare un database specifico
- // Schema discovery completato senza successo
- databaseErrorMessage = "Impossibile rilevare le tabelle del database. Verificare le credenziali di connessione.";
+ // Schema discovery completato senza successo
+ databaseErrorMessage = "Impossibile rilevare le tabelle del database. Verificare le credenziali di connessione.";
}
else
{
@@ -2251,7 +2282,7 @@ public partial class DataCoupler : ComponentBase
// TODO: Implementare la logica specifica per il caricamento di uno schema
// Per ora utilizziamo il discovery standard e filtriamo i risultati
var schema = await currentDatabaseManager!.GetDatabaseSchemaAsync();
-
+
databaseTables = schema as Dictionary> ??
new Dictionary>();
@@ -2312,11 +2343,11 @@ public partial class DataCoupler : ComponentBase
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 =>
+ 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";
@@ -2324,7 +2355,7 @@ public partial class DataCoupler : ComponentBase
///
/// Verifica se una query è una SELECT query sicura
-
+
///
private bool IsSelectQuery(string query)
{
@@ -2332,20 +2363,20 @@ public partial class DataCoupler : ComponentBase
return false;
var trimmedQuery = query.Trim();
-
+
// Deve iniziare con SELECT
if (!trimmedQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
return false;
-
+
// Lista di parole chiave vietate per sicurezza
var forbiddenKeywords = new[]
{
"INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", "TRUNCATE",
"EXEC", "EXECUTE", "sp_", "xp_", "BULK", "OPENROWSET", "OPENDATASOURCE"
};
-
+
var upperQuery = trimmedQuery.ToUpperInvariant();
-
+
// Verifica che non contenga parole chiave vietate
foreach (var keyword in forbiddenKeywords)
{
@@ -2355,14 +2386,14 @@ public partial class DataCoupler : ComponentBase
return false;
}
}
-
+
// Verifica che non contenga commenti SQL potenzialmente pericolosi
if (upperQuery.Contains("--") || upperQuery.Contains("/*"))
{
Logger.LogWarning("Query rifiutata: contiene commenti SQL non consentiti");
return false;
}
-
+
return true;
}
@@ -2376,16 +2407,16 @@ public partial class DataCoupler : ComponentBase
// Rimuove caratteri potenzialmente pericolosi
var cleanQuery = query.Trim();
-
+
// Rimuove eventuali terminatori multipli
while (cleanQuery.EndsWith(";"))
{
cleanQuery = cleanQuery.Substring(0, cleanQuery.Length - 1).Trim();
}
-
+
// Rimuove caratteri di controllo pericolosi
cleanQuery = System.Text.RegularExpressions.Regex.Replace(cleanQuery, @"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]", "");
-
+
// Normalizza spazi multipli
cleanQuery = System.Text.RegularExpressions.Regex.Replace(cleanQuery, @"\s+", " ");
@@ -2398,7 +2429,7 @@ public partial class DataCoupler : ComponentBase
private void OnQueryModeChanged(ChangeEventArgs e)
{
useCustomQuery = (bool)(e.Value ?? false);
-
+
// Reset stato quando cambia modalità
if (useCustomQuery)
{
@@ -2416,7 +2447,7 @@ public partial class DataCoupler : ComponentBase
queryColumns.Clear();
showQueryPreview = false;
}
-
+
StateHasChanged();
}
@@ -2433,7 +2464,7 @@ public partial class DataCoupler : ComponentBase
}
isValidatingQuery = true;
-
+
try
{
// Controllo di sicurezza: verifica che sia una SELECT
@@ -2445,7 +2476,7 @@ public partial class DataCoupler : ComponentBase
}
var cleanQuery = CleanQuery(customQuery);
-
+
// Trova la credenziale per determinare il tipo di database
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null)
@@ -2454,28 +2485,28 @@ public partial class DataCoupler : ComponentBase
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();
-
+
// AUTO-SELECT della chiave per query custom
TryAutoSelectKeyForQuery(queryColumns);
-
+
Logger.LogInformation("Query validata con successo: {ColumnCount} colonne", queryColumns.Count);
}
else
@@ -2506,11 +2537,11 @@ public partial class DataCoupler : ComponentBase
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)
@@ -2518,16 +2549,16 @@ public partial class DataCoupler : ComponentBase
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)
@@ -2549,7 +2580,7 @@ public partial class DataCoupler : ComponentBase
{
return databaseType switch
{
- DatabaseType.SqlServer => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery",
+ 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}",
@@ -2587,7 +2618,7 @@ public partial class DataCoupler : ComponentBase
return null;
}
}
-
+
return null;
}
@@ -2609,7 +2640,7 @@ public partial class DataCoupler : ComponentBase
return null;
}
}
-
+
return null;
}
@@ -2629,12 +2660,12 @@ public partial class DataCoupler : ComponentBase
private async Task> GetTableColumns(string fullTableName, string databaseName, string tableName)
{
var columns = new List();
-
+
try
{
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null) return columns;
-
+
var columnsQuery = credential.DatabaseType switch
{
DatabaseType.SqlServer => $@"
@@ -2644,7 +2675,7 @@ public partial class DataCoupler : ComponentBase
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
@@ -2652,29 +2683,29 @@ public partial class DataCoupler : ComponentBase
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)
@@ -2682,12 +2713,12 @@ public partial class DataCoupler : ComponentBase
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
@@ -2704,7 +2735,7 @@ public partial class DataCoupler : ComponentBase
{
Logger.LogError(ex, "Errore nell'ottenere le colonne per la tabella {TableName}", fullTableName);
}
-
+
return columns;
}
@@ -2717,14 +2748,14 @@ public partial class DataCoupler : ComponentBase
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);
@@ -2736,7 +2767,7 @@ public partial class DataCoupler : ComponentBase
Logger.LogError(ex, "Errore nella connessione con lo schema selezionato");
databaseErrorMessage = $"Errore nella connessione al database {selectedSchema}: {ex.Message}";
}
-
+
StateHasChanged();
}
@@ -2750,7 +2781,7 @@ public partial class DataCoupler : ComponentBase
isLoadingSchemas = true;
availableSchemas.Clear();
-
+
try
{
// Prova a ottenere tutti gli schemi/database disponibili
@@ -2759,7 +2790,7 @@ public partial class DataCoupler : ComponentBase
try
{
var allSchemas = await currentDatabaseManager.GetDatabaseSchemaAsync();
-
+
if (allSchemas != null)
{
// Estrai i nomi degli schemi dalle chiavi delle tabelle
@@ -2769,11 +2800,11 @@ public partial class DataCoupler : ComponentBase
.Distinct()
.OrderBy(schema => schema)
.ToList();
-
+
if (schemaNames.Any())
{
availableSchemas.AddRange(schemaNames);
- Logger.LogInformation("Rilevati {SchemaCount} schemi dalle tabelle: {Schemas}",
+ Logger.LogInformation("Rilevati {SchemaCount} schemi dalle tabelle: {Schemas}",
schemaNames.Count, string.Join(", ", schemaNames));
return;
}
@@ -2824,10 +2855,10 @@ public partial class DataCoupler : ComponentBase
{
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 schemas = results.Select(row =>
{
var firstValue = row.Values.FirstOrDefault();
return firstValue?.ToString() ?? "";
@@ -2839,7 +2870,7 @@ public partial class DataCoupler : ComponentBase
if (schemas.Any())
{
availableSchemas.AddRange(schemas);
- Logger.LogInformation("Caricati {SchemaCount} database/schemi via query diretta per {DatabaseType}: {Schemas}",
+ Logger.LogInformation("Caricati {SchemaCount} database/schemi via query diretta per {DatabaseType}: {Schemas}",
schemas.Count, credential.DatabaseType, string.Join(", ", schemas));
}
}
@@ -2885,11 +2916,11 @@ public partial class DataCoupler : ComponentBase
databaseErrorMessage = "Nessun database selezionato";
return;
}
-
+
showDatabaseSelectionModal = false;
-
+
Logger.LogInformation("Database selezionato: {DatabaseName}. Riconnessione in corso...", selectedDatabase);
-
+
// Riconnessione al database selezionato
await ConnectToDatabaseWithSpecificDatabase(selectedDatabase);
}
@@ -2913,16 +2944,16 @@ public partial class DataCoupler : ComponentBase
{
try
{
- Logger.LogInformation("Verifica database specificato - Tipo: {DatabaseType}, DatabaseName: '{DatabaseName}', Connection: {ConnectionString}",
+ 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)
{
@@ -2930,18 +2961,18 @@ public partial class DataCoupler : ComponentBase
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}",
+
+ 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
}
@@ -2965,9 +2996,9 @@ public partial class DataCoupler : ComponentBase
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();
}
@@ -2991,15 +3022,15 @@ public partial class DataCoupler : ComponentBase
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)",
+
+ Logger.LogInformation("Trovati {TotalDatabases} database, filtrati a {FilteredDatabases} (esclusi quelli di sistema)",
allDatabases.Count, availableDatabases.Count);
}
catch (Exception ex)
@@ -3025,7 +3056,7 @@ public partial class DataCoupler : ComponentBase
}
var databaseType = credential.DatabaseType;
-
+
// Filtri per SQL Server
if (databaseType == DatabaseType.SqlServer)
{
@@ -3034,29 +3065,29 @@ public partial class DataCoupler : ComponentBase
"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
}
@@ -3066,8 +3097,34 @@ public partial class DataCoupler : ComponentBase
private async Task ConnectToDatabaseWithSpecificDatabase(string databaseName)
{
+ 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");
if (currentDatabaseManager == null)
{
databaseErrorMessage = "Database manager non disponibile";
@@ -3075,14 +3132,14 @@ public partial class DataCoupler : ComponentBase
}
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);
}
@@ -3105,54 +3162,54 @@ public partial class DataCoupler : ComponentBase
sourceKeyField = "";
suggestedPrimaryKey = "";
requiresManualKeySelection = true;
-
+
// Pattern comuni per identificare possibili chiavi primarie
var keyPatterns = new[]
{
- "id", "ID", "Id",
+ "id", "ID", "Id",
"_id", "_ID", "_Id",
"key", "KEY", "Key",
"code", "CODE", "Code", "codice", "CODICE", "Codice",
"number", "NUMBER", "Number", "numero", "NUMERO", "Numero",
"index", "INDEX", "Index", "indice", "INDICE", "Indice"
};
-
+
// Cerca colonne che potrebbero essere chiavi primarie
string? detectedKey = null;
-
+
// 1. Cerca esattamente "id", "ID", "Id"
- detectedKey = columns.FirstOrDefault(c =>
+ detectedKey = columns.FirstOrDefault(c =>
c.Equals("id", StringComparison.OrdinalIgnoreCase) ||
c.Equals("ID", StringComparison.Ordinal) ||
c.Equals("Id", StringComparison.Ordinal));
-
+
// 2. Se non trovato, cerca colonne che terminano con "id", "ID", "Id"
if (detectedKey == null)
{
- detectedKey = columns.FirstOrDefault(c =>
+ detectedKey = columns.FirstOrDefault(c =>
c.EndsWith("id", StringComparison.OrdinalIgnoreCase) ||
c.EndsWith("ID", StringComparison.Ordinal) ||
c.EndsWith("Id", StringComparison.Ordinal));
}
-
+
// 3. Se non trovato, cerca colonne che contengono pattern di chiave
if (detectedKey == null)
{
foreach (var pattern in keyPatterns)
{
- detectedKey = columns.FirstOrDefault(c =>
+ detectedKey = columns.FirstOrDefault(c =>
c.Contains(pattern, StringComparison.OrdinalIgnoreCase));
if (detectedKey != null) break;
}
}
-
+
// 4. Auto-seleziona se trovato
if (!string.IsNullOrEmpty(detectedKey))
{
sourceKeyField = detectedKey;
suggestedPrimaryKey = detectedKey;
requiresManualKeySelection = false;
-
+
Logger.LogInformation("Chiave auto-selezionata per query custom: {KeyField}", detectedKey);
}
else
@@ -3180,57 +3237,57 @@ public partial class DataCoupler : ComponentBase
sourceKeyField = "";
suggestedPrimaryKey = "";
requiresManualKeySelection = true;
-
+
// Pattern comuni per identificare possibili chiavi primarie nei file
var keyPatterns = new[]
{
- "id", "ID", "Id",
+ "id", "ID", "Id",
"_id", "_ID", "_Id",
"key", "KEY", "Key",
"code", "CODE", "Code", "codice", "CODICE", "Codice",
"number", "NUMBER", "Number", "numero", "NUMERO", "Numero",
"index", "INDEX", "Index", "indice", "INDICE", "Indice"
};
-
+
// Cerca colonne che potrebbero essere chiavi primarie
string? detectedKey = null;
-
+
// 1. Cerca esattamente "id", "ID", "Id"
- detectedKey = columns.FirstOrDefault(c =>
+ detectedKey = columns.FirstOrDefault(c =>
c.Equals("id", StringComparison.OrdinalIgnoreCase) ||
c.Equals("ID", StringComparison.Ordinal) ||
c.Equals("Id", StringComparison.Ordinal) ||
c.Equals("codice", StringComparison.OrdinalIgnoreCase));
-
+
// 2. Se non trovato, cerca colonne che terminano con pattern comuni
if (detectedKey == null)
{
foreach (var pattern in keyPatterns.Take(6)) // Solo i primi pattern più comuni
{
- detectedKey = columns.FirstOrDefault(c =>
+ detectedKey = columns.FirstOrDefault(c =>
c.EndsWith(pattern, StringComparison.OrdinalIgnoreCase));
if (detectedKey != null) break;
}
}
-
+
// 3. Se non trovato, cerca colonne che contengono pattern di chiave
if (detectedKey == null)
{
foreach (var pattern in keyPatterns)
{
- detectedKey = columns.FirstOrDefault(c =>
+ detectedKey = columns.FirstOrDefault(c =>
c.Contains(pattern, StringComparison.OrdinalIgnoreCase));
if (detectedKey != null) break;
}
}
-
+
// 4. Auto-seleziona se trovato
if (!string.IsNullOrEmpty(detectedKey))
{
sourceKeyField = detectedKey;
suggestedPrimaryKey = detectedKey;
requiresManualKeySelection = false;
-
+
Logger.LogInformation("Chiave auto-selezionata per file: {KeyField}", detectedKey);
}
else
@@ -3251,13 +3308,13 @@ public partial class DataCoupler : ComponentBase
{
var uniqueName = baseName;
var counter = 1;
-
+
while (await ProfileService.GetProfileByNameIncludingInactiveAsync(uniqueName) != null)
{
uniqueName = $"{baseName} ({counter})";
counter++;
}
-
+
return uniqueName;
}
}
diff --git a/Data_Coupler/Pages/DataCoupler_temp.cs b/Data_Coupler/Pages/DataCoupler_temp.cs
deleted file mode 100644
index e69de29..0000000
diff --git a/Data_Coupler/wwwroot/data/credentials.db b/Data_Coupler/wwwroot/data/credentials.db
index cbd53c8..b1ecb44 100644
Binary files a/Data_Coupler/wwwroot/data/credentials.db and b/Data_Coupler/wwwroot/data/credentials.db differ
diff --git a/Data_Coupler/wwwroot/data/credentials.db-shm b/Data_Coupler/wwwroot/data/credentials.db-shm
index a814b92..7f3662a 100644
Binary files a/Data_Coupler/wwwroot/data/credentials.db-shm and b/Data_Coupler/wwwroot/data/credentials.db-shm differ
diff --git a/Data_Coupler/wwwroot/data/credentials.db-wal b/Data_Coupler/wwwroot/data/credentials.db-wal
index 7c12581..fd11c4d 100644
Binary files a/Data_Coupler/wwwroot/data/credentials.db-wal and b/Data_Coupler/wwwroot/data/credentials.db-wal differ
diff --git a/TestCredentialDatabaseName/Program.cs b/TestCredentialDatabaseName/Program.cs
new file mode 100644
index 0000000..54e7af4
--- /dev/null
+++ b/TestCredentialDatabaseName/Program.cs
@@ -0,0 +1,53 @@
+using CredentialManager.Data;
+using CredentialManager.Models;
+using CredentialManager.Services;
+using Microsoft.EntityFrameworkCore;
+
+Console.WriteLine("🧪 Testing SourceDatabaseName retrieval from credentials...");
+
+// Configurazione del database temporaneo
+var options = new DbContextOptionsBuilder()
+ .UseSqlite("Data Source=test_credential_db.db")
+ .Options;
+
+using var context = new CredentialDbContext(options);
+await context.Database.EnsureCreatedAsync();
+
+var credentialService = new CredentialService(context);
+
+// Test 1: Crea una credenziale database con nome database
+var testCredential = new DatabaseCredential
+{
+ Name = "TestDatabaseCredential",
+ DatabaseType = "SqlServer",
+ Host = "localhost",
+ Port = 1433,
+ DatabaseName = "MyProductionDB",
+ Username = "testuser",
+ Password = "testpassword"
+};
+
+Console.WriteLine($"📝 Creando credenziale con DatabaseName: {testCredential.DatabaseName}");
+var credentialId = await credentialService.SaveDatabaseCredentialAsync(testCredential);
+Console.WriteLine($"✅ Credenziale salvata con ID: {credentialId}");
+
+// Test 2: Recupera la credenziale
+var retrievedCredential = await credentialService.GetDatabaseCredentialAsync(credentialId);
+Console.WriteLine($"✅ Credenziale recuperata: {retrievedCredential?.Name}");
+Console.WriteLine($" DatabaseName: {retrievedCredential?.DatabaseName}");
+
+// Test 3: Simula il recupero del database name come farebbe ProfileSaver
+if (retrievedCredential != null && !string.IsNullOrEmpty(retrievedCredential.DatabaseName))
+{
+ Console.WriteLine($"✅ SUCCESSO: DatabaseName recuperato dalle credenziali: {retrievedCredential.DatabaseName}");
+}
+else
+{
+ Console.WriteLine("❌ ERRORE: DatabaseName non recuperato dalle credenziali");
+}
+
+// Pulizia
+await context.Database.EnsureDeletedAsync();
+Console.WriteLine("🧹 Database temporaneo eliminato");
+
+Console.WriteLine("\n🎯 Test completato con successo!");
diff --git a/TestCredentialDatabaseName/TestCredentialDatabaseName.csproj b/TestCredentialDatabaseName/TestCredentialDatabaseName.csproj
new file mode 100644
index 0000000..e696ab1
--- /dev/null
+++ b/TestCredentialDatabaseName/TestCredentialDatabaseName.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/TestSourceDatabaseName/Program.cs b/TestSourceDatabaseName/Program.cs
new file mode 100644
index 0000000..9289edb
--- /dev/null
+++ b/TestSourceDatabaseName/Program.cs
@@ -0,0 +1,62 @@
+using CredentialManager.Data;
+using CredentialManager.Models;
+using CredentialManager.Services;
+using Microsoft.EntityFrameworkCore;
+
+Console.WriteLine("🧪 Testing SourceDatabaseName database persistence...");
+
+// Configurazione del database temporaneo
+var options = new DbContextOptionsBuilder()
+ .UseSqlite("Data Source=test_sourcedatabase.db")
+ .Options;
+
+using var context = new CredentialDbContext(options);
+await context.Database.EnsureCreatedAsync();
+
+var profileService = new DataCouplerProfileService(context);
+
+// Test: Creazione e salvataggio di un profilo con SourceDatabaseName
+var testProfile = new DataCouplerProfile
+{
+ Name = "Test Profile DB",
+ Description = "Test per verificare il salvataggio del SourceDatabaseName",
+ SourceType = "database",
+ SourceDatabaseName = "MyProductionDatabase",
+ SourceSchema = "dbo",
+ SourceTable = "customers",
+ DestinationType = "rest",
+ DestinationEndpoint = "/api/customers",
+ CreatedBy = "TestUser"
+};
+
+Console.WriteLine($"📝 Creando profilo con SourceDatabaseName: {testProfile.SourceDatabaseName}");
+
+// Salvataggio nel database
+var savedProfile = await profileService.SaveProfileAsync(testProfile);
+Console.WriteLine($"✅ Profilo salvato con ID: {savedProfile.Id}");
+
+// Recupero dal database
+var retrievedProfile = await profileService.GetProfileByIdAsync(savedProfile.Id);
+Console.WriteLine($"✅ Profilo recuperato dal database");
+
+// Verifica che il SourceDatabaseName sia stato salvato e recuperato correttamente
+if (retrievedProfile != null && retrievedProfile.SourceDatabaseName == testProfile.SourceDatabaseName)
+{
+ Console.WriteLine($"✅ SUCCESSO: SourceDatabaseName salvato e recuperato correttamente: {retrievedProfile.SourceDatabaseName}");
+}
+else
+{
+ Console.WriteLine($"❌ ERRORE: SourceDatabaseName non salvato correttamente");
+ Console.WriteLine($" Originale: {testProfile.SourceDatabaseName}");
+ Console.WriteLine($" Recuperato: {retrievedProfile?.SourceDatabaseName ?? "NULL"}");
+}
+
+// Test conversione DTO
+var dto = profileService.ToDto(retrievedProfile!);
+Console.WriteLine($"✅ DTO convertito con SourceDatabaseName: {dto.SourceDatabaseName}");
+
+// Pulizia
+await context.Database.EnsureDeletedAsync();
+Console.WriteLine("🧹 Database temporaneo eliminato");
+
+Console.WriteLine("\n🎯 Test completato con successo!");
diff --git a/TestSourceDatabaseName/TestSourceDatabaseName.csproj b/TestSourceDatabaseName/TestSourceDatabaseName.csproj
new file mode 100644
index 0000000..e696ab1
--- /dev/null
+++ b/TestSourceDatabaseName/TestSourceDatabaseName.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+