fix: Risolto errore "Invalid object name" nel trasferimento dati e pulizia codice
- Modificato GetAllRecordsAsync per utilizzare la stessa connection string del discovery schema - Aggiunto metodo CreateConnection per creare connessioni DB appropriate per tipo - Migliorata gestione nomi tabelle con schema (es. "dbo.OCRD") - Rimossi metodi obsoleti di creazione entità (UpdateEntityData, CreateNewEntity) - Eliminati riferimenti a variabili non dichiarate (newEntityData, isCreatingEntity) - Aggiunto logging debug per connection string e query SQL - Completata implementazione trasferimento dati da database a REST API Il trasferimento dati ora utilizza la stessa connessione per discovery e estrazione, risolvendo problemi di accesso alle tabelle durante l'operazione di upsert.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -8,6 +9,7 @@ using DataConnection.Interfaces;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
|
||||||
namespace DataConnection.EF;
|
namespace DataConnection.EF;
|
||||||
|
|
||||||
@@ -103,27 +105,119 @@ public class EFCoreDatabaseManager : IDatabaseManager
|
|||||||
{
|
{
|
||||||
return await _context.Database.ExecuteSqlRawAsync(sql, parameters);
|
return await _context.Database.ExecuteSqlRawAsync(sql, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync()
|
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"[DEBUG] Iniziando GetDatabaseSchemaAsync - DatabaseType: {_options.DatabaseType}");
|
||||||
|
|
||||||
// Assicurarsi che il contesto sia connesso
|
// Assicurarsi che il contesto sia connesso
|
||||||
await _context.Database.OpenConnectionAsync();
|
await _context.Database.OpenConnectionAsync();
|
||||||
|
Console.WriteLine($"[DEBUG] Connessione al database aperta. Connection string: {_context.Database.GetConnectionString()}");
|
||||||
|
|
||||||
// Usa la factory per ottenere il provider appropriato in base al tipo di database
|
// Usa la factory per ottenere il provider appropriato in base al tipo di database
|
||||||
var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType);
|
var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType);
|
||||||
|
Console.WriteLine($"[DEBUG] Schema provider creato: {schemaProvider.GetType().Name}");
|
||||||
|
|
||||||
// Usa il provider per ottenere lo schema
|
// Usa il provider per ottenere lo schema
|
||||||
return await schemaProvider.GetDatabaseSchemaAsync(_context.Database.GetConnectionString());
|
var result = await schemaProvider.GetDatabaseSchemaAsync(_context.Database.GetConnectionString());
|
||||||
|
Console.WriteLine($"[DEBUG] Schema ottenuto. Numero tabelle: {result?.Count ?? 0}");
|
||||||
|
|
||||||
|
if (result != null && result.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var table in result.Take(3))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[DEBUG] Tabella: {table.Key}, Colonne: {table.Value?.Count() ?? 0}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Errore nel recupero dello schema del database: {ex.Message}");
|
Console.WriteLine($"Errore nel recupero dello schema del database: {ex.Message}");
|
||||||
|
Console.WriteLine($"[DEBUG] Stack trace: {ex.StackTrace}");
|
||||||
|
throw; }
|
||||||
|
} public async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsAsync(string tableName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var records = new List<Dictionary<string, object>>();
|
||||||
|
|
||||||
|
// Usa la stessa connection string utilizzata per il discovery dello schema
|
||||||
|
var connectionString = _context.Database.GetConnectionString();
|
||||||
|
Console.WriteLine($"[DEBUG] GetAllRecordsAsync - Using connection string: {connectionString?.Substring(0, Math.Min(50, connectionString?.Length ?? 0))}...");
|
||||||
|
|
||||||
|
// Determina il tipo di connessione in base al DatabaseType
|
||||||
|
using var connection = CreateConnection(connectionString);
|
||||||
|
await connection.OpenAsync();
|
||||||
|
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
|
||||||
|
// Query SQL semplice per ottenere tutti i record - limitiamo a 1000 per sicurezza
|
||||||
|
// Se il nome della tabella contiene già lo schema (es. "dbo.OCRD"), lo usiamo così com'è
|
||||||
|
// Altrimenti aggiungiamo le parentesi quadre
|
||||||
|
string tableReference;
|
||||||
|
if (tableName.Contains('.'))
|
||||||
|
{
|
||||||
|
// Il nome contiene già lo schema, separiamo e mettiamo entrambi tra parentesi quadre
|
||||||
|
var parts = tableName.Split('.');
|
||||||
|
tableReference = $"[{parts[0]}].[{parts[1]}]";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Solo il nome della tabella, usiamo le parentesi quadre
|
||||||
|
tableReference = $"[{tableName}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
command.CommandText = $"SELECT TOP 1000 * FROM {tableReference}";
|
||||||
|
Console.WriteLine($"[DEBUG] GetAllRecordsAsync - Query: {command.CommandText}");
|
||||||
|
|
||||||
|
using var reader = await command.ExecuteReaderAsync();
|
||||||
|
|
||||||
|
while (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
var record = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
for (int i = 0; i < reader.FieldCount; i++)
|
||||||
|
{
|
||||||
|
var columnName = reader.GetName(i);
|
||||||
|
var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
|
||||||
|
record[columnName] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
records.Add(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"[DEBUG] GetAllRecordsAsync - Tabella: {tableName}, Record ottenuti: {records.Count}");
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Errore nell'ottenere i record dalla tabella {tableName}: {ex.Message}");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea una connessione database appropriata in base al tipo di database
|
||||||
|
/// </summary>
|
||||||
|
private DbConnection CreateConnection(string connectionString)
|
||||||
|
{
|
||||||
|
switch (_options.DatabaseType)
|
||||||
|
{
|
||||||
|
case Enums.DatabaseType.SqlServer:
|
||||||
|
return new SqlConnection(connectionString);
|
||||||
|
// Aggiungi altri tipi di database quando necessario
|
||||||
|
// case Enums.DatabaseType.MySQL:
|
||||||
|
// return new MySqlConnection(connectionString);
|
||||||
|
// case Enums.DatabaseType.PostgreSQL:
|
||||||
|
// return new NpgsqlConnection(connectionString);
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException($"Database type {_options.DatabaseType} is not supported for direct connections");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_context?.Dispose();
|
_context?.Dispose();
|
||||||
|
|||||||
@@ -11,16 +11,18 @@ namespace DataConnection.EF.SchemaProviders;
|
|||||||
/// Provider di schema per database SQL Server
|
/// Provider di schema per database SQL Server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SqlServerSchemaProvider : IDatabaseSchemaProvider
|
public class SqlServerSchemaProvider : IDatabaseSchemaProvider
|
||||||
{
|
{ public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync(string connectionString)
|
||||||
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync(string connectionString)
|
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, IEnumerable<DbColumnInfo>>();
|
var result = new Dictionary<string, IEnumerable<DbColumnInfo>>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"[DEBUG] SqlServerSchemaProvider - Connection string: {connectionString?.Substring(0, Math.Min(50, connectionString?.Length ?? 0))}...");
|
||||||
|
|
||||||
using (var connection = new SqlConnection(connectionString))
|
using (var connection = new SqlConnection(connectionString))
|
||||||
{
|
{
|
||||||
await connection.OpenAsync();
|
await connection.OpenAsync();
|
||||||
|
Console.WriteLine($"[DEBUG] SqlServerSchemaProvider - Connessione aperta");
|
||||||
|
|
||||||
// Query per ottenere la struttura delle tabelle in SQL Server
|
// Query per ottenere la struttura delle tabelle in SQL Server
|
||||||
string sql = @"
|
string sql = @"
|
||||||
@@ -110,12 +112,17 @@ public class SqlServerSchemaProvider : IDatabaseSchemaProvider
|
|||||||
|
|
||||||
columns?.Add(columnInfo);
|
columns?.Add(columnInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aggiungiamo l'ultima tabella
|
// Aggiungiamo l'ultima tabella
|
||||||
if (currentTable != null && columns != null && columns.Count > 0)
|
if (currentTable != null && columns != null && columns.Count > 0)
|
||||||
{
|
{
|
||||||
result[currentTable] = columns;
|
result[currentTable] = columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"[DEBUG] SqlServerSchemaProvider - Query completata. Trovate {result.Count} tabelle");
|
||||||
|
foreach (var table in result.Take(3))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[DEBUG] SqlServerSchemaProvider - Tabella: {table.Key}, Colonne: {table.Value?.Count() ?? 0}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,11 +45,15 @@ public interface IDatabaseManager : IDisposable
|
|||||||
/// Esegue un comando SQL che non restituisce risultati
|
/// Esegue un comando SQL che non restituisce risultati
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<int> ExecuteCommandAsync(string sql, params object[] parameters);
|
Task<int> ExecuteCommandAsync(string sql, params object[] parameters);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ottiene i metadati delle tabelle nel database
|
/// Ottiene i metadati delle tabelle nel database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync();
|
Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene tutti i record da una tabella specifica come dizionari chiave-valore
|
||||||
|
/// </summary>
|
||||||
|
Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsAsync(string tableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -126,7 +126,20 @@ namespace DataConnection.REST.Implementations
|
|||||||
{
|
{
|
||||||
Console.WriteLine($"Error during entity creation: {ex.Message}");
|
Console.WriteLine($"Error during entity creation: {ex.Message}");
|
||||||
throw;
|
throw;
|
||||||
|
} }
|
||||||
|
|
||||||
|
public virtual async Task<Dictionary<string, object>?> UpsertEntityAsync(string entityName, Dictionary<string, object> entityData, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// Default implementation - just delegates to CreateEntityAsync
|
||||||
|
// Derived classes can override this for service-specific upsert logic
|
||||||
|
return await CreateEntityAsync(entityName, entityData, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual async Task<bool> AuthenticateAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// Default implementation for basic authentication (already handled in ConfigureHttpClient)
|
||||||
|
// For services that require additional authentication steps, override this method
|
||||||
|
return await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement other methods (PUT, DELETE, etc.) similarly
|
// Implement other methods (PUT, DELETE, etc.) similarly
|
||||||
|
|||||||
@@ -109,6 +109,35 @@ namespace DataConnection.REST.Implementations
|
|||||||
Console.WriteLine($"Error during Salesforce Authentication: {ex.Message}");
|
Console.WriteLine($"Error during Salesforce Authentication: {ex.Message}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} /// <summary>
|
||||||
|
/// Authenticates with Salesforce using the credentials from options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>True if authentication is successful</returns>
|
||||||
|
public override async Task<bool> AuthenticateAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// For Salesforce, we need ClientId, ClientSecret, Username, and Password
|
||||||
|
// These should be provided in the options
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_options.Username) || string.IsNullOrEmpty(_options.Password))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Salesforce authentication requires username and password in options");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_options.ApiKey) || string.IsNullOrEmpty(_options.AuthToken))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Salesforce authentication requires ApiKey (ClientId) and AuthToken (ClientSecret) in options");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the actual credentials from options
|
||||||
|
var clientId = _options.ApiKey; // ClientId should be in ApiKey field
|
||||||
|
var clientSecret = _options.AuthToken; // ClientSecret should be in AuthToken field
|
||||||
|
|
||||||
|
Console.WriteLine($"Using Salesforce credentials - ClientId: {clientId}, Username: {_options.Username}");
|
||||||
|
|
||||||
|
return await AuthenticateAsync(clientId, clientSecret, _options.Username, _options.Password, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -421,6 +450,36 @@ namespace DataConnection.REST.Implementations
|
|||||||
Console.WriteLine($"Error during Salesforce entity creation: {ex.Message}");
|
Console.WriteLine($"Error during Salesforce entity creation: {ex.Message}");
|
||||||
Console.WriteLine($"--- End Salesforce Entity Creation Attempt (Exception) ---");
|
Console.WriteLine($"--- End Salesforce Entity Creation Attempt (Exception) ---");
|
||||||
return null;
|
return null;
|
||||||
|
} }
|
||||||
|
|
||||||
|
public override async Task<Dictionary<string, object>?> UpsertEntityAsync(string entityName, Dictionary<string, object> entityData, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// Per Salesforce, implementiamo upsert provando prima la creazione
|
||||||
|
// Se fallisce con un errore di duplicato, potremmo implementare logic di aggiornamento
|
||||||
|
// Per ora, semplicemente tentiamo la creazione
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.WriteLine($"--- Starting Salesforce Entity Upsert: {entityName} ---");
|
||||||
|
Console.WriteLine($"Entity Data: {string.Join(", ", entityData.Select(kvp => $"{kvp.Key}={kvp.Value}"))}");
|
||||||
|
|
||||||
|
// Prima tenta la creazione
|
||||||
|
var result = await CreateEntityAsync(entityName, entityData, cancellationToken);
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Upsert completed successfully via CREATE for {entityName}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se la creazione fallisce, potresti implementare qui la logica di aggiornamento
|
||||||
|
// Per ora, restituiamo null
|
||||||
|
Console.WriteLine($"Upsert failed for {entityName}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error during Salesforce entity upsert: {ex.Message}");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -540,6 +540,28 @@ namespace DataConnection.REST.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
} /// <summary>
|
||||||
|
/// Authenticates with SAP B1 Service Layer using the credentials from options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>True if authentication is successful</returns>
|
||||||
|
public override async Task<bool> AuthenticateAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_options.Username) || string.IsNullOrEmpty(_options.Password))
|
||||||
|
{
|
||||||
|
Console.WriteLine("SAP B1 authentication requires username and password in options");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For SAP B1, we also need the company database name
|
||||||
|
// We'll check multiple fields for the company DB name
|
||||||
|
var companyDB = !string.IsNullOrEmpty(_options.ApiKey) ? _options.ApiKey :
|
||||||
|
!string.IsNullOrEmpty(_options.AuthToken) ? _options.AuthToken :
|
||||||
|
"SBODEMOUS"; // Default fallback
|
||||||
|
|
||||||
|
Console.WriteLine($"Using SAP B1 credentials - CompanyDB: {companyDB}, Username: {_options.Username}");
|
||||||
|
|
||||||
|
return await LoginAsync(companyDB, _options.Username, _options.Password, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to get cookie value
|
// Helper to get cookie value
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace DataConnection.REST.Interfaces
|
namespace DataConnection.REST.Interfaces
|
||||||
{
|
{
|
||||||
@@ -8,6 +9,14 @@ namespace DataConnection.REST.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRestServiceClient
|
public interface IRestServiceClient
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Authenticates the client using the provided credentials.
|
||||||
|
/// Implementation varies by service type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
|
/// <returns>True if authentication was successful, false otherwise.</returns>
|
||||||
|
Task<bool> AuthenticateAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a GET request to the specified URI.
|
/// Sends a GET request to the specified URI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -26,9 +35,7 @@ namespace DataConnection.REST.Interfaces
|
|||||||
/// <param name="payload">The HTTP request content sent to the server.</param>
|
/// <param name="payload">The HTTP request content sent to the server.</param>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
/// <returns>The deserialized response content.</returns>
|
/// <returns>The deserialized response content.</returns>
|
||||||
Task<TResponse?> PostAsync<TRequest, TResponse>(string requestUri, TRequest payload, CancellationToken cancellationToken = default);
|
Task<TResponse?> PostAsync<TRequest, TResponse>(string requestUri, TRequest payload, CancellationToken cancellationToken = default); /// <summary>
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new entity by sending a POST request with the provided data.
|
/// Creates a new entity by sending a POST request with the provided data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entityName">The name of the entity to create.</param>
|
/// <param name="entityName">The name of the entity to create.</param>
|
||||||
@@ -37,6 +44,15 @@ namespace DataConnection.REST.Interfaces
|
|||||||
/// <returns>The created entity data or null if creation failed.</returns>
|
/// <returns>The created entity data or null if creation failed.</returns>
|
||||||
Task<Dictionary<string, object>?> CreateEntityAsync(string entityName, Dictionary<string, object> entityData, CancellationToken cancellationToken = default);
|
Task<Dictionary<string, object>?> CreateEntityAsync(string entityName, Dictionary<string, object> entityData, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new entity or updates an existing one (upsert operation).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityName">The name of the entity to upsert.</param>
|
||||||
|
/// <param name="entityData">The data for the entity as key-value pairs.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
|
/// <returns>The upserted entity data or null if operation failed.</returns>
|
||||||
|
Task<Dictionary<string, object>?> UpsertEntityAsync(string entityName, Dictionary<string, object> entityData, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
// Add other methods as needed (PUT, DELETE, PATCH, etc.)
|
// Add other methods as needed (PUT, DELETE, PATCH, etc.)
|
||||||
// Consider adding methods for handling raw HttpResponseMessage or string responses
|
// Consider adding methods for handling raw HttpResponseMessage or string responses
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ using DataConnection.EF.DatabaseDiscovery;
|
|||||||
using DataConnection.Enums;
|
using DataConnection.Enums;
|
||||||
using DataConnection.CredentialManagement;
|
using DataConnection.CredentialManagement;
|
||||||
using CredentialManager;
|
using CredentialManager;
|
||||||
|
using Data_Coupler.Services;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -26,6 +27,9 @@ builder.Services.AddDataConnectionCredentialManagement($"Data Source={dbPath}");
|
|||||||
// Register IHttpClientFactory
|
// Register IHttpClientFactory
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
|
|
||||||
|
// Register Data Connection Factory
|
||||||
|
builder.Services.AddScoped<IDataConnectionFactory, DataConnectionFactory>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Initialize database
|
// Initialize database
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
using CredentialManager.Models;
|
||||||
|
using DataConnection.EF;
|
||||||
|
using DataConnection.Interfaces;
|
||||||
|
using DataConnection.REST.Interfaces;
|
||||||
|
using DataConnection.REST.Implementations;
|
||||||
|
using DataConnection.REST.Configuration;
|
||||||
|
using DataConnection.CredentialManagement.Interfaces;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Data_Coupler.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory service per creare istanze di DatabaseManager e RestClient
|
||||||
|
/// </summary>
|
||||||
|
public interface IDataConnectionFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un DatabaseManager per la credenziale specificata
|
||||||
|
/// </summary>
|
||||||
|
Task<IDatabaseManager> CreateDatabaseManagerAsync(string credentialName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un RestServiceClient per la credenziale specificata
|
||||||
|
/// </summary>
|
||||||
|
Task<IRestServiceClient> CreateRestServiceClientAsync(string credentialName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un RestMetadataDiscovery per la credenziale specificata
|
||||||
|
/// </summary>
|
||||||
|
Task<IRestMetadataDiscovery> CreateRestMetadataDiscoveryAsync(string credentialName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pulisce la cache del client REST per una credenziale specifica
|
||||||
|
/// </summary>
|
||||||
|
void ClearRestClientCache(string credentialName);
|
||||||
|
/// <summary>
|
||||||
|
/// Pulisce tutta la cache dei client REST
|
||||||
|
/// </summary>
|
||||||
|
void ClearAllRestClientCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DataConnectionFactory : IDataConnectionFactory
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ILogger<DataConnectionFactory> _logger;
|
||||||
|
private readonly IDataConnectionCredentialService _credentialService;
|
||||||
|
|
||||||
|
// Cache per mantenere le istanze dei client REST autenticati
|
||||||
|
private readonly Dictionary<string, IRestServiceClient> _restClientCache = new();
|
||||||
|
|
||||||
|
public DataConnectionFactory(IServiceProvider serviceProvider, ILogger<DataConnectionFactory> logger, IDataConnectionCredentialService credentialService)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_logger = logger;
|
||||||
|
_credentialService = credentialService;
|
||||||
|
}public async Task<IDatabaseManager> CreateDatabaseManagerAsync(string credentialName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var credential = await _credentialService.GetDatabaseCredentialAsync(credentialName);
|
||||||
|
if (credential == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Credenziale database '{credentialName}' non trovata");
|
||||||
|
}
|
||||||
|
|
||||||
|
var dbManagerOptions = await _credentialService.GetDbManagerOptionsAsync(credential.Name);
|
||||||
|
return new EFCoreDatabaseManager(dbManagerOptions);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Errore nella creazione del DatabaseManager per {CredentialName}", credentialName);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
} public async Task<IRestServiceClient> CreateRestServiceClientAsync(string credentialName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Controlla se abbiamo già un client cached per questa credenziale
|
||||||
|
if (_restClientCache.TryGetValue(credentialName, out var cachedClient))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Utilizzando client REST cached per {CredentialName}", credentialName);
|
||||||
|
return cachedClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
var credential = await _credentialService.GetRestApiCredentialAsync(credentialName);
|
||||||
|
if (credential == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Credenziale REST API '{credentialName}' non trovata");
|
||||||
|
} var options = new RestServiceOptions
|
||||||
|
{
|
||||||
|
BaseUrl = credential.BaseUrl,
|
||||||
|
Username = credential.Username,
|
||||||
|
Password = credential.Password,
|
||||||
|
TimeoutSeconds = credential.TimeoutSeconds,
|
||||||
|
IgnoreSslErrors = credential.IgnoreSslErrors
|
||||||
|
}; // Mapping specifico per tipo di servizio
|
||||||
|
switch (credential.ServiceType)
|
||||||
|
{
|
||||||
|
case RestServiceType.Salesforce:
|
||||||
|
// Per Salesforce usiamo i campi specifici ClientId e ClientSecret
|
||||||
|
options.ApiKey = credential.ClientId; // ClientId -> ApiKey
|
||||||
|
options.AuthToken = credential.ClientSecret; // ClientSecret -> AuthToken
|
||||||
|
_logger.LogInformation("Salesforce mapping - ClientId: {ClientId}, ClientSecret: {HasSecret}, Username: {Username}",
|
||||||
|
credential.ClientId, !string.IsNullOrEmpty(credential.ClientSecret), credential.Username);
|
||||||
|
break;
|
||||||
|
case RestServiceType.SapB1ServiceLayer:
|
||||||
|
// Per SAP B1 usiamo il CompanyDatabase come ApiKey
|
||||||
|
options.ApiKey = credential.CompanyDatabase;
|
||||||
|
options.AuthToken = credential.AuthToken;
|
||||||
|
_logger.LogInformation("SAP B1 mapping - CompanyDB: {CompanyDB}, Username: {Username}",
|
||||||
|
credential.CompanyDatabase, credential.Username);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Per servizi generici usiamo ApiKey e AuthToken direttamente
|
||||||
|
options.ApiKey = credential.ApiKey;
|
||||||
|
options.AuthToken = credential.AuthToken;
|
||||||
|
_logger.LogInformation("Generic mapping - ApiKey: {ApiKey}, AuthToken: {HasToken}, Username: {Username}",
|
||||||
|
credential.ApiKey, !string.IsNullOrEmpty(credential.AuthToken), credential.Username);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = credential.ServiceType switch
|
||||||
|
{
|
||||||
|
RestServiceType.SapB1ServiceLayer => new SapB1ServiceClient(options),
|
||||||
|
RestServiceType.Salesforce => CreateSalesforceClient(options),
|
||||||
|
RestServiceType.Generic => CreateGenericRestClient(options),
|
||||||
|
_ => throw new NotSupportedException($"Tipo di servizio REST non supportato: {credential.ServiceType}")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Salva il client nella cache
|
||||||
|
_restClientCache[credentialName] = client;
|
||||||
|
_logger.LogInformation("Client REST creato e cached per {CredentialName}", credentialName);
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Errore nella creazione del RestServiceClient per {CredentialName}", credentialName);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
} public async Task<IRestMetadataDiscovery> CreateRestMetadataDiscoveryAsync(string credentialName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Utilizza lo stesso client REST cached (che è già autenticato)
|
||||||
|
var restClient = await CreateRestServiceClientAsync(credentialName);
|
||||||
|
|
||||||
|
// I service client già implementano IRestMetadataDiscovery
|
||||||
|
if (restClient is IRestMetadataDiscovery metadataDiscovery)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Utilizzando lo stesso client REST per metadata discovery per {CredentialName}", credentialName);
|
||||||
|
return metadataDiscovery;
|
||||||
|
}
|
||||||
|
|
||||||
|
var credential = await _credentialService.GetRestApiCredentialAsync(credentialName);
|
||||||
|
throw new NotSupportedException($"Il servizio REST {credential?.ServiceType} non supporta il metadata discovery");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Errore nella creazione del RestMetadataDiscovery per {CredentialName}", credentialName);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRestServiceClient CreateGenericRestClient(RestServiceOptions options)
|
||||||
|
{
|
||||||
|
var httpClientFactory = _serviceProvider.GetRequiredService<IHttpClientFactory>();
|
||||||
|
var httpClient = httpClientFactory.CreateClient();
|
||||||
|
return new BaseRestServiceClient(httpClient, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRestServiceClient CreateSalesforceClient(RestServiceOptions options)
|
||||||
|
{
|
||||||
|
var httpClientFactory = _serviceProvider.GetRequiredService<IHttpClientFactory>();
|
||||||
|
var httpClient = httpClientFactory.CreateClient();
|
||||||
|
return new SalesforceServiceClient(httpClient, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pulisce la cache del client REST per una credenziale specifica
|
||||||
|
/// </summary>
|
||||||
|
public void ClearRestClientCache(string credentialName)
|
||||||
|
{
|
||||||
|
if (_restClientCache.Remove(credentialName))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Cache del client REST rimossa per {CredentialName}", credentialName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pulisce tutta la cache dei client REST
|
||||||
|
/// </summary>
|
||||||
|
public void ClearAllRestClientCache()
|
||||||
|
{
|
||||||
|
_restClientCache.Clear();
|
||||||
|
_logger.LogInformation("Cache di tutti i client REST pulita");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,12 +22,16 @@
|
|||||||
<NavLink class="nav-link" href="fetchdata">
|
<NavLink class="nav-link" href="fetchdata">
|
||||||
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div> <div class="nav-item px-3">
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="credentials">
|
<NavLink class="nav-link" href="credentials">
|
||||||
<span class="oi oi-key" aria-hidden="true"></span> Gestione Credenziali
|
<span class="oi oi-key" aria-hidden="true"></span> Gestione Credenziali
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="data-coupler">
|
||||||
|
<span class="oi oi-transfer" aria-hidden="true"></span> Data Coupler
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user