Allineamento dati con repository di origine
This commit is contained in:
122
HorseRacingPredictor/HorseRacingPredictor/Manager/API.cs
Normal file
122
HorseRacingPredictor/HorseRacingPredictor/Manager/API.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using RestSharp;
|
||||
|
||||
namespace HorseRacingPredictor.Manager
|
||||
{
|
||||
internal class API
|
||||
{
|
||||
// Intervallo tra le chiamate API per evitare limitazioni di rate
|
||||
private const int DefaultApiCallDelay = 6000;
|
||||
|
||||
/// <summary>
|
||||
/// Esegue una chiamata API generica e restituisce la risposta
|
||||
/// </summary>
|
||||
public static RestResponse ExecuteApiRequest(string url, string apiKey, string apiKeyHeader, string hostHeader, string hostValue, int delay = DefaultApiCallDelay)
|
||||
{
|
||||
var clientApi = new RestClient(url);
|
||||
var request = new RestRequest();
|
||||
request.AddHeader(apiKeyHeader, apiKey);
|
||||
request.AddHeader(hostHeader, hostValue);
|
||||
|
||||
try
|
||||
{
|
||||
var response = clientApi.Execute(request);
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
throw new Exception($"Errore nella richiesta API: {response.ErrorMessage}");
|
||||
}
|
||||
|
||||
// Aggiungi una pausa tra una chiamata e l'altra
|
||||
if (delay > 0)
|
||||
{
|
||||
Thread.Sleep(delay);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Errore durante la richiesta API: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue una chiamata API generica con informazioni di tipo per il salvataggio nel database
|
||||
/// </summary>
|
||||
public static RestResponse ExecuteApiRequest(string url, string apiKey, string apiKeyHeader, string hostHeader, string hostValue,
|
||||
string apiType, string parameters, int delay = DefaultApiCallDelay)
|
||||
{
|
||||
// La logica è la stessa ma ora passa anche i parametri necessari per salvare nel database
|
||||
return ExecuteApiRequest(url, apiKey, apiKeyHeader, hostHeader, hostValue, delay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Versione asincrona di ExecuteApiRequest
|
||||
/// </summary>
|
||||
public static async Task<RestResponse> ExecuteApiRequestAsync(string url, string apiKey, string apiKeyHeader, string hostHeader, string hostValue, int delay = DefaultApiCallDelay)
|
||||
{
|
||||
var clientApi = new RestClient(url);
|
||||
var request = new RestRequest();
|
||||
request.AddHeader(apiKeyHeader, apiKey);
|
||||
request.AddHeader(hostHeader, hostValue);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await clientApi.ExecuteAsync(request);
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
throw new Exception($"Errore nella richiesta API: {response.ErrorMessage}");
|
||||
}
|
||||
|
||||
// Aggiungi una pausa tra una chiamata e l'altra
|
||||
if (delay > 0)
|
||||
{
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Errore durante la richiesta API: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Versione asincrona di ExecuteApiRequest con informazioni di tipo
|
||||
/// </summary>
|
||||
public static async Task<RestResponse> ExecuteApiRequestAsync(string url, string apiKey, string apiKeyHeader, string hostHeader, string hostValue,
|
||||
string apiType, string parameters, int delay = DefaultApiCallDelay)
|
||||
{
|
||||
// La logica è la stessa ma ora passa anche i parametri necessari per salvare nel database
|
||||
return await ExecuteApiRequestAsync(url, apiKey, apiKeyHeader, hostHeader, hostValue, delay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Salva la risposta dell'API su file per debugging
|
||||
/// </summary>
|
||||
public static void SaveResponseToFile(string url, string content, string baseFolderName = "ApiResponses")
|
||||
{
|
||||
try
|
||||
{
|
||||
string folderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, baseFolderName);
|
||||
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
string fileName = Path.Combine(folderPath, $"ApiResponse_{DateTime.Now:yyyyMMdd_HHmmss}.json");
|
||||
|
||||
File.WriteAllText(fileName, content);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Errore durante il salvataggio della risposta API: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
namespace HorseRacingPredictor.Manager
|
||||
{
|
||||
internal abstract class Database
|
||||
{
|
||||
// La stringa di connessione viene rimossa da qui e definita nelle classi derivate
|
||||
protected abstract string _connectionString { get; }
|
||||
|
||||
protected SqlConnection GetConnection()
|
||||
{
|
||||
var connection = new SqlConnection(_connectionString);
|
||||
connection.Open();
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void LogError(string operation, Exception ex)
|
||||
{
|
||||
var message = ex != null ? ex.Message : "Nessuna eccezione fornita";
|
||||
Console.WriteLine($"Errore durante {operation}: {message}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue una query SQL e gestisce le eccezioni in modo centralizzato
|
||||
/// </summary>
|
||||
public void ExecuteQuery(string operation, Action<SqlConnection> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
action(connection);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError(operation, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue una query SQL all'interno di una transazione e gestisce le eccezioni
|
||||
/// </summary>
|
||||
public void ExecuteTransactionalQuery(string operation, Action<SqlConnection, SqlTransaction> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var connection = GetConnection())
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
action(connection, transaction);
|
||||
transaction.Commit();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
transaction.Rollback();
|
||||
LogError($"{operation} (transazione rollback)", ex);
|
||||
throw; // Rilancia l'eccezione per gestione di livello superiore
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError(operation, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metodo per verificare se la connessione al database è valida
|
||||
/// </summary>
|
||||
public bool TestConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
return connection.State == System.Data.ConnectionState.Open;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("test della connessione", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
293
HorseRacingPredictor/HorseRacingPredictor/Manager/FileReader.cs
Normal file
293
HorseRacingPredictor/HorseRacingPredictor/Manager/FileReader.cs
Normal file
@@ -0,0 +1,293 @@
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using CsvHelper.TypeConversion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HorseRacingPredictor.Manager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gestore generico per la lettura di file CSV
|
||||
/// </summary>
|
||||
public class FileReader
|
||||
{
|
||||
public class NullableDecimalConverter : DefaultTypeConverter
|
||||
{
|
||||
public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
|
||||
{
|
||||
return ConvertToDecimal(text);
|
||||
}
|
||||
}
|
||||
|
||||
public class NullableIntConverter : DefaultTypeConverter
|
||||
{
|
||||
public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
|
||||
{
|
||||
return ConvertToInt(text);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Legge un file CSV e lo converte in un DataTable
|
||||
/// </summary>
|
||||
/// <param name="filePath">Percorso del file CSV</param>
|
||||
/// <returns>DataTable contenente i dati del file CSV</returns>
|
||||
public DataTable ReadCsvToDataTable(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
throw new ArgumentException("Il percorso del file non può essere vuoto.", nameof(filePath));
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"File non trovato: {filePath}");
|
||||
}
|
||||
|
||||
var dataTable = new DataTable();
|
||||
|
||||
// Configurazione per CsvHelper
|
||||
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
HasHeaderRecord = true,
|
||||
MissingFieldFound = null, // Ignora campi mancanti
|
||||
TrimOptions = TrimOptions.Trim, // Rimuove spazi iniziali e finali
|
||||
PrepareHeaderForMatch = args => args.Header.Replace("#", "").Trim() // Rimuove # e spazi dai nomi delle colonne
|
||||
};
|
||||
|
||||
using (var reader = new StreamReader(filePath))
|
||||
using (var csv = new CsvReader(reader, config))
|
||||
{
|
||||
// Legge l'intestazione
|
||||
csv.Read();
|
||||
csv.ReadHeader();
|
||||
|
||||
// Crea le colonne della tabella dai nomi delle colonne del CSV
|
||||
foreach (var header in csv.HeaderRecord)
|
||||
{
|
||||
string columnName = header.Replace("#", "").Trim();
|
||||
dataTable.Columns.Add(columnName);
|
||||
}
|
||||
|
||||
// Legge i dati riga per riga
|
||||
while (csv.Read())
|
||||
{
|
||||
var row = dataTable.NewRow();
|
||||
for (int i = 0; i < csv.HeaderRecord.Length; i++)
|
||||
{
|
||||
string columnName = csv.HeaderRecord[i].Replace("#", "").Trim();
|
||||
string value = csv.GetField(i) ?? string.Empty;
|
||||
row[columnName] = value;
|
||||
}
|
||||
dataTable.Rows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene una lista di tutti i file CSV in una cartella e nelle sue sottocartelle
|
||||
/// </summary>
|
||||
/// <param name="folderPath">Percorso della cartella da esplorare</param>
|
||||
/// <returns>Lista di percorsi dei file CSV trovati</returns>
|
||||
public List<string> GetCsvFiles(string folderPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(folderPath))
|
||||
{
|
||||
throw new ArgumentException("Il percorso della cartella non può essere vuoto.", nameof(folderPath));
|
||||
}
|
||||
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Directory non trovata: {folderPath}");
|
||||
}
|
||||
|
||||
var csvFiles = Directory.GetFiles(folderPath, "*.csv", SearchOption.AllDirectories);
|
||||
return new List<string>(csvFiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metodo helper per estrarre un valore stringa da una riga di DataTable
|
||||
/// </summary>
|
||||
public string GetStringValue(DataRow row, string columnName)
|
||||
{
|
||||
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return row[columnName].ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metodo helper per estrarre un valore intero da una riga di DataTable
|
||||
/// Gestisce valori nulli o non validi restituendo 0
|
||||
/// </summary>
|
||||
public int GetIntValue(DataRow row, string columnName)
|
||||
{
|
||||
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (int.TryParse(row[columnName].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metodo helper per estrarre un valore intero nullable da una riga di DataTable
|
||||
/// Restituisce null per valori mancanti o non validi
|
||||
/// </summary>
|
||||
public int? GetNullableIntValue(DataRow row, string columnName)
|
||||
{
|
||||
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string stringValue = row[columnName].ToString();
|
||||
if (string.IsNullOrWhiteSpace(stringValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (int.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metodo helper per estrarre un valore decimale da una riga di DataTable con controllo di precisione
|
||||
/// Gestisce valori nulli o non validi restituendo 0
|
||||
/// </summary>
|
||||
public decimal GetDecimalValue(DataRow row, string columnName, int precision, int scale)
|
||||
{
|
||||
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||
{
|
||||
return 0.0M;
|
||||
}
|
||||
|
||||
if (decimal.TryParse(row[columnName].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
|
||||
{
|
||||
// Arrotonda il valore per rispettare il limite di precisione e scala
|
||||
decimal factor = (decimal)Math.Pow(10, scale);
|
||||
result = Math.Round(result, scale);
|
||||
|
||||
// Verifica se il valore è entro i limiti di precisione
|
||||
decimal maxValue = (decimal)Math.Pow(10, precision - scale) - (1 / factor);
|
||||
if (Math.Abs(result) > maxValue)
|
||||
{
|
||||
Console.WriteLine($"Valore fuori range per {columnName}: {result}, limitato a {maxValue}");
|
||||
result = result < 0 ? -maxValue : maxValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return 0.0M;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metodo helper per estrarre un valore decimale nullable da una riga di DataTable
|
||||
/// Restituisce null per valori mancanti o non validi
|
||||
/// </summary>
|
||||
public decimal? GetNullableDecimalValue(DataRow row, string columnName, int precision, int scale)
|
||||
{
|
||||
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string stringValue = row[columnName].ToString();
|
||||
if (string.IsNullOrWhiteSpace(stringValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (decimal.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
|
||||
{
|
||||
// Arrotonda il valore per rispettare il limite di precisione e scala
|
||||
decimal factor = (decimal)Math.Pow(10, scale);
|
||||
result = Math.Round(result, scale);
|
||||
|
||||
// Verifica se il valore è entro i limiti di precisione
|
||||
decimal maxValue = (decimal)Math.Pow(10, precision - scale) - (1 / factor);
|
||||
if (Math.Abs(result) > maxValue)
|
||||
{
|
||||
Console.WriteLine($"Valore fuori range per {columnName}: {result}, limitato a {maxValue}");
|
||||
result = result < 0 ? -maxValue : maxValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converte un valore stringa in int con gestione dei valori nulli o non validi
|
||||
/// </summary>
|
||||
public static int ConvertToInt(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return 0;
|
||||
|
||||
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
|
||||
return result;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converte un valore stringa in decimal con gestione dei valori nulli o non validi
|
||||
/// </summary>
|
||||
public static decimal ConvertToDecimal(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return 0m;
|
||||
|
||||
if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
|
||||
return result;
|
||||
|
||||
return 0m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converte un valore stringa in int? con gestione dei valori nulli o non validi
|
||||
/// </summary>
|
||||
public static int? ConvertToNullableInt(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return null;
|
||||
|
||||
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
|
||||
return result;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converte un valore stringa in decimal? con gestione dei valori nulli o non validi
|
||||
/// </summary>
|
||||
public static decimal? ConvertToNullableDecimal(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return null;
|
||||
|
||||
if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
|
||||
return result;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user