Fix: Risolto double-mapping negli External ID Relationships per Salesforce

- Implementata funzionalità completa External ID Relationships nell'interfaccia di mapping
- Corretto bug double-mapping: i campi sorgente usati per External ID non vengono più inclusi nei mapping normali
- Risolto errore MALFORMED_ID causato dall'invio duplicato di campi come proprietà dirette e nested objects
- Implementata logica corretta per relationship names: oggetti standard usano il nome diretto, custom objects usano suffisso __r
- Aggiunta UI a 3 colonne (Object, External ID Field, Source Field) per configurazione External ID Relationships
- Migrazione database per supporto External ID Relationships nei profili
- Aggiornato ProfileSaver.razor.cs per salvare/caricare External ID Relationships
- Aggiornato ScheduledProfileExecutionService.cs per gestire External ID nelle esecuzioni schedulate
- Formato JSON output corretto: { 'Account': { 'CardCode__c': 'V50000' } }

Documentazione: EXTERNAL_ID_RELATIONSHIPS_IMPLEMENTATION.md
This commit is contained in:
2026-02-15 18:44:15 +01:00
parent ed5316fbdf
commit 483eb7b407
16 changed files with 1923 additions and 21 deletions
+345
View File
@@ -0,0 +1,345 @@
# Fix Connessione SQL Server con Localhost
**Data**: 15 Febbraio 2026
**Versione**: 2.1+
## 📋 Problema Risolto
Il sistema non riusciva a connettersi correttamente a SQL Server quando si utilizzava "localhost" come host, specialmente per:
- Named Instances (es. `localhost\SQLEXPRESS`)
- LocalDB (es. `(localdb)\MSSQLLocalDB`)
- Windows Authentication
## 🔧 Modifiche Implementate
### 1. ConnectionStringBuilder - Gestione Intelligente del Server
**File**: `CredentialManager/Models/CredentialModels.cs`
#### Miglioramenti:
**a) Named Instances**
- Se l'host contiene `\` (backslash), la porta viene omessa automaticamente
- Esempi supportati:
- `localhost\SQLEXPRESS`
- `.\SQLEXPRESS`
- `SERVERNAME\INSTANCE`
**b) LocalDB**
- Se l'host inizia con `(localdb)`, la porta viene omessa
- Esempi supportati:
- `(localdb)\MSSQLLocalDB`
- `(localdb)\v11.0`
- `(localdb)\ProjectsV13`
**c) Localhost con Named Pipes**
- Per `localhost`, `.` o `127.0.0.1` con porta 1433 (default), la porta viene omessa
- Questo permette a SQL Server di usare Named Pipes invece di TCP/IP per connessioni locali più veloci
**d) Windows Authentication**
- Se username è vuoto, `Integrated` o `Windows`, usa Windows Authentication
- Non richiede password quando si usa Windows Authentication
- Connection string include `Integrated Security=True`
#### Codice Modificato:
```csharp
private static string BuildSqlServerConnectionString(DatabaseCredential credential)
{
var builder = new List<string>();
// Gestione speciale per SQL Server locale e named instances
bool hasInstanceName = credential.Host.Contains('\\') ||
credential.Host.StartsWith("(localdb)", StringComparison.OrdinalIgnoreCase);
if (hasInstanceName)
{
// Per named instances e LocalDB, non includere la porta
builder.Add($"Server={credential.Host}");
}
else
{
// Per localhost con porta default, ometti la porta per usare Named Pipes
if ((credential.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase) ||
credential.Host == "." ||
credential.Host == "127.0.0.1") && credential.Port == 1433)
{
builder.Add($"Server={credential.Host}");
}
else
{
// Per altri casi, usa host,porta
builder.Add($"Server={credential.Host},{credential.Port}");
}
}
// Windows Authentication vs SQL Authentication
if (string.IsNullOrWhiteSpace(credential.Username) ||
credential.Username.Equals("Integrated", StringComparison.OrdinalIgnoreCase) ||
credential.Username.Equals("Windows", StringComparison.OrdinalIgnoreCase))
{
builder.Add("Integrated Security=True");
}
else
{
builder.Add($"User Id={credential.Username}");
builder.Add($"Password={credential.Password}");
}
builder.Add($"Connection Timeout={credential.CommandTimeout}");
if (!string.IsNullOrEmpty(credential.DatabaseName))
builder.Add($"Database={credential.DatabaseName}");
if (credential.IgnoreSslErrors)
builder.Add("TrustServerCertificate=True");
return string.Join(";", builder);
}
```
### 2. UI - Guida Contestuale per SQL Server
**File**: `Data_Coupler/Pages/CredentialManagement.razor`
#### Aggiunte:
**a) Help Text per Host/Server**
- Mostra esempi specifici per SQL Server locale:
- Named Instance: `localhost\SQLEXPRESS` o `.\SQLEXPRESS`
- LocalDB: `(localdb)\MSSQLLocalDB`
- Default: `localhost` o `.` (usa porta 1433)
**b) Nota sulla Porta**
- Indica che la porta viene ignorata per named instances e LocalDB
**c) Guida Windows Authentication**
- Nel campo Username: placeholder "o scrivi 'Integrated' per Windows Auth"
- Help text: "Per Windows Authentication, scrivi **Integrated** o lascia vuoto"
- Nel campo Password: "Non richiesta per Windows Authentication"
#### Codice Aggiunto:
```razor
@if (currentDatabaseCredential.DatabaseType == DatabaseType.SqlServer)
{
<div class="form-text">
<strong>SQL Server locale:</strong><br/>
• Named Instance: <code>localhost\SQLEXPRESS</code> o <code>.\SQLEXPRESS</code><br/>
• LocalDB: <code>(localdb)\MSSQLLocalDB</code><br/>
• Default: <code>localhost</code> o <code>.</code> (usa porta 1433)
</div>
}
```
### 3. Validazione Aggiornata
**File**: `Data_Coupler/Pages/CredentialManagement.razor`
#### Miglioramenti:
**a) Validazione Credenziali**
- Permette username/password vuoti per SQL Server con Windows Authentication
- Riconosce "Integrated" e "Windows" come segnali per Windows Authentication
- Validazione più specifica con messaggi di errore appropriati
#### Codice Modificato:
```csharp
// Per SQL Server, permetti Windows Authentication
bool isSqlServerWithWindowsAuth = currentDatabaseCredential.DatabaseType == DatabaseType.SqlServer &&
(string.IsNullOrWhiteSpace(currentDatabaseCredential.Username) ||
currentDatabaseCredential.Username.Equals("Integrated", StringComparison.OrdinalIgnoreCase) ||
currentDatabaseCredential.Username.Equals("Windows", StringComparison.OrdinalIgnoreCase));
if (!isSqlServerWithWindowsAuth)
{
// Per database che non usano Windows Authentication, richiedi username e password
if (string.IsNullOrEmpty(currentDatabaseCredential.Username) ||
string.IsNullOrEmpty(currentDatabaseCredential.Password))
{
await JSRuntime.InvokeVoidAsync("alert",
"Username e Password sono obbligatori. Per SQL Server con Windows Authentication, inserisci 'Integrated' come username.");
return;
}
}
```
## 📚 Guida Utilizzo
### Scenario 1: SQL Server Express Locale
**Configurazione Credenziale:**
- **Host**: `localhost\SQLEXPRESS` o `.\SQLEXPRESS`
- **Porta**: 1433 (ignorata)
- **Database**: Nome del database (es. `MyDatabase`)
- **Username**: `Integrated` o lascia vuoto
- **Password**: Lascia vuoto
**Connection String Generata:**
```
Server=localhost\SQLEXPRESS;Integrated Security=True;Connection Timeout=30;Database=MyDatabase;TrustServerCertificate=True
```
### Scenario 2: SQL Server LocalDB
**Configurazione Credenziale:**
- **Host**: `(localdb)\MSSQLLocalDB`
- **Porta**: 1433 (ignorata)
- **Database**: Nome del database (es. `TestDB`)
- **Username**: `Integrated` o lascia vuoto
- **Password**: Lascia vuoto
**Connection String Generata:**
```
Server=(localdb)\MSSQLLocalDB;Integrated Security=True;Connection Timeout=30;Database=TestDB
```
### Scenario 3: SQL Server Locale con SQL Authentication
**Configurazione Credenziale:**
- **Host**: `localhost`
- **Porta**: 1433
- **Database**: Nome del database (es. `Production`)
- **Username**: `sa` (o un altro utente SQL)
- **Password**: Password dell'utente
**Connection String Generata:**
```
Server=localhost;User Id=sa;Password=***;Connection Timeout=30;Database=Production;TrustServerCertificate=True
```
### Scenario 4: SQL Server Remoto
**Configurazione Credenziale:**
- **Host**: `sql.example.com`
- **Porta**: 1433 (o porta custom, es. 14330)
- **Database**: Nome del database
- **Username**: Utente SQL
- **Password**: Password
**Connection String Generata:**
```
Server=sql.example.com,1433;User Id=username;Password=***;Connection Timeout=30;Database=DBName;TrustServerCertificate=True
```
### Scenario 5: SQL Server con Instance Name Remoto
**Configurazione Credenziale:**
- **Host**: `server.domain.com\PRODUCTION`
- **Porta**: 1433 (ignorata)
- **Database**: Nome del database
- **Username**: Utente SQL
- **Password**: Password
**Connection String Generata:**
```
Server=server.domain.com\PRODUCTION;User Id=username;Password=***;Connection Timeout=30;Database=DBName;TrustServerCertificate=True
```
## 🔍 Troubleshooting
### Problema: "A network-related or instance-specific error"
**Possibili Cause:**
1. **SQL Server Browser non in esecuzione** (per named instances)
- Soluzione: Avvia il servizio "SQL Server Browser" da services.msc
2. **TCP/IP non abilitato**
- Soluzione: SQL Server Configuration Manager → Protocols → Enable TCP/IP
3. **Named Instance non specificata**
- Soluzione: Usa `localhost\SQLEXPRESS` invece di solo `localhost`
4. **Firewall blocca la porta**
- Soluzione: Aggiungi eccezione firewall per SQL Server
### Problema: "Login failed for user"
**Possibili Cause:**
1. **Windows Authentication richiesta ma SQL Auth specificata**
- Soluzione: Usa username `Integrated` o lascialo vuoto
2. **SQL Authentication non abilitata**
- Soluzione: SQL Server Management Studio → Proprietà Server → Security → SQL Server and Windows Authentication mode
3. **Password errata**
- Soluzione: Verifica la password
### Problema: "Cannot open database"
**Possibili Cause:**
1. **Database non esiste**
- Soluzione: Verifica il nome del database o lascia il campo vuoto per connetterti solo al server
2. **Permessi insufficienti**
- Soluzione: Verifica che l'utente abbia accesso al database
## ✅ Test di Connessione
Dopo aver configurato la credenziale, usa il pulsante **"Testa Connessione"** per verificare:
- ✅ Connection string corretta
- ✅ SQL Server raggiungibile
- ✅ Autenticazione riuscita
- ✅ Database accessibile (se specificato)
Il test mostra:
- Versione SQL Server
- Host e porta usati
- Database connesso
- Timeout configurato
## 📝 Note Tecniche
### Differenze TCP/IP vs Named Pipes
**Named Pipes** (preferito per localhost):
- Più veloce per connessioni locali
- Non richiede SQL Server Browser
- Usa IPC invece di network stack
- Sintassi: `Server=localhost` o `Server=.`
**TCP/IP** (richiesto per remote):
- Richiesto per connessioni remote
- Richiede porta specifica
- Richiede SQL Server Browser per named instances
- Sintassi: `Server=hostname,port`
### Windows Authentication vs SQL Authentication
**Windows Authentication**:
- ✅ Più sicuro (usa credenziali Windows)
- ✅ No password nel codice
- ✅ Single Sign-On
- ❌ Richiede domain trust per remote
**SQL Authentication**:
- ✅ Funziona sempre (anche cross-domain)
- ✅ Credenziali specifiche per SQL Server
- ❌ Password nel connection string
- ❌ Deve essere abilitato in SQL Server
## 🔄 Retrocompatibilità
Le modifiche sono completamente retrocompatibili:
- ✅ Connection string esistenti continuano a funzionare
- ✅ Credenziali già salvate non richiedono modifiche
- ✅ Comportamento default invariato per server remoti
- ✅ Nessuna migrazione database richiesta
## 📊 Impatto Performance
**Miglioramenti**:
- 🚀 Named Pipes più veloce di TCP/IP per localhost
- 🚀 Riduzione overhead network stack
- 🚀 Connection pooling più efficiente
**Nessun Impatto Negativo**:
- ✅ Server remoti usano sempre TCP/IP (comportamento corretto)
- ✅ Connection string ottimizzate per scenario specifico
---
**Sviluppatore**: Alessio Dalsanto
**Issue**: Connessione localhost SQL Server
**Status**: ✅ Risolto