Merge upgrade-to-NET8: .NET Framework 4.8.1 → .NET 10.0 + appsettings.json migration

This commit is contained in:
2026-04-07 14:46:06 +02:00
42 changed files with 351050 additions and 726 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+220
View File
@@ -0,0 +1,220 @@
# Projects and dependencies analysis
This document provides a comprehensive overview of the projects and their dependencies in the context of upgrading to .NETCoreApp,Version=v10.0.
## Table of Contents
- [Executive Summary](#executive-Summary)
- [Highlevel Metrics](#highlevel-metrics)
- [Projects Compatibility](#projects-compatibility)
- [Package Compatibility](#package-compatibility)
- [API Compatibility](#api-compatibility)
- [Aggregate NuGet packages details](#aggregate-nuget-packages-details)
- [Top API Migration Challenges](#top-api-migration-challenges)
- [Technologies and Features](#technologies-and-features)
- [Most Frequent API Issues](#most-frequent-api-issues)
- [Projects Relationship Graph](#projects-relationship-graph)
- [Project Details](#project-details)
- [HorseRacingPredictor\BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj)
## Executive Summary
### Highlevel Metrics
| Metric | Count | Status |
| :--- | :---: | :--- |
| Total Projects | 1 | All require upgrade |
| Total NuGet Packages | 26 | 9 need upgrade |
| Total Code Files | 39 | |
| Total Code Files with Incidents | 27 | |
| Total Lines of Code | 7140 | |
| Total Number of Issues | 2202 | |
| Estimated LOC to modify | 2185+ | at least 30,6% of codebase |
### Projects Compatibility
| Project | Target Framework | Difficulty | Package Issues | API Issues | Est. LOC Impact | Description |
| :--- | :---: | :---: | :---: | :---: | :---: | :--- |
| [HorseRacingPredictor\BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | net481 | 🟡 Medium | 15 | 2185 | 2185+ | ClassicWpf, Sdk Style = False |
### Package Compatibility
| Status | Count | Percentage |
| :--- | :---: | :---: |
| ✅ Compatible | 17 | 65,4% |
| ⚠️ Incompatible | 0 | 0,0% |
| 🔄 Upgrade Recommended | 9 | 34,6% |
| ***Total NuGet Packages*** | ***26*** | ***100%*** |
### API Compatibility
| Category | Count | Impact |
| :--- | :---: | :--- |
| 🔴 Binary Incompatible | 897 | High - Require code changes |
| 🟡 Source Incompatible | 1264 | Medium - Needs re-compilation and potential conflicting API error fixing |
| 🔵 Behavioral change | 24 | Low - Behavioral changes that may require testing at runtime |
| ✅ Compatible | 10445 | |
| ***Total APIs Analyzed*** | ***12630*** | |
## Aggregate NuGet packages details
| Package | Current Version | Suggested Version | Projects | Description |
| :--- | :---: | :---: | :--- | :--- |
| CsvHelper | 33.1.0 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| Microsoft.Bcl.AsyncInterfaces | 10.0.0-rc.1.25451.107 | 10.0.5 | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | È consigliabile eseguire l'aggiornamento del pacchetto NuGet |
| Microsoft.Bcl.HashCode | 6.0.0 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| Microsoft.Bcl.Numerics | 10.0.0-rc.1.25451.107 | 10.0.5 | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | È consigliabile eseguire l'aggiornamento del pacchetto NuGet |
| Microsoft.CSharp | 4.7.0 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| Microsoft.ML | 5.0.0-preview.25503.2 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| Microsoft.ML.CpuMath | 5.0.0-preview.25503.2 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| Microsoft.ML.DataView | 5.0.0-preview.25503.2 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| Microsoft.ML.FastTree | 5.0.0-preview.25503.2 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| Microsoft.Web.WebView2 | 1.0.3800.47 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| Newtonsoft.Json | 13.0.4 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| RestSharp | 112.1.1-alpha.0.4 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| System.Buffers | 4.6.1 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | Le funzionalità del pacchetto NuGet sono incluse nel riferimento al framework. |
| System.CodeDom | 10.0.0-rc.1.25451.107 | 10.0.5 | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | È consigliabile eseguire l'aggiornamento del pacchetto NuGet |
| System.Collections.Immutable | 10.0.0-rc.1.25451.107 | 10.0.5 | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | È consigliabile eseguire l'aggiornamento del pacchetto NuGet |
| System.IO.Pipelines | 10.0.0-rc.1.25451.107 | 10.0.5 | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | È consigliabile eseguire l'aggiornamento del pacchetto NuGet |
| System.Memory | 4.6.3 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | Le funzionalità del pacchetto NuGet sono incluse nel riferimento al framework. |
| System.Numerics.Tensors | 10.0.0-rc.1.25451.107 | 10.0.5 | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | È consigliabile eseguire l'aggiornamento del pacchetto NuGet |
| System.Numerics.Vectors | 4.6.1 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | Le funzionalità del pacchetto NuGet sono incluse nel riferimento al framework. |
| System.Reflection.Emit.Lightweight | 4.7.0 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | Le funzionalità del pacchetto NuGet sono incluse nel riferimento al framework. |
| System.Runtime.CompilerServices.Unsafe | 6.1.2 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | ✅Compatible |
| System.Text.Encodings.Web | 10.0.0-rc.1.25451.107 | 10.0.5 | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | È consigliabile eseguire l'aggiornamento del pacchetto NuGet |
| System.Text.Json | 10.0.0-rc.1.25451.107 | 10.0.5 | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | È consigliabile eseguire l'aggiornamento del pacchetto NuGet |
| System.Threading.Channels | 10.0.0-rc.1.25451.107 | 10.0.5 | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | È consigliabile eseguire l'aggiornamento del pacchetto NuGet |
| System.Threading.Tasks.Extensions | 4.6.3 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | Le funzionalità del pacchetto NuGet sono incluse nel riferimento al framework. |
| System.ValueTuple | 4.6.1 | | [BettingPredictor.csproj](#horseracingpredictorbettingpredictorcsproj) | Le funzionalità del pacchetto NuGet sono incluse nel riferimento al framework. |
## Top API Migration Challenges
### Technologies and Features
| Technology | Issues | Percentage | Migration Path |
| :--- | :---: | :---: | :--- |
| WPF (Windows Presentation Foundation) | 566 | 25,9% | WPF APIs for building Windows desktop applications with XAML-based UI that are available in .NET on Windows. WPF provides rich desktop UI capabilities with data binding and styling. Enable Windows Desktop support: Option 1 (Recommended): Target net9.0-windows; Option 2: Add <UseWindowsDesktop>true</UseWindowsDesktop>. |
| Windows Forms | 18 | 0,8% | Windows Forms APIs for building Windows desktop applications with traditional Forms-based UI that are available in .NET on Windows. Enable Windows Desktop support: Option 1 (Recommended): Target net9.0-windows; Option 2: Add <UseWindowsDesktop>true</UseWindowsDesktop>; Option 3 (Legacy): Use Microsoft.NET.Sdk.WindowsDesktop SDK. |
| Legacy Configuration System | 2 | 0,1% | Legacy XML-based configuration system (app.config/web.config) that has been replaced by a more flexible configuration model in .NET Core. The old system was rigid and XML-based. Migrate to Microsoft.Extensions.Configuration with JSON/environment variables; use System.Configuration.ConfigurationManager NuGet package as interim bridge if needed. |
### Most Frequent API Issues
| API | Count | Percentage | Category |
| :--- | :---: | :---: | :--- |
| T:System.Data.SqlClient.SqlParameterCollection | 273 | 12,5% | Source Incompatible |
| P:System.Data.SqlClient.SqlCommand.Parameters | 273 | 12,5% | Source Incompatible |
| T:System.Data.SqlClient.SqlParameter | 273 | 12,5% | Source Incompatible |
| M:System.Data.SqlClient.SqlParameterCollection.AddWithValue(System.String,System.Object) | 270 | 12,4% | Source Incompatible |
| T:System.Windows.Controls.TextBox | 81 | 3,7% | Binary Incompatible |
| T:System.Windows.Controls.TextBlock | 57 | 2,6% | Binary Incompatible |
| T:System.Windows.RoutedEventHandler | 52 | 2,4% | Binary Incompatible |
| P:System.Windows.Controls.TextBox.Text | 47 | 2,2% | Binary Incompatible |
| T:System.Windows.Controls.Button | 42 | 1,9% | Binary Incompatible |
| T:System.Windows.Controls.ComboBox | 41 | 1,9% | Binary Incompatible |
| P:System.Windows.Controls.TextBlock.Text | 40 | 1,8% | Binary Incompatible |
| T:System.Windows.Visibility | 40 | 1,8% | Binary Incompatible |
| T:System.Data.SqlClient.SqlConnection | 35 | 1,6% | Source Incompatible |
| T:System.Data.SqlClient.SqlCommand | 33 | 1,5% | Source Incompatible |
| T:System.Windows.MessageBoxImage | 30 | 1,4% | Binary Incompatible |
| T:System.Windows.MessageBoxButton | 30 | 1,4% | Binary Incompatible |
| M:System.Data.SqlClient.SqlCommand.ExecuteNonQuery | 23 | 1,1% | Source Incompatible |
| M:System.Data.SqlClient.SqlCommand.#ctor(System.String,System.Data.SqlClient.SqlConnection) | 22 | 1,0% | Source Incompatible |
| T:System.Windows.Controls.DatePicker | 22 | 1,0% | Binary Incompatible |
| T:System.Windows.Controls.CheckBox | 20 | 0,9% | Binary Incompatible |
| T:System.Windows.Controls.RadioButton | 19 | 0,9% | Binary Incompatible |
| T:System.Windows.RoutedEventArgs | 17 | 0,8% | Binary Incompatible |
| T:System.Text.Json.JsonDocument | 16 | 0,7% | Behavioral Change |
| T:System.Windows.Controls.SelectionChangedEventHandler | 16 | 0,7% | Binary Incompatible |
| T:System.Windows.Controls.TextChangedEventHandler | 16 | 0,7% | Binary Incompatible |
| F:System.Windows.MessageBoxButton.OK | 15 | 0,7% | Binary Incompatible |
| T:System.Windows.MessageBox | 15 | 0,7% | Binary Incompatible |
| T:System.Windows.MessageBoxResult | 15 | 0,7% | Binary Incompatible |
| M:System.Windows.MessageBox.Show(System.String,System.String,System.Windows.MessageBoxButton,System.Windows.MessageBoxImage) | 15 | 0,7% | Binary Incompatible |
| T:System.Windows.Controls.ProgressBar | 14 | 0,6% | Binary Incompatible |
| P:System.Windows.UIElement.IsEnabled | 14 | 0,6% | Binary Incompatible |
| P:System.Windows.Controls.ContentControl.Content | 13 | 0,6% | Binary Incompatible |
| T:System.Windows.Controls.Grid | 12 | 0,5% | Binary Incompatible |
| M:System.Data.SqlClient.SqlCommand.#ctor(System.String,System.Data.SqlClient.SqlConnection,System.Data.SqlClient.SqlTransaction) | 11 | 0,5% | Source Incompatible |
| E:System.Windows.Controls.Primitives.ButtonBase.Click | 11 | 0,5% | Binary Incompatible |
| T:System.Data.SqlClient.SqlTransaction | 10 | 0,5% | Source Incompatible |
| E:System.Windows.Controls.Primitives.ToggleButton.Checked | 10 | 0,5% | Binary Incompatible |
| P:System.Windows.Controls.DatePicker.SelectedDate | 10 | 0,5% | Binary Incompatible |
| P:System.Windows.Controls.Primitives.RangeBase.Value | 10 | 0,5% | Binary Incompatible |
| P:System.Windows.Controls.Primitives.Selector.SelectedItem | 9 | 0,4% | Binary Incompatible |
| P:System.Windows.Controls.Primitives.ToggleButton.IsChecked | 9 | 0,4% | Binary Incompatible |
| M:System.Data.SqlClient.SqlCommand.ExecuteScalar | 8 | 0,4% | Source Incompatible |
| T:System.Windows.Controls.DataGrid | 8 | 0,4% | Binary Incompatible |
| F:System.Windows.Visibility.Visible | 8 | 0,4% | Binary Incompatible |
| F:System.Windows.Visibility.Collapsed | 8 | 0,4% | Binary Incompatible |
| P:System.Windows.UIElement.Visibility | 8 | 0,4% | Binary Incompatible |
| E:System.Windows.Controls.Primitives.Selector.SelectionChanged | 8 | 0,4% | Binary Incompatible |
| E:System.Windows.Controls.Primitives.TextBoxBase.TextChanged | 8 | 0,4% | Binary Incompatible |
| F:System.Windows.MessageBoxImage.Error | 7 | 0,3% | Binary Incompatible |
| T:System.Uri | 6 | 0,3% | Behavioral Change |
## Projects Relationship Graph
Legend:
📦 SDK-style project
⚙️ Classic project
```mermaid
flowchart LR
P1["<b>⚙️&nbsp;BettingPredictor.csproj</b><br/><small>net481</small>"]
click P1 "#horseracingpredictorbettingpredictorcsproj"
```
## Project Details
<a id="horseracingpredictorbettingpredictorcsproj"></a>
### HorseRacingPredictor\BettingPredictor.csproj
#### Project Info
- **Current Target Framework:** net481
- **Proposed Target Framework:** net10.0-windows
- **SDK-style**: False
- **Project Kind:** ClassicWpf
- **Dependencies**: 0
- **Dependants**: 0
- **Number of Files**: 43
- **Number of Files with Incidents**: 27
- **Lines of Code**: 7140
- **Estimated LOC to modify**: 2185+ (at least 30,6% of the project)
#### Dependency Graph
Legend:
📦 SDK-style project
⚙️ Classic project
```mermaid
flowchart TB
subgraph current["BettingPredictor.csproj"]
MAIN["<b>⚙️&nbsp;BettingPredictor.csproj</b><br/><small>net481</small>"]
click MAIN "#horseracingpredictorbettingpredictorcsproj"
end
```
### API Compatibility
| Category | Count | Impact |
| :--- | :---: | :--- |
| 🔴 Binary Incompatible | 897 | High - Require code changes |
| 🟡 Source Incompatible | 1264 | Medium - Needs re-compilation and potential conflicting API error fixing |
| 🔵 Behavioral change | 24 | Low - Behavioral changes that may require testing at runtime |
| ✅ Compatible | 10445 | |
| ***Total APIs Analyzed*** | ***12630*** | |
#### Project Technologies and Features
| Technology | Issues | Percentage | Migration Path |
| :--- | :---: | :---: | :--- |
| Legacy Configuration System | 2 | 0,1% | Legacy XML-based configuration system (app.config/web.config) that has been replaced by a more flexible configuration model in .NET Core. The old system was rigid and XML-based. Migrate to Microsoft.Extensions.Configuration with JSON/environment variables; use System.Configuration.ConfigurationManager NuGet package as interim bridge if needed. |
| Windows Forms | 18 | 0,8% | Windows Forms APIs for building Windows desktop applications with traditional Forms-based UI that are available in .NET on Windows. Enable Windows Desktop support: Option 1 (Recommended): Target net9.0-windows; Option 2: Add <UseWindowsDesktop>true</UseWindowsDesktop>; Option 3 (Legacy): Use Microsoft.NET.Sdk.WindowsDesktop SDK. |
| WPF (Windows Presentation Foundation) | 566 | 25,9% | WPF APIs for building Windows desktop applications with XAML-based UI that are available in .NET on Windows. WPF provides rich desktop UI capabilities with data binding and styling. Enable Windows Desktop support: Option 1 (Recommended): Target net9.0-windows; Option 2: Add <UseWindowsDesktop>true</UseWindowsDesktop>. |
+116
View File
@@ -0,0 +1,116 @@
## [2026-03-31 21:08] TASK-001: Verify Prerequisites
Status: Complete
- **Verified**:
- .NET 10.0 SDK is installed and compatible
- No global.json found, no conflicts to resolve
### Outcome
Success - All prerequisites verified. Environment ready for .NET 10.0 upgrade.
## [2026-03-31 21:16] TASK-002: Convert Project to SDK-style
Status: Complete
- **Files Modified**: HorseRacingPredictor/HorseRacingPredictor/BettingPredictor.csproj
- **Code Changes**: Converted from classic WPF project to SDK-style format using conversion tool. Removed 8 malformed duplicate PackageReference entries (RestSharp.112.1.1-alpha, System.CodeDom.10.0.0-rc, etc.) that were created by the conversion tool alongside the correct entries.
- **Verified**:
- Project uses `<Project Sdk="Microsoft.NET.Sdk">` format
- `<UseWPF>true</UseWPF>` is present
- `<UseWindowsForms>true</UseWindowsForms>` is present
- All source files properly included via globbing
- Malformed duplicate package entries cleaned up
### Outcome
Success - Project successfully converted to SDK-style format with cleanup.
## [2026-03-31 21:21] TASK-003: Update Target Framework and Project Configuration
Status: Complete
- **Files Modified**: HorseRacingPredictor/HorseRacingPredictor/BettingPredictor.csproj
- **Code Changes**: Changed TargetFramework from net481 to net10.0-windows. Removed unnecessary ImportWindowsDesktopTargets property.
- **Verified**:
- TargetFramework is net10.0-windows
- UseWPF is true
- UseWindowsForms is true
- ImportWindowsDesktopTargets removed (redundant with -windows TFM)
### Outcome
Success - Target framework updated to net10.0-windows.
## [2026-03-31 21:30] TASK-004: Update NuGet Package References
Status: Complete
- **Files Modified**: HorseRacingPredictor/HorseRacingPredictor/BettingPredictor.csproj
- **Code Changes**:
- Removed legacy assembly References (System.Web, System.Data.DataSetExtensions, Microsoft.CSharp, System.Net.Http)
- Updated Microsoft.Bcl.AsyncInterfaces, Microsoft.Bcl.Numerics to 10.0.5
- Updated System.Numerics.Tensors to 10.0.5
- Removed 13 framework-included packages: Microsoft.CSharp, System.Buffers, System.CodeDom, System.Collections.Immutable, System.IO.Pipelines, System.Memory, System.Numerics.Vectors, System.Reflection.Emit.Lightweight, System.Runtime.CompilerServices.Unsafe, System.Text.Encodings.Web, System.Text.Json, System.Threading.Channels, System.Threading.Tasks.Extensions, System.ValueTuple
- Added Microsoft.Data.SqlClient 7.0.0
- Added System.Configuration.ConfigurationManager 10.0.5
- **Verified**: dotnet restore completed with 0 errors and only 1 informational warning (NU1510)
### Outcome
Success - All NuGet packages updated, removed, or added per plan. Restore clean.
## [2026-03-31 21:38] TASK-005: Migrate SqlClient Namespace
Status: Complete
- **Files Modified**:
- Football/Database/APIResponse.cs, BetType.cs, Bookmaker.cs, Comparison.cs, Fixture.cs, FixtureLeague.cs, Goals.cs, H2H.cs, League.cs, LeagueStats.cs, Odds.cs, Prediction.cs, Score.cs, Team.cs, TeamStats.cs
- Football/Manager/Database.cs
- Manager/Database.cs
- HorseRacing/Main.cs
- (Additional file: HorseRacing/Manager/Database.cs if present)
- **Code Changes**: Replaced all `System.Data.SqlClient` references with `Microsoft.Data.SqlClient` across 19 files (including using directives and fully-qualified references in Fixture.cs)
- **Errors Fixed**: APIResponse.cs was accidentally emptied due to file lock during bulk replace; recovered from git and manually patched
- **Verified**:
- Zero remaining `System.Data.SqlClient` references
- All files confirmed using `Microsoft.Data.SqlClient`
### Outcome
Success - SqlClient namespace migration complete across all 19 affected files.
## [2026-03-31 21:46] TASK-006: Build Solution and Fix Compilation Errors
Status: Complete
- **Files Deleted**: HorseRacingPredictor/HorseRacingPredictor/CsvHelperStubs.cs
- **Code Changes**: Removed CsvHelperStubs.cs which contained placeholder type definitions conflicting with actual CsvHelper 33.1.0 package types (IReaderRow, MemberMapData, DefaultTypeConverter, CsvReader, etc.)
- **Errors Fixed**: 4 CS0104 ambiguous reference errors in Manager/FileReader.cs (IReaderRow, MemberMapData) resolved by removing conflicting stub file
- **Build Status**: Successful — 0 errors, 1 warning (NU1510 informational)
### Outcome
Success - Solution builds with 0 errors on .NET 10.0.
## [2026-03-31 21:54] TASK-007: Final Verification and Commit
Status: Complete
- **Verified**:
- Project targets net10.0-windows ✅
- Project uses SDK-style format (Microsoft.NET.Sdk) ✅
- UseWPF enabled ✅
- Microsoft.Data.SqlClient 7.0.0 present ✅
- System.Configuration.ConfigurationManager 10.0.5 present ✅
- Zero remaining System.Data.SqlClient references ✅
- Framework-included packages removed ✅
- Solution builds with 0 errors ✅
- **Commits**: cfb29cc: "Upgrade BettingPredictor from .NET Framework 4.8.1 to .NET 10.0"
- **Files Modified**: 24 files (211 insertions, 424 deletions)
### Outcome
Success - All success criteria verified, changes committed on upgrade-to-NET8 branch.
+592
View File
@@ -0,0 +1,592 @@
# .NET 10.0 Upgrade Plan — BettingPredictor
## Table of Contents
- [1. Executive Summary](#1-executive-summary)
- [1.1 Scope & Target](#11-scope--target)
- [1.2 Selected Strategy](#12-selected-strategy)
- [1.3 Complexity Classification](#13-complexity-classification)
- [1.4 Critical Issues](#14-critical-issues)
- [2. Migration Strategy](#2-migration-strategy)
- [2.1 Approach & Justification](#21-approach--justification)
- [2.2 Prerequisites](#22-prerequisites)
- [2.3 Implementation Timeline](#23-implementation-timeline)
- [3. Detailed Dependency Analysis](#3-detailed-dependency-analysis)
- [4. Project-by-Project Plans](#4-project-by-project-plans)
- [4.1 BettingPredictor.csproj](#41-bettingpredictorcsproj)
- [5. Package Update Reference](#5-package-update-reference)
- [5.1 Packages to Update](#51-packages-to-update)
- [5.2 Packages to Remove (Framework-Included)](#52-packages-to-remove-framework-included)
- [5.3 Packages to Add](#53-packages-to-add)
- [5.4 Compatible Packages (No Change)](#54-compatible-packages-no-change)
- [6. Breaking Changes Catalog](#6-breaking-changes-catalog)
- [6.1 SqlClient Namespace Migration](#61-sqlclient-namespace-migration)
- [6.2 WPF Binary Incompatibilities](#62-wpf-binary-incompatibilities)
- [6.3 Windows Forms API References](#63-windows-forms-api-references)
- [6.4 Legacy Configuration System](#64-legacy-configuration-system)
- [6.5 Behavioral Changes](#65-behavioral-changes)
- [7. Testing & Validation Strategy](#7-testing--validation-strategy)
- [8. Risk Management](#8-risk-management)
- [9. Complexity & Effort Assessment](#9-complexity--effort-assessment)
- [10. Source Control Strategy](#10-source-control-strategy)
- [11. Success Criteria](#11-success-criteria)
---
## 1. Executive Summary
### 1.1 Scope & Target
| Property | Value |
|---|---|
| **Solution** | BettingPredictor.sln |
| **Projects** | 1 (BettingPredictor.csproj) |
| **Project Type** | Classic WPF (non SDK-style) |
| **Current Framework** | .NET Framework 4.8.1 (net481) |
| **Target Framework** | .NET 10.0 (net10.0-windows) |
| **Total LOC** | 7,140 |
| **Total Files** | 39 (27 with compatibility issues) |
| **NuGet Packages** | 26 total — 9 to update, 6 to remove (framework-included) |
| **Total Issues** | 2,202 |
| **Estimated LOC Impact** | 2,185+ (~30.6% of codebase) |
### 1.2 Selected Strategy
**All-At-Once Strategy** — Single project upgraded in one atomic operation.
**Rationale**:
- 1 project (well under 30-project threshold)
- No inter-project dependencies
- Homogeneous codebase (single WPF desktop application)
- All 9 packages requiring update have known target versions (stable 10.0.5)
- No incompatible packages — all have clear upgrade or removal paths
- Fastest completion with single coordinated upgrade
### 1.3 Complexity Classification
**Simple** — ?5 projects, dependency depth 0, no security vulnerabilities, no circular dependencies.
| Criterion | Value | Threshold |
|---|---|---|
| Project count | 1 | ? 5 ? |
| Dependency depth | 0 | ? 2 ? |
| Security vulnerabilities | 0 | None ? |
| High-risk items | 0 | None ? |
**Iteration strategy**: Simple batch — all project details in 12 detail iterations.
### 1.4 Critical Issues
| Priority | Issue | Impact | Resolution |
|---|---|---|---|
| ?? Mandatory | SDK-style conversion | Project won't build in .NET 10 without SDK-style format | Use SDK-style conversion tool |
| ?? Mandatory | Target framework change | net481 ? net10.0-windows | Update TargetFramework in .csproj |
| ?? High | SqlClient namespace migration | 1,264 source-incompatible API references across 16+ files | Replace `System.Data.SqlClient` with `Microsoft.Data.SqlClient` |
| ?? High | 9 pre-release packages ? stable | rc.1 packages must move to stable 10.0.5 | Update PackageReference versions |
| ?? Medium | 6 framework-included packages | Redundant packages may cause conflicts | Remove PackageReference entries |
| ?? Low | WPF binary incompatibilities (897) | Resolved automatically by recompilation against .NET 10.0 WPF | No code changes — recompile with net10.0-windows |
| ?? Low | Behavioral changes (24) | JsonDocument, System.Uri — may affect runtime behavior | Requires runtime testing and validation |
| ?? Low | Legacy configuration (2) | Settings.Designer.cs uses old config system | Add `System.Configuration.ConfigurationManager` NuGet as bridge |
---
## 2. Migration Strategy
### 2.1 Approach & Justification
**All-At-Once** — All updates performed as a single coordinated atomic operation with no intermediate states.
This is ideal because:
- **Single project**: no dependency ordering needed
- **Clear package paths**: all 9 packages have exact target versions (10.0.5 stable)
- **No incompatible packages**: 0% incompatible, no blocking issues
- **WPF continuity**: WPF is fully supported in .NET 10.0 with `-windows` TFM — same APIs, new runtime
- **Risk is contained**: single project means build failures are immediately visible and fixable
### 2.2 Prerequisites
Before starting the atomic upgrade:
1. **Verify .NET 10.0 SDK installation**
- Required SDK version: .NET 10.0 or later
- Download from: https://dotnet.microsoft.com/download/dotnet/10.0
- Verify with: `dotnet --list-sdks`
2. **Check global.json** (if present)
- Ensure it allows .NET 10.0 SDK or update/remove it
- If present, update `sdk.version` to a .NET 10.0 compatible version
3. **Source control**
- Working branch: `upgrade-to-NET8` (from `main`)
- No pending changes
### 2.3 Implementation Timeline
#### Phase 0: Preparation
- Verify .NET 10.0 SDK installation
- Validate global.json compatibility
#### Phase 1: Atomic Upgrade
**Operations** (performed as single coordinated batch):
1. Convert project to SDK-style format
2. Update TargetFramework to `net10.0-windows`
3. Update all 9 package references to stable versions
4. Remove 6 framework-included package references
5. Add `Microsoft.Data.SqlClient` package
6. Replace all `System.Data.SqlClient` usages with `Microsoft.Data.SqlClient`
7. Address legacy configuration bridge (add `System.Configuration.ConfigurationManager` if needed)
8. Build solution and fix all compilation errors
9. Verify: solution builds with 0 errors
**Deliverable**: Solution builds successfully targeting net10.0-windows
#### Phase 2: Validation
**Operations**:
- No automated test projects exist in the solution
- Manual verification of application startup and core functionality
- Review behavioral changes (JsonDocument, System.Uri) at runtime
**Deliverable**: Application runs correctly on .NET 10.0
---
## 3. Detailed Dependency Analysis
This solution contains a **single project** with **zero project dependencies** and **zero dependants**. There is no dependency graph to navigate — the upgrade operates on one isolated project.
```mermaid
flowchart LR
P1["BettingPredictor.csproj\nnet481 ? net10.0-windows"]
```
| Property | Value |
|---|---|
| Total projects | 1 |
| Dependency depth | 0 |
| Circular dependencies | None |
| Critical path | BettingPredictor.csproj (only project) |
| Migration phases needed | 1 (atomic) |
| Test projects | 0 |
Since there is only one project with no dependencies, the entire upgrade is executed as a single atomic operation. No phased ordering is required.
---
## 4. Project-by-Project Plans
### 4.1 BettingPredictor.csproj
#### Current State
| Property | Value |
|---|---|
| **Path** | `HorseRacingPredictor\HorseRacingPredictor\BettingPredictor.csproj` |
| **Target Framework** | net481 (.NET Framework 4.8.1) |
| **SDK-style** | ? No (Classic WPF format) |
| **Project Kind** | ClassicWpf |
| **Lines of Code** | 7,140 |
| **Files** | 43 total, 27 with incidents |
| **NuGet Packages** | 26 (9 update, 6 remove, 11 compatible) |
| **Dependencies** | 0 project references |
| **Dependants** | 0 |
| **Risk Level** | ?? Medium |
**Key technologies used**:
- WPF (Windows Presentation Foundation) — primary UI framework
- WebView2 — embedded browser control
- Microsoft.ML / ML.NET — machine learning predictions
- System.Data.SqlClient — SQL database access
- RestSharp — REST API client
- CsvHelper — CSV file handling
- Newtonsoft.Json & System.Text.Json — JSON serialization
#### Target State
| Property | Value |
|---|---|
| **Target Framework** | net10.0-windows |
| **SDK-style** | ? Yes |
| **Updated packages** | 9 |
| **Removed packages** | 6 (framework-included) |
| **New packages** | Microsoft.Data.SqlClient, System.Configuration.ConfigurationManager |
#### Migration Steps
**Step 1: Convert to SDK-style project**
- Use the SDK-style conversion tool on `BettingPredictor.csproj`
- The conversion tool handles:
- Migrating `<PackageReference>` entries from `packages.config`
- Setting up the new SDK-style project structure
- Preserving existing project configuration
- After conversion, the `.csproj` will use `Microsoft.NET.Sdk` format
**Step 2: Update TargetFramework**
- Change `<TargetFramework>` to `net10.0-windows`
- This enables WPF and WinForms support via the `-windows` platform specifier
- No need for `<UseWPF>true</UseWPF>` separately — SDK-style WPF projects include this automatically when converted
**Step 3: Update package references (9 packages)**
- Update all pre-release (rc.1) packages to stable 10.0.5 versions
- See [§5.1 Packages to Update](#51-packages-to-update) for complete version matrix
**Step 4: Remove framework-included packages (6 packages)**
- Remove references to packages whose functionality is now built into .NET 10.0
- See [§5.2 Packages to Remove](#52-packages-to-remove-framework-included) for complete list
**Step 5: Migrate SqlClient**
- Add `Microsoft.Data.SqlClient` NuGet package
- In all affected files, replace:
- `using System.Data.SqlClient;` ? `using Microsoft.Data.SqlClient;`
- The API surface is identical — `SqlConnection`, `SqlCommand`, `SqlParameter`, `SqlTransaction` all exist in `Microsoft.Data.SqlClient` with the same signatures
- **Affected files** (16 files, ~1,264 API references):
- `Manager\Database.cs` (12 issues) — base database class with `GetConnection()`, transactions
- `Football\Manager\Database.cs` (10 issues) — football-specific DB operations
- `Football\Database\APIResponse.cs` (82 issues)
- `Football\Database\LeagueStats.cs` (76 issues)
- `Football\Database\Fixture.cs` (67 issues)
- `Football\Database\Comparison.cs` (64 issues)
- `Football\Database\League.cs` (57 issues)
- `Football\Database\TeamStats.cs` (56 issues)
- `Football\Database\Prediction.cs` (52 issues)
- `Football\Database\Odds.cs` (43 issues)
- `Football\Database\Score.cs` (40 issues)
- `Football\Database\FixtureLeague.cs` (39 issues)
- `Football\Database\H2H.cs` (20 issues)
- `Football\Database\Goals.cs` (16 issues)
- `Football\Database\BetType.cs` (12 issues)
- `Football\Database\Bookmaker.cs` (12 issues)
**Step 6: Address legacy configuration**
- `Properties\Settings.Designer.cs` (2 issues) uses the legacy `System.Configuration` APIs
- Add NuGet package `System.Configuration.ConfigurationManager` as an interim bridge
- This provides the `ConfigurationManager`, `Settings`, and related classes on .NET 10.0
- ?? Long-term recommendation: migrate to `Microsoft.Extensions.Configuration` with `appsettings.json`
**Step 7: Restore, build, and fix compilation errors**
- Run `dotnet restore` to resolve all updated package references
- Build the entire solution
- Fix any compilation errors discovered (expected areas: SqlClient namespace, removed APIs, configuration)
- Rebuild and verify: **0 compilation errors**
#### Validation Checklist
- [ ] Project converted to SDK-style
- [ ] TargetFramework set to net10.0-windows
- [ ] All 9 packages updated to stable versions
- [ ] 6 framework-included packages removed
- [ ] Microsoft.Data.SqlClient added and all usages migrated
- [ ] System.Configuration.ConfigurationManager added (if needed)
- [ ] Solution builds with 0 errors
- [ ] Solution builds with 0 warnings (best effort)
- [ ] Application starts and main UI renders correctly
---
## 5. Package Update Reference
### 5.1 Packages to Update
All 9 packages are currently on pre-release `10.0.0-rc.1.25451.107` and must be updated to stable `10.0.5`.
| Package | Current Version | Target Version | Reason |
|---|---|---|---|
| Microsoft.Bcl.AsyncInterfaces | 10.0.0-rc.1.25451.107 | 10.0.5 | Pre-release ? stable |
| Microsoft.Bcl.Numerics | 10.0.0-rc.1.25451.107 | 10.0.5 | Pre-release ? stable |
| System.CodeDom | 10.0.0-rc.1.25451.107 | 10.0.5 | Pre-release ? stable |
| System.Collections.Immutable | 10.0.0-rc.1.25451.107 | 10.0.5 | Pre-release ? stable |
| System.IO.Pipelines | 10.0.0-rc.1.25451.107 | 10.0.5 | Pre-release ? stable |
| System.Numerics.Tensors | 10.0.0-rc.1.25451.107 | 10.0.5 | Pre-release ? stable |
| System.Text.Encodings.Web | 10.0.0-rc.1.25451.107 | 10.0.5 | Pre-release ? stable |
| System.Text.Json | 10.0.0-rc.1.25451.107 | 10.0.5 | Pre-release ? stable |
| System.Threading.Channels | 10.0.0-rc.1.25451.107 | 10.0.5 | Pre-release ? stable |
### 5.2 Packages to Remove (Framework-Included)
These packages provide functionality that is now built into the .NET 10.0 runtime. Their `PackageReference` entries should be removed to avoid version conflicts.
| Package | Current Version | Reason for Removal |
|---|---|---|
| System.Buffers | 4.6.1 | Included in .NET 10.0 runtime |
| System.Memory | 4.6.3 | Included in .NET 10.0 runtime |
| System.Numerics.Vectors | 4.6.1 | Included in .NET 10.0 runtime |
| System.Reflection.Emit.Lightweight | 4.7.0 | Included in .NET 10.0 runtime |
| System.Threading.Tasks.Extensions | 4.6.3 | Included in .NET 10.0 runtime |
| System.ValueTuple | 4.6.1 | Included in .NET 10.0 runtime |
### 5.3 Packages to Add
| Package | Version | Reason |
|---|---|---|
| Microsoft.Data.SqlClient | Latest stable | Replaces `System.Data.SqlClient` for .NET 10.0 |
| System.Configuration.ConfigurationManager | Latest stable for net10.0 | Bridge for legacy `Settings.Designer.cs` configuration |
### 5.4 Compatible Packages (No Change)
These packages are already compatible with .NET 10.0 and require no version changes.
| Package | Current Version | Notes |
|---|---|---|
| CsvHelper | 33.1.0 | ? Compatible |
| Microsoft.Bcl.HashCode | 6.0.0 | ? Compatible |
| Microsoft.CSharp | 4.7.0 | ? Compatible |
| Microsoft.ML | 5.0.0-preview.25503.2 | ? Compatible (pre-release but no stable alternative) |
| Microsoft.ML.CpuMath | 5.0.0-preview.25503.2 | ? Compatible |
| Microsoft.ML.DataView | 5.0.0-preview.25503.2 | ? Compatible |
| Microsoft.ML.FastTree | 5.0.0-preview.25503.2 | ? Compatible |
| Microsoft.Web.WebView2 | 1.0.3800.47 | ? Compatible |
| Newtonsoft.Json | 13.0.4 | ? Compatible |
| RestSharp | 112.1.1-alpha.0.4 | ? Compatible |
| System.Runtime.CompilerServices.Unsafe | 6.1.2 | ? Compatible |
---
## 6. Breaking Changes Catalog
### 6.1 SqlClient Namespace Migration
**Category**: Source Incompatible
**Impact**: 1,264 API references across 16+ files
**Severity**: High — requires code changes in every database access file
**Problem**: `System.Data.SqlClient` is not available as a built-in namespace in .NET 10.0. The legacy `System.Data.SqlClient` namespace shipped with .NET Framework is replaced by the standalone `Microsoft.Data.SqlClient` package.
**Resolution**:
1. Add NuGet package `Microsoft.Data.SqlClient`
2. In every file that uses `System.Data.SqlClient`, change:
```csharp
// Before
using System.Data.SqlClient;
// After
using Microsoft.Data.SqlClient;
```
3. The API surface is **API-compatible** — same class names (`SqlConnection`, `SqlCommand`, `SqlParameter`, `SqlTransaction`, etc.) with identical method signatures
4. No logic changes required — only the `using` directive changes
**Affected classes and methods** (most frequent):
- `SqlParameterCollection` / `SqlCommand.Parameters` (273 references)
- `SqlParameter` (273 references)
- `SqlParameterCollection.AddWithValue()` (270 references)
- `SqlConnection` (35 references)
- `SqlCommand` (33 references)
- `SqlCommand.ExecuteNonQuery()` (23 references)
- `SqlCommand.ExecuteScalar()` (8 references)
- `SqlTransaction` / `BeginTransaction()` / `Commit()` / `Rollback()` (10 references)
### 6.2 WPF Binary Incompatibilities
**Category**: Binary Incompatible
**Impact**: 897 API references
**Severity**: Low — **resolved automatically by recompilation**
**Problem**: WPF types in .NET 10.0 are binary-incompatible with .NET Framework 4.8.1 assemblies. The types exist at the same namespaces and with the same API surfaces, but are in different assemblies.
**Resolution**: No code changes needed. Targeting `net10.0-windows` and recompiling resolves all 897 binary incompatibilities. The WPF APIs (`TextBox`, `TextBlock`, `Button`, `ComboBox`, `DataGrid`, `DatePicker`, `CheckBox`, `RadioButton`, `ProgressBar`, `MessageBox`, `Grid`, `Visibility`, etc.) are fully available in .NET 10.0 WPF.
### 6.3 Windows Forms API References
**Category**: Binary Incompatible
**Impact**: 18 API references
**Severity**: Low — **resolved automatically by targeting net10.0-windows**
**Problem**: The project uses some Windows Forms APIs (likely `FolderBrowserDialog`, `OpenFileDialog`, or similar interop). These are binary incompatible across frameworks.
**Resolution**: The `-windows` platform specifier in `net10.0-windows` automatically enables both WPF and WinForms support. Recompilation resolves these references.
### 6.4 Legacy Configuration System
**Category**: Source Incompatible
**Impact**: 2 API references in `Properties\Settings.Designer.cs`
**Severity**: Low
**Problem**: The legacy `System.Configuration` APIs (`ConfigurationManager`, `ApplicationSettingsBase`, etc.) from `app.config` / `web.config` are not built into .NET 10.0.
**Resolution**:
- Add NuGet package `System.Configuration.ConfigurationManager` to provide the legacy APIs as a bridge
- `Properties\Settings.Designer.cs` will compile and work as before
- ?? **Long-term recommendation**: Migrate to `Microsoft.Extensions.Configuration` with JSON-based configuration (`appsettings.json`), but this is optional and can be deferred
### 6.5 Behavioral Changes
**Category**: Behavioral Change
**Impact**: 24 API references (16 JsonDocument, 6 System.Uri, 2 other)
**Severity**: Low — no compilation errors, but may cause runtime behavior differences
#### 6.5.1 System.Text.Json.JsonDocument (16 references)
**Files affected**: `HorseRacing\API\RacingApiClient.cs`, `HorseRacing\Main.cs`
**Potential changes in .NET 10.0**:
- Stricter JSON parsing by default
- Changes to how trailing commas, comments, and edge cases are handled
- `JsonSerializerOptions` defaults may differ
**Mitigation**: Test JSON parsing scenarios thoroughly. If issues arise, configure `JsonSerializerOptions` or `JsonDocumentOptions` to match previous behavior (e.g., `AllowTrailingCommas = true`).
#### 6.5.2 System.Uri (6 references)
**Files affected**: `HorseRacing\API\RacingApiClient.cs`, `HorseRacing\Main.cs`
**Potential changes in .NET 10.0**:
- More strict URI parsing conforming to RFC 3986
- Changes in how relative URIs and encoding edge cases are handled
**Mitigation**: Test URI-based operations (API endpoint construction, WebView2 navigation). Typically no issues with standard HTTP/HTTPS URIs.
---
## 7. Testing & Validation Strategy
### 7.1 Automated Tests
?? **No automated test projects exist** in this solution. There are no unit test, integration test, or other test projects to execute.
### 7.2 Build Verification
The primary automated validation is build success:
- [ ] `dotnet restore` completes without errors
- [ ] `dotnet build` completes with **0 errors**
- [ ] No NuGet package version conflicts
- [ ] No unresolved assembly references
### 7.3 Runtime Validation Areas
Since there are no automated tests, the following areas should be manually verified after the upgrade:
| Area | What to Verify | Risk |
|---|---|---|
| **Application startup** | App starts, main window renders correctly | Low — WPF is fully supported |
| **Navigation** | All 4 pages (Football, Racing, Virtual Football, Settings) load | Low |
| **WebView2** | Virtual Football page loads WebView2 browser correctly | Low — WebView2 is compatible |
| **Database operations** | Football/Racing data download and storage work (SqlClient migration) | Medium — namespace change |
| **API calls** | FormFav and Football API calls succeed (RestSharp + JSON parsing) | Medium — behavioral changes |
| **CSV import/export** | CsvHelper operations work correctly | Low — compatible package |
| **Settings persistence** | Save/load settings works (legacy config bridge) | Low |
| **ML predictions** | ML.NET prediction pipeline runs without errors | Low — compatible packages |
### 7.4 Behavioral Change Validation
These specific scenarios require targeted testing due to behavioral changes in .NET 10.0:
1. **JsonDocument parsing** — Test API response parsing in `RacingApiClient.cs` and `Main.cs`:
- Verify JSON responses from FormFav API are parsed correctly
- Test edge cases: empty responses, malformed JSON, large payloads
2. **System.Uri** — Test URL construction in API clients:
- Verify API endpoint URLs are constructed correctly
- Test WebView2 navigation URLs
---
## 8. Risk Management
### 8.1 Risk Assessment
| Risk | Level | Description | Mitigation |
|---|---|---|---|
| SDK-style conversion fails | ?? Medium | Conversion tool may not handle all classic WPF project features | Manual .csproj cleanup after conversion; verify all files included |
| SqlClient migration breaks DB access | ?? Medium | Namespace-only change, but large surface area (1,264 references) | Global find-and-replace `System.Data.SqlClient` ? `Microsoft.Data.SqlClient`; API is compatible |
| ML.NET packages incompatible | ?? Low | ML packages are pre-release (5.0.0-preview) | Assessment confirms compatibility; fall back to stable ML.NET 4.x if needed |
| RestSharp pre-release issues | ?? Low | RestSharp is 112.1.1-alpha.0.4 | Assessment confirms compatibility; fall back to stable RestSharp if needed |
| Behavioral changes cause runtime issues | ?? Low | JsonDocument and Uri behavior differences | Configure strict/lenient options; manual testing of affected paths |
| Legacy config bridge insufficient | ?? Low | Settings.Designer.cs may need additional adjustments | System.Configuration.ConfigurationManager package provides full bridge |
| WPF rendering differences | ?? Low | Minor visual differences between .NET Framework and .NET 10.0 WPF | Visual inspection; typically identical rendering |
### 8.2 Contingency Plans
| Scenario | Action |
|---|---|
| SDK-style conversion produces broken .csproj | Manually create SDK-style .csproj with correct structure, copy settings |
| Microsoft.Data.SqlClient has API differences | Check Microsoft.Data.SqlClient migration guide for any breaking changes beyond namespace |
| ML.NET predictions fail | Pin ML.NET to last known working version for net10.0 |
| Build fails with unresolvable errors | Roll back to `main` branch, investigate specific errors |
---
## 9. Complexity & Effort Assessment
### 9.1 Overall Assessment
| Factor | Rating | Justification |
|---|---|---|
| **Overall complexity** | ?? Medium | Single project but large LOC impact (30.6%), major SqlClient migration |
| **SDK-style conversion** | Low | Automated tool handles conversion |
| **Framework update** | Low | Single TFM change |
| **Package updates** | Low | All 9 are simple version bumps (rc ? stable) |
| **Package removals** | Low | 6 straightforward removals |
| **SqlClient migration** | Medium | Large number of files (16+) but mechanical namespace change |
| **WPF compatibility** | Low | Resolved by recompilation |
| **Configuration bridge** | Low | Single package addition, 2 affected references |
| **Behavioral changes** | Low | 24 references, requires testing not code changes |
### 9.2 Complexity by File Area
| Area | Files Affected | Issue Count | Complexity |
|---|---|---|---|
| Football Database classes | 14 files | ~1,200+ | Medium (bulk SqlClient rename) |
| Manager Database classes | 2 files | 22 | Low |
| API Client / Main | 2 files | 7 | Low (behavioral testing) |
| Settings | 1 file | 2 | Low |
| Project file | 1 file | 17 | Low (tooling-assisted) |
| WPF UI files | ~8 files | 897 | Low (automatic via recompilation) |
---
## 10. Source Control Strategy
### 10.1 Branch Strategy
| Property | Value |
|---|---|
| **Source branch** | `main` |
| **Upgrade branch** | `upgrade-to-NET8` |
| **Approach** | Single commit for entire atomic upgrade |
### 10.2 Commit Strategy
**Single commit** for the entire All-At-Once upgrade operation:
- **Commit message**: `Upgrade BettingPredictor from .NET Framework 4.8.1 to .NET 10.0`
- **Contents**: SDK-style conversion + TFM change + all package updates + SqlClient migration + config bridge + compilation fixes
- **Rationale**: Single project, single atomic operation — one commit captures the complete upgrade
### 10.3 Review & Merge
- Create Pull Request from `upgrade-to-NET8` ? `main`
- PR checklist:
- [ ] Solution builds with 0 errors
- [ ] All package references correct (no pre-release, no framework-included)
- [ ] SqlClient migration complete (no remaining `System.Data.SqlClient` references)
- [ ] Application starts and core functionality works
- [ ] Manual verification of behavioral change areas
---
## 11. Success Criteria
### 11.1 Technical Criteria
- [ ] Project targets `net10.0-windows`
- [ ] Project uses SDK-style format
- [ ] All 9 packages updated from rc.1 to stable 10.0.5
- [ ] All 6 framework-included packages removed
- [ ] `Microsoft.Data.SqlClient` added and all `System.Data.SqlClient` references migrated
- [ ] `System.Configuration.ConfigurationManager` added for legacy config bridge
- [ ] Solution builds with **0 errors**
- [ ] No NuGet package dependency conflicts
- [ ] No security vulnerabilities
### 11.2 Quality Criteria
- [ ] No remaining references to `System.Data.SqlClient` namespace
- [ ] No remaining pre-release package versions (except ML.NET and RestSharp which are already pre-release by design)
- [ ] No framework-included packages remaining as explicit references
- [ ] Code quality maintained (no workarounds or hacks)
### 11.3 Process Criteria
- [ ] All-At-Once strategy followed (single atomic operation)
- [ ] All changes in `upgrade-to-NET8` branch
- [ ] Single commit capturing complete upgrade
- [ ] Assessment findings fully addressed
+120
View File
@@ -0,0 +1,120 @@
# Upgrade Tasks — BettingPredictor (.NET 10.0)
## Progress Dashboard
| Status | Count |
|---|---|
| ? Complete | 1 |
| ? In Progress | 0 |
| ? Not Started | 6 |
**Progress**: 7/7 tasks complete (100%) ![100%](https://progress-bar.xyz/100)
| ? Skipped | 0 |
| **Total** | **7** |
---
## Tasks
### [?] TASK-001: Verify Prerequisites *(Completed: 2026-03-31 21:10)*
**Scope**: Environment readiness
**References**: Plan §2.2
**Actions:**
- [?] (1) Verify .NET 10.0 SDK is installed on the machine (`dotnet --list-sdks`)
- [?] (2) Validate global.json compatibility ? if present, ensure it allows .NET 10.0 SDK; update or remove if needed
---
### [?] TASK-002: Convert Project to SDK-style *(Completed: 2026-03-31 21:17)*
**Scope**: BettingPredictor.csproj
**References**: Plan §4.1 Step 1
**Actions:**
- [?] (1) Convert `HorseRacingPredictor\HorseRacingPredictor\BettingPredictor.csproj` to SDK-style format using the SDK-style conversion tool
- [?] (2) Verify the converted .csproj uses `Microsoft.NET.Sdk` format and all source files are properly included
---
### [?] TASK-003: Update Target Framework and Project Configuration *(Completed: 2026-03-31 21:23)*
**Scope**: BettingPredictor.csproj
**References**: Plan §4.1 Step 2
**Actions:**
- [?] (1) Set `<TargetFramework>` to `net10.0-windows` in BettingPredictor.csproj
- [?] (2) Ensure `<UseWPF>true</UseWPF>` is present in the project file (required for WPF support on .NET 10.0)
- [?] (3) Verify project file structure is correct after TFM change
---
### [?] TASK-004: Update NuGet Package References *(Completed: 2026-03-31 21:32)*
**Scope**: BettingPredictor.csproj
**References**: Plan §5.1, §5.2, §5.3
**Actions:**
- [?] (1) Update 9 pre-release packages to stable 10.0.5:
Microsoft.Bcl.AsyncInterfaces, Microsoft.Bcl.Numerics, System.CodeDom,
System.Collections.Immutable, System.IO.Pipelines, System.Numerics.Tensors,
System.Text.Encodings.Web, System.Text.Json, System.Threading.Channels
- [?] (2) Remove 6 framework-included packages:
System.Buffers, System.Memory, System.Numerics.Vectors,
System.Reflection.Emit.Lightweight, System.Threading.Tasks.Extensions, System.ValueTuple
- [?] (3) Add `Microsoft.Data.SqlClient` package (latest stable version)
- [?] (4) Add `System.Configuration.ConfigurationManager` package (latest stable for net10.0)
- [?] (5) Run `dotnet restore` and verify all packages resolve without conflicts
---
### [?] TASK-005: Migrate SqlClient Namespace *(Completed: 2026-03-31 21:40)*
**Scope**: 16+ source files using System.Data.SqlClient
**References**: Plan §6.1, §4.1 Step 5
**Actions:**
- [?] (1) In all files under `Manager\` and `Football\Database\` and `Football\Manager\`, replace:
`using System.Data.SqlClient;` ? `using Microsoft.Data.SqlClient;`
Affected files (16):
- Manager\Database.cs
- Football\Manager\Database.cs
- Football\Database\APIResponse.cs
- Football\Database\LeagueStats.cs
- Football\Database\Fixture.cs
- Football\Database\Comparison.cs
- Football\Database\League.cs
- Football\Database\TeamStats.cs
- Football\Database\Prediction.cs
- Football\Database\Odds.cs
- Football\Database\Score.cs
- Football\Database\FixtureLeague.cs
- Football\Database\H2H.cs
- Football\Database\Goals.cs
- Football\Database\BetType.cs
- Football\Database\Bookmaker.cs
- [?] (2) Verify no remaining references to `System.Data.SqlClient` exist in the codebase
---
### [?] TASK-006: Build Solution and Fix Compilation Errors *(Completed: 2026-03-31 21:48)*
**Scope**: Entire solution
**References**: Plan §4.1 Step 7
**Actions:**
- [?] (1) Build the entire solution
- [?] (2) Fix any compilation errors discovered during build (expected areas: SqlClient namespace, removed APIs, configuration, WPF assembly references)
- [?] (3) Rebuild and verify: **0 compilation errors**
---
### [?] TASK-007: Final Verification and Commit *(Completed: 2026-03-31 21:55)*
**Scope**: Entire solution
**References**: Plan §10, §11
**Actions:**
- [?] (1) Verify all success criteria from Plan ?11:
- Project targets net10.0-windows
- Project uses SDK-style format
- All 9 packages updated to stable versions
- 6 framework-included packages removed
- Microsoft.Data.SqlClient added and usages migrated
- System.Configuration.ConfigurationManager added
- Solution builds with 0 errors
- [?] (2) Stage and commit all changes:
Message: `Upgrade BettingPredictor from .NET Framework 4.8.1 to .NET 10.0`
@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- System.Threading.Tasks.Extensions -->
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.4.0" newVersion="4.2.4.0" />
</dependentAssembly>
<!-- System.Memory -->
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
<!-- System.Runtime.CompilerServices.Unsafe -->
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
</dependentAssembly>
<!-- System.Buffers -->
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
<!-- System.Numerics.Vectors -->
<dependentAssembly>
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.6.0" newVersion="4.1.6.0" />
</dependentAssembly>
<!-- Microsoft.Bcl.AsyncInterfaces -->
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bcl.HashCode" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Numerics.Tensors" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Channels" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
@@ -1,219 +1,54 @@
<?xml version="1.0" encoding="utf-8"?> <Project Sdk="Microsoft.NET.Sdk">
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props" Condition="Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props')" />
<Import Project="..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props" Condition="Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <TargetFramework>net10.0-windows</TargetFramework>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{63138155-B7F3-4246-B47B-B8CC2D7A60A4}</ProjectGuid>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<RootNamespace>HorseRacingPredictor</RootNamespace> <RootNamespace>HorseRacingPredictor</RootNamespace>
<AssemblyName>HorseRacingPredictor</AssemblyName> <AssemblyName>HorseRacingPredictor</AssemblyName>
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<FileAlignment>512</FileAlignment> <UseWindowsForms>true</UseWindowsForms>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <UseWPF>true</UseWPF>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath> <OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath> <OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="CsvHelper, Version=33.0.0.0, Culture=neutral, PublicKeyToken=8c4a6d608ce8f59c, processorArchitecture=MSIL"> <PackageReference Include="CsvHelper" Version="33.1.0" />
<HintPath>..\packages\CsvHelper.33.1.0\lib\net48\CsvHelper.dll</HintPath> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="11.0.0-preview.2.26159.112" />
</Reference> <PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<Reference Include="Microsoft.Web.WebView2.Core, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL"> <PackageReference Include="Microsoft.Bcl.Numerics" Version="11.0.0-preview.2.26159.112" />
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.Core.dll</HintPath> <PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0" />
</Reference> <PackageReference Include="Microsoft.ML" Version="6.0.0-preview.26160.2" />
<Reference Include="Microsoft.Web.WebView2.Wpf, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL"> <PackageReference Include="Microsoft.ML.CpuMath" Version="6.0.0-preview.26160.2" />
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.Wpf.dll</HintPath> <PackageReference Include="Microsoft.ML.DataView" Version="6.0.0-preview.26160.2" />
</Reference> <PackageReference Include="Microsoft.ML.FastTree" Version="6.0.0-preview.26160.2" />
<Reference Include="Microsoft.Web.WebView2.WinForms, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL"> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3908-prerelease" />
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.WinForms.dll</HintPath> <PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" />
</Reference> <PackageReference Include="RestSharp" Version="114.0.0" />
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath> <PackageReference Include="System.Numerics.Tensors" Version="11.0.0-preview.2.26159.112" />
</Reference>
<Reference Include="RestSharp, Version=112.1.1.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.112.1.1-alpha.0.4\lib\net48\RestSharp.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="WindowsBase" />
<Reference Include="System.Xaml" />
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.CodeDom, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.CodeDom.10.0.0-rc.1.25451.107\lib\net462\System.CodeDom.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.10.0.0-rc.1.25451.107\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.IO.Pipelines, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Pipelines.10.0.0-rc.1.25451.107\lib\net462\System.IO.Pipelines.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Tensors, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Tensors.10.0.0-rc.1.25451.107\lib\net462\System.Numerics.Tensors.dll</HintPath>
</Reference>
<Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encodings.Web, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encodings.Web.10.0.0-rc.1.25451.107\lib\net462\System.Text.Encodings.Web.dll</HintPath>
</Reference>
<Reference Include="System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Json.10.0.0-rc.1.25451.107\lib\net462\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Channels, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Channels.10.0.0-rc.1.25451.107\lib\net462\System.Threading.Channels.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup Label="Compile items now included by globbing that were not in the original project file">
<ApplicationDefinition Include="App.xaml"> <Compile Remove="UI\NavButton.cs" />
<Generator>MSBuild:Compile</Generator> <Compile Remove="UI\ModernTheme.cs" />
<SubType>Designer</SubType> <Compile Remove="UI\ModernProgressBar.cs" />
</ApplicationDefinition> <Compile Remove="UI\ModernButton.cs" />
<Compile Include="VirtualFootball\VirtualMatch.cs" /> <Compile Remove="UI\Controls\ModernTextBox.cs" />
<Page Include="MainWindow.xaml"> <Compile Remove="UI\Controls\ModernTabControl.cs" />
<Generator>MSBuild:Compile</Generator> <Compile Remove="UI\Controls\ModernProgressBar.cs" />
<SubType>Designer</SubType> <Compile Remove="UI\Controls\ModernPanel.cs" />
</Page> <Compile Remove="UI\Controls\ModernLabel.cs" />
<Compile Include="App.xaml.cs"> <Compile Remove="UI\Controls\ModernDateTimePicker.cs" />
<DependentUpon>App.xaml</DependentUpon> <Compile Remove="UI\Controls\ModernDataGridView.cs" />
</Compile> <Compile Remove="UI\Controls\ModernButton.cs" />
<Compile Include="CsvHelperStubs.cs" /> <Compile Remove="UI\CardPanel.cs" />
<Compile Include="MainWindow.xaml.cs"> <Compile Remove="Program.cs" />
<DependentUpon>MainWindow.xaml</DependentUpon> <Compile Remove="Main.Designer.cs" />
</Compile> <Compile Remove="Main.cs" />
<Compile Include="Football\Database\BetType.cs" />
<Compile Include="Football\Database\Bookmaker.cs" />
<Compile Include="Football\Database\APIResponse.cs" />
<Compile Include="Football\Database\TeamStats.cs" />
<Compile Include="Football\Database\LeagueStats.cs" />
<Compile Include="Football\Database\H2H.cs" />
<Compile Include="Football\Database\Comparison.cs" />
<Compile Include="Football\Database\FixtureLeague.cs" />
<Compile Include="Football\Main.cs" />
<Compile Include="Football\Manager\API.cs" />
<Compile Include="Football\API\Fixture.cs" />
<Compile Include="Football\API\League.cs" />
<Compile Include="Football\API\Odds.cs" />
<Compile Include="Football\API\Prediction.cs" />
<Compile Include="Football\Database\Fixture.cs" />
<Compile Include="Football\Database\Goals.cs" />
<Compile Include="Football\Database\League.cs" />
<Compile Include="Football\Database\Odds.cs" />
<Compile Include="Football\Database\Prediction.cs" />
<Compile Include="Football\Database\Score.cs" />
<Compile Include="Football\Database\Team.cs" />
<Compile Include="HorseRacing\API\RacingApiClient.cs" />
<Compile Include="HorseRacing\Main.cs" />
<Compile Include="Horses\Calculator.cs" />
<Compile Include="Horses\Files\Maps\Punters.cs" />
<Compile Include="Manager\API.cs" />
<Compile Include="Manager\FileReader.cs" />
<Compile Include="Manager\Database.cs" />
<Compile Include="Football\Manager\Database.cs" />
<Compile Include="Horses\FileReader.cs" />
<Compile Include="Horses\Database.cs" />
<Compile Include="Horses\Files\Punters.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup Label="EmbeddedResource items now included by globbing that were not in the original project file">
<None Include="App.config" /> <EmbeddedResource Remove="Main.resx" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Themes\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>Questo progetto fa riferimento a uno o pi pacchetti NuGet che non sono presenti in questo computer. Usare lo strumento di ripristino dei pacchetti NuGet per scaricarli. Per altre informazioni, vedere http://go.microsoft.com/fwlink/?LinkID=322105. Il file mancante {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props'))" />
</Target>
<Import Project="..\packages\Microsoft.Web.WebView2.1.0.3800.47\build\Microsoft.Web.WebView2.targets" Condition="Exists('..\packages\Microsoft.Web.WebView2.1.0.3800.47\build\Microsoft.Web.WebView2.targets')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets')" />
</Project> </Project>
@@ -1,115 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Globalization;
// Minimal compatibility stubs for CsvHelper types used in the project.
// These are light-weight placeholders so the project can compile when the CsvHelper NuGet
// package is not restored. They intentionally provide only the members the project expects.
namespace CsvHelper.Configuration.Attributes
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class NameAttribute : Attribute
{
public string[] Names { get; }
public NameAttribute(params string[] names) => Names = names ?? Array.Empty<string>();
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class TypeConverterAttribute : Attribute
{
public Type ConverterType { get; }
public TypeConverterAttribute(Type converterType) => ConverterType = converterType;
}
}
namespace CsvHelper.TypeConversion
{
public class DefaultTypeConverter
{
// Signature matches CsvHelper's DefaultTypeConverter override used in the project
public virtual object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) => (object)text;
}
public class TypeConverterCache
{
private readonly Dictionary<Type, object> _converters = new Dictionary<Type, object>();
public void AddConverter<T>(DefaultTypeConverter converter) => _converters[typeof(T)] = converter;
}
// Minimal placeholders referenced in code
public interface IReaderRow { }
public class MemberMapData { }
}
namespace CsvHelper.Configuration
{
public enum TrimOptions { None, Trim }
public class CsvConfiguration
{
public CsvConfiguration(CultureInfo culture) { Culture = culture; }
public CultureInfo Culture { get; }
public bool HasHeaderRecord { get; set; }
public Func<PrepareHeaderForMatchArgs, string> PrepareHeaderForMatch { get; set; }
public object HeaderValidated { get; set; }
public Action<object> MissingFieldFound { get; set; }
public Action<object> BadDataFound { get; set; }
public TrimOptions TrimOptions { get; set; }
public string Delimiter { get; set; }
}
// Basic ClassMap and mapping helper used by the project's mapping files
public class ClassMap<T>
{
public MemberMap Map(Func<T, object> func) => new MemberMap();
}
public class MemberMap
{
public MemberMap Name(params string[] names) { return this; }
}
// Minimal args used by PrepareHeaderForMatch delegate in code above
public class PrepareHeaderForMatchArgs
{
public string Header { get; set; }
}
}
namespace CsvHelper
{
using CsvHelper.Configuration;
using CsvHelper.TypeConversion;
public class CsvReader : IDisposable
{
private readonly TextReader _reader;
public CsvReader(TextReader reader, CsvConfiguration config)
{
_reader = reader;
Configuration = config;
Context = new ReaderContext();
}
public CsvConfiguration Configuration { get; }
public ReaderContext Context { get; }
public void Dispose() { /* nothing to dispose in stub */ }
public bool Read() => false;
public void ReadHeader() { }
public string[] HeaderRecord => Array.Empty<string>();
public string GetField(int index) => null;
public IEnumerable<T> GetRecords<T>() { return Enumerable.Empty<T>(); }
public class ReaderContext
{
public TypeConverterCache TypeConverterCache { get; } = new TypeConverterCache();
public void RegisterClassMap<TMap>() { }
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using System.Text; using System.Text;
using RestSharp; using RestSharp;
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -11,7 +11,7 @@ namespace HorseRacingPredictor.Football.Database
try try
{ {
var id = betType["id"]?.Value<int>(); var id = betType["id"]?.Value<int>();
if (id == null) return; // Salta il record se l'id è null if (id == null) return; // Salta il record se l'id è null
var query = @" var query = @"
IF EXISTS (SELECT 1 FROM BetType WHERE bet_type_id = @bet_type_id) IF EXISTS (SELECT 1 FROM BetType WHERE bet_type_id = @bet_type_id)
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -11,7 +11,7 @@ namespace HorseRacingPredictor.Football.Database
try try
{ {
var id = bookmaker["id"]?.Value<int>(); var id = bookmaker["id"]?.Value<int>();
if (id == null) return; // Salta il record se l'id è null if (id == null) return; // Salta il record se l'id è null
var query = @" var query = @"
IF EXISTS (SELECT 1 FROM Bookmaker WHERE bookmaker_id = @bookmaker_id) IF EXISTS (SELECT 1 FROM Bookmaker WHERE bookmaker_id = @bookmaker_id)
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -1,6 +1,6 @@
using System; using System;
using System.Data; using System.Data;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -143,7 +143,7 @@ namespace HorseRacingPredictor.Football.Database
try try
{ {
using (var connection = new System.Data.SqlClient.SqlConnection(_connectionString)) using (var connection = new Microsoft.Data.SqlClient.SqlConnection(_connectionString))
{ {
connection.Open(); connection.Open();
@@ -186,7 +186,7 @@ namespace HorseRacingPredictor.Football.Database
ORDER BY ORDER BY
f.date ASC"; f.date ASC";
using (var adapter = new System.Data.SqlClient.SqlDataAdapter(query, connection)) using (var adapter = new Microsoft.Data.SqlClient.SqlDataAdapter(query, connection))
{ {
adapter.Fill(result); adapter.Fill(result);
} }
@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database namespace HorseRacingPredictor.Football.Database
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
@@ -8,7 +8,7 @@ using RestSharp;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
namespace HorseRacingPredictor.Football namespace HorseRacingPredictor.Football
{ {
@@ -276,7 +276,7 @@ namespace HorseRacingPredictor.Football
} }
/// <summary> /// <summary>
/// Recupera le quote per la data specificata (potenzialmente più pagine) utilizzando API.Odds /// Recupera le quote per la data specificata (potenzialmente più pagine) utilizzando API.Odds
/// </summary> /// </summary>
private List<RestResponse> GetOdds(DateTime date) private List<RestResponse> GetOdds(DateTime date)
{ {
@@ -392,7 +392,7 @@ namespace HorseRacingPredictor.Football
} }
} }
// FASE 5: Inserisci relazioni tra entità e dati dipendenti // FASE 5: Inserisci relazioni tra entità e dati dipendenti
foreach (var responseItem in asArray(jsonObject["response"])) foreach (var responseItem in asArray(jsonObject["response"]))
{ {
int? fixtureId = null; int? fixtureId = null;
@@ -572,8 +572,8 @@ namespace HorseRacingPredictor.Football
{ {
try try
{ {
// In questo metodo non elaboriamo più direttamente le risposte // In questo metodo non elaboriamo più direttamente le risposte
// Le risposte sono già state salvate nel database dalla classe API // Le risposte sono già state salvate nel database dalla classe API
// e verranno elaborate dal metodo ProcessUnprocessedApiResponses // e verranno elaborate dal metodo ProcessUnprocessedApiResponses
// Processa le risposte non elaborate // Processa le risposte non elaborate
@@ -608,7 +608,7 @@ namespace HorseRacingPredictor.Football
dataTable.Columns.Add("Quota Trasferta", typeof(string)); dataTable.Columns.Add("Quota Trasferta", typeof(string));
dataTable.Columns.Add("Over 2.5", typeof(string)); dataTable.Columns.Add("Over 2.5", typeof(string));
dataTable.Columns.Add("Under 2.5", typeof(string)); dataTable.Columns.Add("Under 2.5", typeof(string));
dataTable.Columns.Add("BTTS Sì", typeof(string)); dataTable.Columns.Add("BTTS Sì", typeof(string));
dataTable.Columns.Add("BTTS No", typeof(string)); dataTable.Columns.Add("BTTS No", typeof(string));
dataTable.Columns.Add("Doppia Casa/X", typeof(string)); dataTable.Columns.Add("Doppia Casa/X", typeof(string));
dataTable.Columns.Add("Doppia Casa/Trasf", typeof(string)); dataTable.Columns.Add("Doppia Casa/Trasf", typeof(string));
@@ -646,7 +646,7 @@ namespace HorseRacingPredictor.Football
{ {
try try
{ {
// Verifica che le proprietà essenziali esistano // Verifica che le proprietà essenziali esistano
if (!item.TryGetProperty("fixture", out var fixtureEl) || if (!item.TryGetProperty("fixture", out var fixtureEl) ||
!item.TryGetProperty("league", out var leagueEl) || !item.TryGetProperty("league", out var leagueEl) ||
!item.TryGetProperty("teams", out var teamsEl)) !item.TryGetProperty("teams", out var teamsEl))
@@ -700,7 +700,7 @@ namespace HorseRacingPredictor.Football
row["Quota Trasferta"] = DBNull.Value; row["Quota Trasferta"] = DBNull.Value;
row["Over 2.5"] = DBNull.Value; row["Over 2.5"] = DBNull.Value;
row["Under 2.5"] = DBNull.Value; row["Under 2.5"] = DBNull.Value;
row["BTTS Sì"] = DBNull.Value; row["BTTS Sì"] = DBNull.Value;
row["BTTS No"] = DBNull.Value; row["BTTS No"] = DBNull.Value;
row["Doppia Casa/X"] = DBNull.Value; row["Doppia Casa/X"] = DBNull.Value;
row["Doppia Casa/Trasf"] = DBNull.Value; row["Doppia Casa/Trasf"] = DBNull.Value;
@@ -731,7 +731,7 @@ namespace HorseRacingPredictor.Football
// Crea una copia del DataTable delle partite // Crea una copia del DataTable delle partite
var combinedTable = fixturesTable.Copy(); var combinedTable = fixturesTable.Copy();
// Se non ci sono risposte di quote o la tabella delle partite è vuota, ritorna la tabella originale // Se non ci sono risposte di quote o la tabella delle partite è vuota, ritorna la tabella originale
if (oddsResponses == null || oddsResponses.Count == 0 || combinedTable.Rows.Count == 0) if (oddsResponses == null || oddsResponses.Count == 0 || combinedTable.Rows.Count == 0)
{ {
return combinedTable; return combinedTable;
@@ -868,7 +868,7 @@ namespace HorseRacingPredictor.Football
var table = new DataTable(); var table = new DataTable();
table.Columns.Add("Errore", typeof(string)); table.Columns.Add("Errore", typeof(string));
var row = table.NewRow(); var row = table.NewRow();
row["Errore"] = "Si è verificato un errore durante il recupero dei dati."; row["Errore"] = "Si è verificato un errore durante il recupero dei dati.";
table.Rows.Add(row); table.Rows.Add(row);
return table; return table;
} }
@@ -972,7 +972,7 @@ namespace HorseRacingPredictor.Football
{ {
string val = GetOddValueString(v, "value"); string val = GetOddValueString(v, "value");
string odd = GetOddValueString(v, "odd"); string odd = GetOddValueString(v, "odd");
if (val == "Yes") row["BTTS Sì"] = odd; if (val == "Yes") row["BTTS Sì"] = odd;
else if (val == "No") row["BTTS No"] = odd; else if (val == "No") row["BTTS No"] = odd;
} }
break; break;
@@ -1079,7 +1079,7 @@ namespace HorseRacingPredictor.Football
var result = CreateEmptyFixturesDataTable(); var result = CreateEmptyFixturesDataTable();
// Step 2: Delegare alla classe repository appropriata il recupero dei dati // Step 2: Delegare alla classe repository appropriata il recupero dei dati
// Utilizziamo principalmente il repository Fixture che può coordinarsi con gli altri repository // Utilizziamo principalmente il repository Fixture che può coordinarsi con gli altri repository
return _fixtureRepository.GetProcessedFixtures(); return _fixtureRepository.GetProcessedFixtures();
} }
catch (Exception ex) catch (Exception ex)
@@ -9,12 +9,12 @@ namespace HorseRacingPredictor.Football.Manager
{ {
internal class API : HorseRacingPredictor.Manager.API internal class API : HorseRacingPredictor.Manager.API
{ {
// Configurazione dell'API // Configurazione dell'API caricata da appsettings.json
protected const string ApiKey = "f3795ccef056c5478d316162517d9970"; protected static string ApiKey => AppConfig.FootballApiKey;
protected const string KeyHeader = "x-rapidapi-key"; protected static string KeyHeader => AppConfig.FootballApiKeyHeader;
protected const string HostHeader = "x-rapidapi-host"; protected const string HostHeader = "x-rapidapi-host";
protected const string HostValue = "v3.football.api-sports.io"; protected static string HostValue => AppConfig.FootballApiHost;
protected const string BaseUrl = "https://v3.football.api-sports.io"; protected static string BaseUrl => $"https://{AppConfig.FootballApiHost}";
// Repository per le risposte API // Repository per le risposte API
private readonly APIResponse _apiResponseRepository; private readonly APIResponse _apiResponseRepository;
@@ -1,5 +1,5 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using HorseRacingPredictor.Football.Database; using HorseRacingPredictor.Football.Database;
@@ -7,8 +7,7 @@ namespace HorseRacingPredictor.Football.Manager
{ {
internal class Database : HorseRacingPredictor.Manager.Database internal class Database : HorseRacingPredictor.Manager.Database
{ {
// Implementazione della proprietà astratta _connectionString per il database calcio public Database() : base(AppConfig.FootballConnectionString) { }
protected override string _connectionString => "Server=DESKTOP-9O9JHFS;Database=TestBS_Football;User Id=sa;Password=Asti2019;";
// Usato il modificatore "new" per evitare il warning CS0108 // Usato il modificatore "new" per evitare il warning CS0108
protected new void LogError(string operation, Exception ex) protected new void LogError(string operation, Exception ex)
@@ -28,11 +27,11 @@ namespace HorseRacingPredictor.Football.Manager
base.ExecuteTransactionalQuery(operation, action); base.ExecuteTransactionalQuery(operation, action);
} }
// Questo metodo ora è vuoto e sarà implementato nella Main // Questo metodo ora è vuoto e sarà implementato nella Main
public void ProcessAndInsertData(string jsonResponse) public void ProcessAndInsertData(string jsonResponse)
{ {
// Il codice è stato spostato in Football.Main // Il codice è stato spostato in Football.Main
LogError("l'elaborazione dei dati calcistici", new NotImplementedException("Questo metodo è stato spostato in Football.Main")); LogError("l'elaborazione dei dati calcistici", new NotImplementedException("Questo metodo è stato spostato in Football.Main"));
} }
/// <summary> /// <summary>
@@ -31,7 +31,9 @@ namespace HorseRacingPredictor.HorseRacing.API
/// Esegue una richiesta GET autenticata con X-API-Key header. /// Esegue una richiesta GET autenticata con X-API-Key header.
/// Rispetta un intervallo minimo tra richieste e gestisce HTTP 429 con retry. /// Rispetta un intervallo minimo tra richieste e gestisce HTTP 429 con retry.
/// </summary> /// </summary>
private RestResponse ExecuteRequest(string endpoint, CancellationToken ct = default) /// <param name="throwOnNotFound">Se false, restituisce null in caso di 404.</param>
private RestResponse ExecuteRequest(string endpoint, CancellationToken ct = default,
bool throwOnNotFound = true)
{ {
ct.ThrowIfCancellationRequested(); ct.ThrowIfCancellationRequested();
@@ -67,7 +69,7 @@ namespace HorseRacingPredictor.HorseRacing.API
{ {
var response = client.Execute(request); var response = client.Execute(request);
// HTTP 429 Too Many Requests backoff e riprova // HTTP 429 Too Many Requests - backoff e riprova
if (response.StatusCode == (HttpStatusCode)429 && attempt <= MaxRetries) if (response.StatusCode == (HttpStatusCode)429 && attempt <= MaxRetries)
{ {
System.Diagnostics.Debug.WriteLine( System.Diagnostics.Debug.WriteLine(
@@ -81,6 +83,10 @@ namespace HorseRacingPredictor.HorseRacing.API
continue; continue;
} }
// 404 Not Found - restituisci null se richiesto
if (response.StatusCode == HttpStatusCode.NotFound && !throwOnNotFound)
return null;
if (!response.IsSuccessful) if (!response.IsSuccessful)
{ {
throw new Exception( throw new Exception(
@@ -98,10 +104,10 @@ namespace HorseRacingPredictor.HorseRacing.API
} }
/// <summary> /// <summary>
/// Ottiene l'elenco dei meeting per una data /// Ottiene l'elenco dei meeting per una data (solo meeting australiani).
/// </summary> /// </summary>
public RestResponse GetMeetings(DateTime date, string raceCode = "gallops", public RestResponse GetMeetings(DateTime date, string raceCode = "gallops",
string timezone = "Europe/Rome", CancellationToken ct = default) string timezone = "Australia/Sydney", CancellationToken ct = default)
{ {
var sb = new StringBuilder("form/meetings?"); var sb = new StringBuilder("form/meetings?");
sb.Append($"date={date:yyyy-MM-dd}"); sb.Append($"date={date:yyyy-MM-dd}");
@@ -114,11 +120,38 @@ namespace HorseRacingPredictor.HorseRacing.API
} }
/// <summary> /// <summary>
/// Ottiene i dati di forma per una singola corsa /// Ottiene i dati di forma per una singola corsa.
/// Lancia eccezione se la corsa non esiste.
/// </summary> /// </summary>
public RestResponse GetRaceForm(DateTime date, string track, int raceNumber, public RestResponse GetRaceForm(DateTime date, string track, int raceNumber,
string raceCode = "gallops", string country = "au", string raceCode = "gallops", string country = "au",
string timezone = "Europe/Rome", CancellationToken ct = default) string timezone = "Australia/Sydney", CancellationToken ct = default)
{
string endpoint = BuildFormEndpoint(date, track, raceNumber, raceCode, country, timezone);
return ExecuteRequest(endpoint, ct);
}
/// <summary>
/// Prova a ottenere i dati di forma per una corsa. Restituisce null se 404.
/// </summary>
public RestResponse TryGetRaceForm(DateTime date, string track, int raceNumber,
string raceCode = "gallops", string country = "au",
string timezone = "Australia/Sydney", CancellationToken ct = default)
{
string endpoint = BuildFormEndpoint(date, track, raceNumber, raceCode, country, timezone);
return ExecuteRequest(endpoint, ct, throwOnNotFound: false);
}
/// <summary>
/// Ottiene l'elenco delle piste/venue disponibili.
/// </summary>
public RestResponse GetVenues(CancellationToken ct = default)
{
return ExecuteRequest("form/venues", ct);
}
private static string BuildFormEndpoint(DateTime date, string track, int raceNumber,
string raceCode, string country, string timezone)
{ {
var sb = new StringBuilder("form?"); var sb = new StringBuilder("form?");
sb.Append($"date={date:yyyy-MM-dd}"); sb.Append($"date={date:yyyy-MM-dd}");
@@ -130,16 +163,7 @@ namespace HorseRacingPredictor.HorseRacing.API
sb.Append($"&country={country}"); sb.Append($"&country={country}");
if (!string.IsNullOrEmpty(timezone)) if (!string.IsNullOrEmpty(timezone))
sb.Append($"&timezone={Uri.EscapeDataString(timezone)}"); sb.Append($"&timezone={Uri.EscapeDataString(timezone)}");
return sb.ToString();
return ExecuteRequest(sb.ToString(), ct);
}
/// <summary>
/// Ottiene l'elenco delle piste/venue disponibili
/// </summary>
public RestResponse GetVenues(CancellationToken ct = default)
{
return ExecuteRequest("form/venues", ct);
} }
} }
} }
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using HorseRacingPredictor.HorseRacing.API; using HorseRacingPredictor.HorseRacing.API;
@@ -10,30 +11,66 @@ namespace HorseRacingPredictor.HorseRacing
/// <summary> /// <summary>
/// Gestore centralizzato per la sezione Corse dei Cavalli. /// Gestore centralizzato per la sezione Corse dei Cavalli.
/// Scarica i dati da FormFav Racing API e li converte in DataTable. /// Scarica i dati da FormFav Racing API e li converte in DataTable.
///
/// NOTA: l'API FormFav supporta dati di forma SOLO per AU e NZ.
/// - AU: discovery efficiente tramite /form/meetings
/// - NZ: discovery tramite probing delle venue (meetings restituisce solo AU)
/// Le altre nazioni (gb, ie, fr, ...) sono presenti nel catalogo venue
/// ma non hanno dati di forma disponibili.
/// </summary> /// </summary>
public class Main public class Main
{ {
/// <summary>Nazioni con dati di forma disponibili nell'API.</summary>
public static readonly string[] SupportedCountries = { "au", "nz" };
/// <summary>Tutte le nazioni nel catalogo venue (solo AU e NZ hanno form data).</summary>
public static readonly string[] AllCountries = {
"au","nz","hk","gb","ie","fr","us","ca","jp","sg",
"ae","sa","za","de","it","se","no","dk","kr","my",
"ar","br","cl","ma"
};
/// <summary>Nomi leggibili per ogni codice nazione.</summary>
public static readonly Dictionary<string, string> CountryNames = new Dictionary<string, string>
{
{"au","Australia"},{"nz","Nuova Zelanda"},{"hk","Hong Kong"},
{"gb","Gran Bretagna"},{"ie","Irlanda"},{"fr","Francia"},
{"us","Stati Uniti"},{"ca","Canada"},{"jp","Giappone"},
{"sg","Singapore"},{"ae","Emirati Arabi"},{"sa","Arabia Saudita"},
{"za","Sudafrica"},{"de","Germania"},{"it","Italia"},
{"se","Svezia"},{"no","Norvegia"},{"dk","Danimarca"},
{"kr","Corea del Sud"},{"my","Malesia"},{"ar","Argentina"},
{"br","Brasile"},{"cl","Cile"},{"ma","Marocco"}
};
private const int MaxRacesPerVenue = 12;
private RacingApiClient _client; private RacingApiClient _client;
private List<VenueInfo> _venuesCache;
/// <summary>Nazioni da scaricare (default: au, nz). Solo au e nz sono supportate.</summary>
public List<string> Countries { get; set; } = new List<string> { "au", "nz" };
/// <summary>Timezone IANA (default: Australia/Sydney).</summary>
public string Timezone { get; set; } = "Australia/Sydney";
public Main(string apiKey) public Main(string apiKey)
{ {
_client = new RacingApiClient(apiKey); _client = new RacingApiClient(apiKey);
} }
/// <summary>
/// Aggiorna la API key
/// </summary>
public void UpdateApiKey(string apiKey) public void UpdateApiKey(string apiKey)
{ {
_client = new RacingApiClient(apiKey); _client = new RacingApiClient(apiKey);
_venuesCache = null;
} }
/// <summary> /// <summary>
/// Scarica tutti i meeting per una data, poi per ciascun meeting scarica tutte le corse /// Scarica tutte le corse per una data.
/// e le restituisce come DataTable con una riga per runner. /// - Per AU: usa /form/meetings (efficiente, restituisce numero corse)
/// La progress bar avanza per singola corsa scaricata. /// - Per NZ: usa probing venue per venue
/// </summary> /// </summary>
public DataTable GetAllRacesForDate(DateTime date, string raceCode = "gallops", public DataTable GetAllRacesForDate(DateTime date,
IProgress<int> progress = null, IProgress<string> status = null, IProgress<int> progress = null, IProgress<string> status = null,
CancellationToken ct = default) CancellationToken ct = default)
{ {
@@ -41,75 +78,226 @@ namespace HorseRacingPredictor.HorseRacing
try try
{ {
status?.Report("Connessione a FormFav API..."); // Filtra solo nazioni supportate
progress?.Report(2); var requestedCountries = Countries
.Select(c => c.ToLowerInvariant())
.Where(c => SupportedCountries.Contains(c))
.Distinct()
.ToList();
// 1. Ottieni l'elenco dei meeting per la data if (requestedCountries.Count == 0)
var meetingsResp = _client.GetMeetings(date, raceCode, ct: ct);
progress?.Report(8);
var meetings = ParseMeetings(meetingsResp.Content);
if (meetings.Count == 0)
{ {
status?.Report("Nessun meeting trovato per questa data"); status?.Report("Nessuna nazione supportata selezionata. Usa: AU, NZ");
progress?.Report(100); progress?.Report(100);
return dt; return dt;
} }
// 2. Calcola il totale corse per un progresso granulare bool doAu = requestedCountries.Contains("au");
int totalRaces = 0; bool doNz = requestedCountries.Contains("nz");
foreach (var m in meetings)
if (!m.Abandoned) totalRaces += m.NumberOfRaces;
if (totalRaces == 0) int totalPhases = (doAu ? 1 : 0) + (doNz ? 1 : 0);
int currentPhase = 0;
int totalRunners = 0;
int totalErrors = 0;
int totalMeetings = 0;
// ?? FASE AU: usa /form/meetings ??
if (doAu)
{ {
status?.Report("Tutti i meeting sono stati annullati"); status?.Report("AU: Recupero elenco meeting...");
progress?.Report(100); progress?.Report(2);
return dt;
}
status?.Report($"Trovati {meetings.Count} meeting ({totalRaces} corse). Scaricamento..."); int phaseBase = 0;
int completedRaces = 0; int phaseSpan = doNz ? 50 : 95; // Se c'e' anche NZ, AU occupa 0-50%
int errors = 0;
// 3. Scarica ogni singola corsa try
foreach (var meeting in meetings)
{
ct.ThrowIfCancellationRequested();
if (meeting.Abandoned) continue;
for (int raceNum = 1; raceNum <= meeting.NumberOfRaces; raceNum++)
{ {
ct.ThrowIfCancellationRequested(); var meetingsResp = _client.GetMeetings(date, "gallops", Timezone, ct);
var meetings = ParseMeetings(meetingsResp.Content);
try if (meetings.Count > 0)
{ {
status?.Report($"{meeting.Track} R{raceNum}/{meeting.NumberOfRaces} " + // Calcola totale corse AU
$"({completedRaces + 1}/{totalRaces})"); int totalAuRaces = 0;
foreach (var m in meetings)
if (!m.Abandoned) totalAuRaces += m.NumberOfRaces;
var formResp = _client.GetRaceForm(date, meeting.TrackSlug, raceNum, status?.Report($"AU: {meetings.Count} meeting ({totalAuRaces} corse)");
raceCode, meeting.Country, ct: ct);
ParseRaceFormIntoTable(dt, formResp.Content); int completedAuRaces = 0;
foreach (var meeting in meetings)
{
ct.ThrowIfCancellationRequested();
if (meeting.Abandoned) continue;
totalMeetings++;
for (int raceNum = 1; raceNum <= meeting.NumberOfRaces; raceNum++)
{
ct.ThrowIfCancellationRequested();
status?.Report($"AU: {meeting.Track} R{raceNum}/{meeting.NumberOfRaces} " +
$"({completedAuRaces + 1}/{totalAuRaces})");
try
{
var formResp = _client.GetRaceForm(date, meeting.TrackSlug,
raceNum, "gallops", "au", Timezone, ct);
ParseRaceFormIntoTable(dt, formResp.Content, "au");
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
totalErrors++;
System.Diagnostics.Debug.WriteLine(
$"Errore AU {meeting.Track} R{raceNum}: {ex.Message}");
}
completedAuRaces++;
int pct = phaseBase + (int)((double)completedAuRaces / Math.Max(totalAuRaces, 1) * phaseSpan);
progress?.Report(Math.Min(pct, phaseBase + phaseSpan));
}
}
} }
catch (OperationCanceledException) { throw; } else
catch (Exception ex)
{ {
errors++; status?.Report("AU: Nessun meeting trovato");
System.Diagnostics.Debug.WriteLine(
$"Errore scaricamento {meeting.Track} R{raceNum}: {ex.Message}");
} }
completedRaces++;
// Progresso: 8% per meetings, 8-98% per le corse singole, 100% alla fine
int pct = 8 + (int)((double)completedRaces / totalRaces * 90);
progress?.Report(Math.Min(pct, 98));
} }
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
totalErrors++;
System.Diagnostics.Debug.WriteLine($"Errore fase AU meetings: {ex.Message}");
}
currentPhase++;
}
// ?? FASE NZ: probing venue per venue ??
if (doNz)
{
int phaseBase = doAu ? 50 : 0;
int phaseSpan = doAu ? 48 : 95;
status?.Report("NZ: Caricamento elenco piste...");
progress?.Report(phaseBase + 2);
try
{
var nzVenues = GetFilteredVenues("nz", ct);
if (nzVenues.Count > 0)
{
// Discovery: prova Race 1 per ogni venue
int venuesChecked = 0;
var activeVenues = new List<ActiveVenue>();
int discoverySpan = phaseSpan / 3;
foreach (var v in nzVenues)
{
ct.ThrowIfCancellationRequested();
status?.Report($"NZ: Verifica {v.Name}... [{venuesChecked + 1}/{nzVenues.Count}]");
try
{
var resp = _client.TryGetRaceForm(date, v.Slug, 1,
"gallops", "nz", Timezone, ct);
if (resp != null && !string.IsNullOrEmpty(resp.Content))
{
activeVenues.Add(new ActiveVenue
{
Name = v.Name,
Slug = v.Slug,
Country = "nz",
FirstRaceContent = resp.Content
});
}
}
catch (OperationCanceledException) { throw; }
catch { }
venuesChecked++;
int pct = phaseBase + (int)((double)venuesChecked / nzVenues.Count * discoverySpan);
progress?.Report(Math.Min(pct, phaseBase + discoverySpan));
}
// Download rimanenti corse per venue attive
if (activeVenues.Count > 0)
{
int downloadBase = phaseBase + discoverySpan;
int downloadSpan = phaseSpan - discoverySpan;
int completedNzRaces = 0;
int estimatedNzRaces = activeVenues.Count * 8;
status?.Report($"NZ: {activeVenues.Count} meeting attivi");
foreach (var av in activeVenues)
{
ct.ThrowIfCancellationRequested();
totalMeetings++;
// Parsifica Race 1 (gia' scaricata)
ParseRaceFormIntoTable(dt, av.FirstRaceContent, "nz");
completedNzRaces++;
for (int raceNum = 2; raceNum <= MaxRacesPerVenue; raceNum++)
{
ct.ThrowIfCancellationRequested();
status?.Report($"NZ: {av.Name} R{raceNum} " +
$"[{totalMeetings} meeting]");
try
{
var resp = _client.TryGetRaceForm(date, av.Slug,
raceNum, "gallops", "nz", Timezone, ct);
if (resp == null || string.IsNullOrEmpty(resp.Content))
break;
ParseRaceFormIntoTable(dt, resp.Content, "nz");
}
catch (OperationCanceledException) { throw; }
catch
{
totalErrors++;
break;
}
completedNzRaces++;
int pct = downloadBase + (int)((double)completedNzRaces / Math.Max(estimatedNzRaces, 1) * downloadSpan);
progress?.Report(Math.Min(pct, phaseBase + phaseSpan));
}
}
}
else
{
status?.Report("NZ: Nessun meeting attivo trovato");
}
}
else
{
status?.Report("NZ: Nessuna pista trovata");
}
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
totalErrors++;
System.Diagnostics.Debug.WriteLine($"Errore fase NZ: {ex.Message}");
}
currentPhase++;
} }
progress?.Report(100); progress?.Report(100);
string errMsg = errors > 0 ? $" ({errors} errori)" : ""; string errMsg = totalErrors > 0 ? $" ({totalErrors} errori)" : "";
status?.Report($"Trovati {dt.Rows.Count} corridori in {meetings.Count} meeting{errMsg}"); string countries = string.Join("+", requestedCountries.Select(c => c.ToUpper()));
status?.Report($"{countries}: {dt.Rows.Count} corridori in {totalMeetings} meeting{errMsg}");
return dt; return dt;
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -124,55 +312,85 @@ namespace HorseRacingPredictor.HorseRacing
} }
} }
#region DataTable creation #region Venues
private DataTable CreateRunnerTable() private class VenueInfo
{ {
var dt = new DataTable(); public string Name { get; set; }
// Campi corsa public string Slug { get; set; }
dt.Columns.Add("Ippodromo", typeof(string)); public string Country { get; set; }
dt.Columns.Add("Corsa N.", typeof(int)); }
dt.Columns.Add("Nome Corsa", typeof(string));
dt.Columns.Add("Orario", typeof(string)); private class ActiveVenue
dt.Columns.Add("Distanza", typeof(string)); {
dt.Columns.Add("Terreno", typeof(string)); public string Name { get; set; }
dt.Columns.Add("Classe", typeof(string)); public string Slug { get; set; }
dt.Columns.Add("Meteo", typeof(string)); public string Country { get; set; }
dt.Columns.Add("Premio", typeof(string)); public string FirstRaceContent { get; set; }
dt.Columns.Add("N. Corridori", typeof(int)); }
// Campi corridore
dt.Columns.Add("Num", typeof(int)); private List<VenueInfo> GetFilteredVenues(string country, CancellationToken ct)
dt.Columns.Add("Cavallo", typeof(string)); {
dt.Columns.Add("Fantino", typeof(string)); if (_venuesCache == null)
dt.Columns.Add("Allenatore", typeof(string)); _venuesCache = ParseVenues(_client.GetVenues(ct).Content);
dt.Columns.Add("Peso", typeof(string));
dt.Columns.Add("Claim", typeof(string)); return _venuesCache
dt.Columns.Add("Box", typeof(string)); .Where(v => string.Equals(v.Country, country, StringComparison.OrdinalIgnoreCase)
dt.Columns.Add("Età", typeof(string)); && !string.IsNullOrEmpty(v.Name))
dt.Columns.Add("Forma", typeof(string)); .OrderBy(v => v.Name)
dt.Columns.Add("Ultimi 20", typeof(string)); .ToList();
dt.Columns.Add("Colori", typeof(string)); }
dt.Columns.Add("Cambio Equip.", typeof(string));
// Statistiche overall private static List<VenueInfo> ParseVenues(string json)
dt.Columns.Add("Vitt.", typeof(string)); {
dt.Columns.Add("Piazz.", typeof(string)); var venues = new List<VenueInfo>();
dt.Columns.Add("Partenze", typeof(string)); if (string.IsNullOrEmpty(json)) return venues;
dt.Columns.Add("% Vitt.", typeof(string));
dt.Columns.Add("% Piazz.", typeof(string)); try
// Statistiche pista {
dt.Columns.Add("Pista V/P/S", typeof(string)); using (var doc = JsonDocument.Parse(json))
// Statistiche distanza {
dt.Columns.Add("Dist. V/P/S", typeof(string)); var root = doc.RootElement;
// Statistiche condizione
dt.Columns.Add("Cond. V/P/S", typeof(string)); JsonElement arr;
// Stato if (root.TryGetProperty("venues", out var venuesEl) &&
dt.Columns.Add("Ritirato", typeof(string)); venuesEl.ValueKind == JsonValueKind.Array)
return dt; arr = venuesEl;
else if (root.ValueKind == JsonValueKind.Array)
arr = root;
else
return venues;
foreach (var v in arr.EnumerateArray())
{
try
{
string name = GetString(v, "name", "");
string country = GetString(v, "country", "");
string raceType = GetString(v, "raceType", "gallops");
if (raceType != "gallops") continue;
if (string.IsNullOrEmpty(name)) continue;
venues.Add(new VenueInfo
{
Name = name,
Slug = name.ToLowerInvariant().Replace(" ", "-"),
Country = country
});
}
catch { }
}
}
}
catch { }
return venues;
} }
#endregion #endregion
#region JSON Parsing #region Meetings parsing (AU)
private class MeetingInfo private class MeetingInfo
{ {
@@ -242,7 +460,53 @@ namespace HorseRacingPredictor.HorseRacing
return meetings; return meetings;
} }
private void ParseRaceFormIntoTable(DataTable dt, string json) #endregion
#region DataTable creation
private DataTable CreateRunnerTable()
{
var dt = new DataTable();
dt.Columns.Add("Ippodromo", typeof(string));
dt.Columns.Add("Paese", typeof(string));
dt.Columns.Add("Corsa N.", typeof(int));
dt.Columns.Add("Nome Corsa", typeof(string));
dt.Columns.Add("Orario", typeof(string));
dt.Columns.Add("Distanza", typeof(string));
dt.Columns.Add("Terreno", typeof(string));
dt.Columns.Add("Classe", typeof(string));
dt.Columns.Add("Meteo", typeof(string));
dt.Columns.Add("Premio", typeof(string));
dt.Columns.Add("N. Corridori", typeof(int));
dt.Columns.Add("Num", typeof(int));
dt.Columns.Add("Cavallo", typeof(string));
dt.Columns.Add("Fantino", typeof(string));
dt.Columns.Add("Allenatore", typeof(string));
dt.Columns.Add("Peso", typeof(string));
dt.Columns.Add("Claim", typeof(string));
dt.Columns.Add("Box", typeof(string));
dt.Columns.Add("Eta'", typeof(string));
dt.Columns.Add("Forma", typeof(string));
dt.Columns.Add("Ultimi 20", typeof(string));
dt.Columns.Add("Colori", typeof(string));
dt.Columns.Add("Cambio Equip.", typeof(string));
dt.Columns.Add("Vitt.", typeof(string));
dt.Columns.Add("Piazz.", typeof(string));
dt.Columns.Add("Partenze", typeof(string));
dt.Columns.Add("% Vitt.", typeof(string));
dt.Columns.Add("% Piazz.", typeof(string));
dt.Columns.Add("Pista V/P/S", typeof(string));
dt.Columns.Add("Dist. V/P/S", typeof(string));
dt.Columns.Add("Cond. V/P/S", typeof(string));
dt.Columns.Add("Ritirato", typeof(string));
return dt;
}
#endregion
#region JSON Parsing
private void ParseRaceFormIntoTable(DataTable dt, string json, string fallbackCountry)
{ {
if (string.IsNullOrEmpty(json)) return; if (string.IsNullOrEmpty(json)) return;
@@ -253,6 +517,7 @@ namespace HorseRacingPredictor.HorseRacing
var root = doc.RootElement; var root = doc.RootElement;
string track = GetString(root, "track", ""); string track = GetString(root, "track", "");
string country = GetString(root, "country", fallbackCountry);
int raceNumber = GetInt(root, "raceNumber"); int raceNumber = GetInt(root, "raceNumber");
string raceName = GetString(root, "raceName", ""); string raceName = GetString(root, "raceName", "");
string distance = GetString(root, "distance", ""); string distance = GetString(root, "distance", "");
@@ -263,21 +528,11 @@ namespace HorseRacingPredictor.HorseRacing
string startTime = GetString(root, "startTime", ""); string startTime = GetString(root, "startTime", "");
int numberOfRunners = GetInt(root, "numberOfRunners"); int numberOfRunners = GetInt(root, "numberOfRunners");
// Formatta orario se è un ISO datetime string orario = FormatStartTime(startTime);
string orario = "";
if (!string.IsNullOrEmpty(startTime)) string countryDisplay = country.ToUpperInvariant();
{ if (CountryNames.TryGetValue(country.ToLowerInvariant(), out var cn))
try countryDisplay = cn;
{
var dto = DateTimeOffset.Parse(startTime);
var romeTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
orario = TimeZoneInfo.ConvertTime(dto, romeTz).ToString("HH:mm");
}
catch
{
orario = startTime;
}
}
if (!root.TryGetProperty("runners", out var runnersEl) || if (!root.TryGetProperty("runners", out var runnersEl) ||
runnersEl.ValueKind != JsonValueKind.Array) runnersEl.ValueKind != JsonValueKind.Array)
@@ -289,8 +544,8 @@ namespace HorseRacingPredictor.HorseRacing
{ {
var row = dt.NewRow(); var row = dt.NewRow();
// Campi corsa
row["Ippodromo"] = track; row["Ippodromo"] = track;
row["Paese"] = countryDisplay;
row["Corsa N."] = raceNumber; row["Corsa N."] = raceNumber;
row["Nome Corsa"] = raceName; row["Nome Corsa"] = raceName;
row["Orario"] = orario; row["Orario"] = orario;
@@ -301,7 +556,6 @@ namespace HorseRacingPredictor.HorseRacing
row["Premio"] = prizeMoney; row["Premio"] = prizeMoney;
row["N. Corridori"] = numberOfRunners; row["N. Corridori"] = numberOfRunners;
// Campi corridore
row["Num"] = GetInt(runner, "number"); row["Num"] = GetInt(runner, "number");
row["Cavallo"] = GetString(runner, "name", ""); row["Cavallo"] = GetString(runner, "name", "");
row["Fantino"] = GetString(runner, "jockey", ""); row["Fantino"] = GetString(runner, "jockey", "");
@@ -315,7 +569,7 @@ namespace HorseRacingPredictor.HorseRacing
row["Box"] = GetInt(runner, "barrier") > 0 row["Box"] = GetInt(runner, "barrier") > 0
? GetInt(runner, "barrier").ToString() ? GetInt(runner, "barrier").ToString()
: GetString(runner, "barrier", ""); : GetString(runner, "barrier", "");
row["Età"] = GetInt(runner, "age") > 0 row["Eta'"] = GetInt(runner, "age") > 0
? GetInt(runner, "age").ToString() ? GetInt(runner, "age").ToString()
: GetString(runner, "age", ""); : GetString(runner, "age", "");
row["Forma"] = GetString(runner, "form", ""); row["Forma"] = GetString(runner, "form", "");
@@ -323,21 +577,20 @@ namespace HorseRacingPredictor.HorseRacing
row["Colori"] = GetString(runner, "racingColours", ""); row["Colori"] = GetString(runner, "racingColours", "");
row["Cambio Equip."] = GetString(runner, "gearChange", ""); row["Cambio Equip."] = GetString(runner, "gearChange", "");
// Statistiche overall
if (runner.TryGetProperty("stats", out var statsEl)) if (runner.TryGetProperty("stats", out var statsEl))
{ {
ParseStatGroup(statsEl, "overall", row, "Vitt.", "Piazz.", "Partenze", "% Vitt.", "% Piazz."); ParseStatGroup(statsEl, "overall", row,
"Vitt.", "Piazz.", "Partenze", "% Vitt.", "% Piazz.");
row["Pista V/P/S"] = FormatStatSummary(statsEl, "track"); row["Pista V/P/S"] = FormatStatSummary(statsEl, "track");
row["Dist. V/P/S"] = FormatStatSummary(statsEl, "distance"); row["Dist. V/P/S"] = FormatStatSummary(statsEl, "distance");
row["Cond. V/P/S"] = FormatStatSummary(statsEl, "condition"); row["Cond. V/P/S"] = FormatStatSummary(statsEl, "condition");
} }
// Ritirato
bool scratched = false; bool scratched = false;
if (runner.TryGetProperty("scratched", out var scEl) && scEl.ValueKind == JsonValueKind.True) if (runner.TryGetProperty("scratched", out var scEl) &&
scEl.ValueKind == JsonValueKind.True)
scratched = true; scratched = true;
row["Ritirato"] = scratched ? "Sì" : ""; row["Ritirato"] = scratched ? "Si" : "";
dt.Rows.Add(row); dt.Rows.Add(row);
} }
@@ -348,9 +601,30 @@ namespace HorseRacingPredictor.HorseRacing
catch { } catch { }
} }
/// <summary> private static string FormatStartTime(string startTime)
/// Estrae wins, places, starts, winPercent, placePercent da un sotto-oggetto stats {
/// </summary> if (string.IsNullOrEmpty(startTime)) return "";
try
{
var dto = DateTimeOffset.Parse(startTime);
// Converti al fuso orario locale di Roma
try
{
var romeTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
return TimeZoneInfo.ConvertTime(dto, romeTz).ToString("HH:mm");
}
catch
{
return dto.ToLocalTime().ToString("HH:mm");
}
}
catch
{
return startTime;
}
}
private static void ParseStatGroup(JsonElement statsEl, string group, private static void ParseStatGroup(JsonElement statsEl, string group,
DataRow row, string winsCol, string placesCol, string startsCol, DataRow row, string winsCol, string placesCol, string startsCol,
string winPctCol, string placePctCol) string winPctCol, string placePctCol)
@@ -370,9 +644,6 @@ namespace HorseRacingPredictor.HorseRacing
row[placePctCol] = (placePct * 100).ToString("F0") + "%"; row[placePctCol] = (placePct * 100).ToString("F0") + "%";
} }
/// <summary>
/// Formatta un riassunto "V-P/S" per un sotto-gruppo statistico (es. track, distance, condition)
/// </summary>
private static string FormatStatSummary(JsonElement statsEl, string group) private static string FormatStatSummary(JsonElement statsEl, string group)
{ {
if (!statsEl.TryGetProperty(group, out var g)) return ""; if (!statsEl.TryGetProperty(group, out var g)) return "";
@@ -0,0 +1,49 @@
using System;
using System.IO;
using Microsoft.Extensions.Configuration;
namespace HorseRacingPredictor
{
/// <summary>
/// Provides centralised access to application configuration loaded from appsettings.json.
/// Connection strings and API keys that were previously hard-coded are now read from here.
/// User-editable preferences (export paths, date formats, …) remain in settings.ini.
/// </summary>
internal static class AppConfig
{
private static IConfiguration _configuration;
public static IConfiguration Configuration => _configuration ??= BuildConfiguration();
private static IConfiguration BuildConfiguration()
{
var basePath = AppDomain.CurrentDomain.BaseDirectory;
return new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile(
$"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json",
optional: true,
reloadOnChange: true)
.Build();
}
// ?? Connection strings ??????????????????????????????????
public static string FootballConnectionString =>
Configuration.GetConnectionString("Football");
public static string HorsesConnectionString =>
Configuration.GetConnectionString("Horses");
// ?? API settings ????????????????????????????????????????
public static string FootballApiKey =>
Configuration["Api:FootballApiKey"] ?? string.Empty;
public static string FootballApiKeyHeader =>
Configuration["Api:FootballApiKeyHeader"] ?? "x-rapidapi-key";
public static string FootballApiHost =>
Configuration["Api:FootballApiHost"] ?? "v3.football.api-sports.io";
}
}
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace HorseRacingPredictor
{
/// <summary>
/// User-editable preferences persisted as JSON.
/// Replaces the legacy settings.ini key=value format.
/// </summary>
internal sealed class UserSettings
{
private static readonly string FilePath =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "usersettings.json");
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
// ?? Football ????????????????????????????????????????????
public string ApiKey { get; set; } = string.Empty;
public string FbExportPath { get; set; } = string.Empty;
public string FbPrefix { get; set; } = string.Empty;
public string FbSuffix { get; set; } = string.Empty;
public bool FbIncludeDate { get; set; } = true;
public string FbDateFormat { get; set; } = "yyyy-MM-dd";
public string FbFormat { get; set; } = "CSV";
// ?? Racing ??????????????????????????????????????????????
public string RacingApiKey { get; set; } = string.Empty;
public string RcExportPath { get; set; } = string.Empty;
public string RcPrefix { get; set; } = string.Empty;
public string RcSuffix { get; set; } = string.Empty;
public bool RcIncludeDate { get; set; } = true;
public string RcDateFormat { get; set; } = "yyyy-MM-dd";
public string RcFormat { get; set; } = "CSV";
public string RcTimezone { get; set; } = "Australia/Sydney";
public List<string> RcCountries { get; set; } = new() { "au", "nz" };
// ?? Persistence ?????????????????????????????????????????
public static UserSettings Load()
{
try
{
if (!File.Exists(FilePath))
return MigrateFromIniOrDefault();
var json = File.ReadAllText(FilePath);
return JsonSerializer.Deserialize<UserSettings>(json, JsonOptions) ?? new UserSettings();
}
catch
{
return new UserSettings();
}
}
public void Save()
{
var json = JsonSerializer.Serialize(this, JsonOptions);
File.WriteAllText(FilePath, json);
}
/// <summary>
/// One-time migration: reads old settings.ini if present, converts to UserSettings,
/// saves the new usersettings.json, then deletes the ini file.
/// </summary>
private static UserSettings MigrateFromIniOrDefault()
{
var iniPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.ini");
if (!File.Exists(iniPath))
return new UserSettings();
var settings = new UserSettings();
try
{
foreach (var line in File.ReadAllLines(iniPath))
{
var idx = line.IndexOf('=');
if (idx < 0) continue;
var key = line[..idx].Trim();
var val = line[(idx + 1)..].Trim();
switch (key)
{
case "ApiKey": settings.ApiKey = val; break;
case "FbExportPath": settings.FbExportPath = val; break;
case "FbPrefix": settings.FbPrefix = val; break;
case "FbSuffix": settings.FbSuffix = val; break;
case "FbIncludeDate": settings.FbIncludeDate = val is "1" or "true" or "True"; break;
case "FbDateFormat": settings.FbDateFormat = val; break;
case "FbFormat": settings.FbFormat = val; break;
case "RcExportPath": settings.RcExportPath = val; break;
case "RcPrefix": settings.RcPrefix = val; break;
case "RcSuffix": settings.RcSuffix = val; break;
case "RcIncludeDate": settings.RcIncludeDate = val is "1" or "true" or "True"; break;
case "RcDateFormat": settings.RcDateFormat = val; break;
case "RcFormat": settings.RcFormat = val; break;
case "RacingApiKey": settings.RacingApiKey = val; break;
case "RcTimezone": settings.RcTimezone = val; break;
case "RcCountries":
settings.RcCountries = new List<string>(
val.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries));
break;
}
}
// Persist as JSON and remove legacy file
settings.Save();
File.Delete(iniPath);
}
catch
{
// If migration fails, return whatever we parsed
}
return settings;
}
}
}
@@ -0,0 +1,11 @@
{
"ConnectionStrings": {
"Football": "Server=DESKTOP-9O9JHFS;Database=TestBS_Football;User Id=sa;Password=Asti2019;TrustServerCertificate=True",
"Horses": "Server=DESKTOP-9O9JHFS;Database=TestBS_Horses;User Id=sa;Password=Asti2019;TrustServerCertificate=True"
},
"Api": {
"FootballApiKey": "f3795ccef056c5478d316162517d9970",
"FootballApiKeyHeader": "x-rapidapi-key",
"FootballApiHost": "v3.football.api-sports.io"
}
}
@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
using System.Data; using System.Data;
using System.IO; using System.IO;
using BettingPredictor; using BettingPredictor;
@@ -10,16 +10,14 @@ namespace HorseRacingPredictor.Horses
{ {
internal class Database : HorseRacingPredictor.Manager.Database internal class Database : HorseRacingPredictor.Manager.Database
{ {
// Implementazione della proprietà astratta _connectionString per il database cavalli // Connection string caricata da appsettings.json
protected override string _connectionString => "Server=DESKTOP-9O9JHFS;Database=TestBS_Horses;User Id=sa;Password=Asti2019;"; public Database() : base(AppConfig.HorsesConnectionString)
private readonly FileReader fileReaderHorses;
public Database()
{ {
fileReaderHorses = new FileReader(); fileReaderHorses = new FileReader();
} }
private readonly FileReader fileReaderHorses;
public DataTable GetAllHorseRaceData() public DataTable GetAllHorseRaceData()
{ {
DataTable horseRaceData = new DataTable(); DataTable horseRaceData = new DataTable();
@@ -644,11 +644,6 @@
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<DatePicker x:Name="dpRacing" Width="160"/> <DatePicker x:Name="dpRacing" Width="160"/>
<ComboBox x:Name="cmbRaceCode" Width="120" Margin="8,0,0,0" SelectedIndex="0">
<ComboBoxItem Content="Galoppo"/>
<ComboBoxItem Content="Trotto"/>
<ComboBoxItem Content="Levrieri"/>
</ComboBox>
<Button x:Name="btnDownloadRc" Content="Scarica Corse" <Button x:Name="btnDownloadRc" Content="Scarica Corse"
Style="{StaticResource AccentBtn}" Style="{StaticResource AccentBtn}"
Background="{StaticResource BrBlue}" Margin="12,0,0,0" Background="{StaticResource BrBlue}" Margin="12,0,0,0"
@@ -792,6 +787,73 @@
<TextBox x:Name="txtRacingApiKey" Style="{StaticResource FlatTb}" MaxWidth="510" HorizontalAlignment="Left"/> <TextBox x:Name="txtRacingApiKey" Style="{StaticResource FlatTb}" MaxWidth="510" HorizontalAlignment="Left"/>
<Grid Margin="0,14,0,0"> <Grid Margin="0,14,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="245"/>
<ColumnDefinition Width="22"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="Timezone (IANA)" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
<TextBox x:Name="txtRcTimezone" Style="{StaticResource FlatTb}" Text="Australia/Sydney"/>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="Nazioni" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
<Grid>
<ToggleButton x:Name="btnRcCountriesToggle"
Height="34" HorizontalContentAlignment="Left"
Padding="10,0,28,0" Cursor="Hand">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Border x:Name="Bd" CornerRadius="6"
Background="{StaticResource BrSurface0}"
BorderBrush="{StaticResource BrSurface1}"
BorderThickness="1"/>
<ContentPresenter Margin="10,0,28,0"
VerticalAlignment="Center"
HorizontalAlignment="Left"/>
<Path Data="M 0,0 L 4,4 L 8,0"
Stroke="{StaticResource BrSubtext0}" StrokeThickness="1.5"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,10,0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrOverlay0}"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrBlue}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
<TextBlock x:Name="lblRcCountriesSummary" Text="AU, NZ"
Foreground="{StaticResource BrText}" FontSize="13"
TextTrimming="CharacterEllipsis"/>
</ToggleButton>
<Popup x:Name="popupRcCountries" Placement="Bottom"
StaysOpen="False" AllowsTransparency="True"
IsOpen="{Binding IsChecked, ElementName=btnRcCountriesToggle, Mode=TwoWay}">
<Border Background="{StaticResource BrSurface1}"
BorderBrush="{StaticResource BrSurface2}" BorderThickness="1"
CornerRadius="8" Padding="6,8" Margin="0,4,0,0"
MinWidth="260" MaxHeight="340">
<Border.Effect>
<DropShadowEffect BlurRadius="16" ShadowDepth="4" Color="#40000000"/>
</Border.Effect>
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<StackPanel x:Name="pnlRcCountries"/>
</ScrollViewer>
</Border>
</Popup>
</Grid>
</StackPanel>
</Grid>
<Border Height="1" Background="{StaticResource BrBorder}" Margin="0,14,0,14"/>
<Grid Margin="0,0,0,0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="245"/> <ColumnDefinition Width="245"/>
<ColumnDefinition Width="22"/> <ColumnDefinition Width="22"/>
@@ -32,6 +32,7 @@ namespace HorseRacingPredictor
InitializeComponent(); InitializeComponent();
_footballManager = new Football.Main(); _footballManager = new Football.Main();
_racingManager = new HorseRacing.Main(DefaultRacingApiKey); _racingManager = new HorseRacing.Main(DefaultRacingApiKey);
BuildCountryCheckboxes();
// Wire preview update events // Wire preview update events
txtFbPrefix.TextChanged += (s, e) => UpdateFbPreview(); txtFbPrefix.TextChanged += (s, e) => UpdateFbPreview();
txtFbSuffix.TextChanged += (s, e) => UpdateFbPreview(); txtFbSuffix.TextChanged += (s, e) => UpdateFbPreview();
@@ -334,13 +335,111 @@ namespace HorseRacingPredictor
// ???????????????????? HORSE RACING ???????????????????? // ???????????????????? HORSE RACING ????????????????????
private readonly Dictionary<string, CheckBox> _countryCheckboxes = new Dictionary<string, CheckBox>();
private void BuildCountryCheckboxes()
{
if (pnlRcCountries == null) return;
pnlRcCountries.Children.Clear();
_countryCheckboxes.Clear();
var supported = new HashSet<string>(HorseRacing.Main.SupportedCountries);
// Header: nazioni con dati
pnlRcCountries.Children.Add(new TextBlock
{
Text = "Con dati disponibili",
FontSize = 10,
FontFamily = new System.Windows.Media.FontFamily("Segoe UI Semibold"),
Foreground = FindResource("BrBlue") as System.Windows.Media.Brush,
Margin = new Thickness(6, 2, 0, 4)
});
foreach (var code in HorseRacing.Main.SupportedCountries)
AddCountryCheckbox(code, supported, true);
// Separator
pnlRcCountries.Children.Add(new Border
{
Height = 1,
Background = FindResource("BrBorder") as System.Windows.Media.Brush,
Margin = new Thickness(4, 6, 4, 6)
});
// Header: catalogo
pnlRcCountries.Children.Add(new TextBlock
{
Text = "Solo catalogo (nessun dato di forma)",
FontSize = 10,
Foreground = FindResource("BrOverlay0") as System.Windows.Media.Brush,
Margin = new Thickness(6, 2, 0, 4)
});
foreach (var code in HorseRacing.Main.AllCountries)
{
if (supported.Contains(code)) continue;
AddCountryCheckbox(code, supported, false);
}
}
private void AddCountryCheckbox(string code, HashSet<string> supported, bool isSupported)
{
string label = HorseRacing.Main.CountryNames.TryGetValue(code, out var name)
? $"{name} ({code.ToUpper()})"
: code.ToUpper();
var cb = new CheckBox
{
Content = label,
Tag = code,
IsChecked = false,
Margin = new Thickness(4, 2, 4, 2),
FontSize = 12,
Foreground = isSupported
? FindResource("BrText") as System.Windows.Media.Brush
: FindResource("BrOverlay0") as System.Windows.Media.Brush,
Opacity = isSupported ? 1.0 : 0.7
};
cb.Checked += (s, e) => UpdateCountriesSummary();
cb.Unchecked += (s, e) => UpdateCountriesSummary();
_countryCheckboxes[code] = cb;
pnlRcCountries.Children.Add(cb);
}
private List<string> GetSelectedCountries()
{
return _countryCheckboxes
.Where(kv => kv.Value.IsChecked == true)
.Select(kv => kv.Key)
.ToList();
}
private void SetSelectedCountries(IEnumerable<string> codes)
{
var set = new HashSet<string>(codes.Select(c => c.Trim().ToLowerInvariant()));
foreach (var kv in _countryCheckboxes)
kv.Value.IsChecked = set.Contains(kv.Key);
UpdateCountriesSummary();
}
private void UpdateCountriesSummary()
{
var selected = GetSelectedCountries();
if (lblRcCountriesSummary != null)
{
lblRcCountriesSummary.Text = selected.Count > 0
? string.Join(", ", selected.Select(c => c.ToUpper()))
: "Nessuna";
}
}
private void rbRcSource_Checked(object sender, RoutedEventArgs e) private void rbRcSource_Checked(object sender, RoutedEventArgs e)
{ {
// Toggle visibility of API vs CSV controls // Toggle visibility of API vs CSV controls
if (dpRacing == null || btnDownloadRc == null || btnBrowseCsvRc == null) return; if (dpRacing == null || btnDownloadRc == null || btnBrowseCsvRc == null) return;
bool isApi = rbRcApi.IsChecked == true; bool isApi = rbRcApi.IsChecked == true;
dpRacing.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed; dpRacing.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
if (cmbRaceCode != null) cmbRaceCode.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
btnDownloadRc.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed; btnDownloadRc.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
btnBrowseCsvRc.Visibility = isApi ? Visibility.Collapsed : Visibility.Visible; btnBrowseCsvRc.Visibility = isApi ? Visibility.Collapsed : Visibility.Visible;
} }
@@ -528,20 +627,9 @@ namespace HorseRacingPredictor
r[col] = n++; r[col] = n++;
} }
private string GetSelectedRaceCode()
{
int idx = cmbRaceCode?.SelectedIndex ?? 0;
switch (idx)
{
case 1: return "harness";
case 2: return "greyhounds";
default: return "gallops";
}
}
private async Task DownloadRacecardsAsync() private async Task DownloadRacecardsAsync()
{ {
// Se è già in corso, annulla // Se e' gia' in corso, annulla
if (_racingCts != null) if (_racingCts != null)
{ {
_racingCts.Cancel(); _racingCts.Cancel();
@@ -557,20 +645,21 @@ namespace HorseRacingPredictor
try try
{ {
pbRacing.Value = 0; pbRacing.Value = 0;
lblStatusRc.Text = "Scaricamento corse da FormFav"; lblStatusRc.Text = "Scaricamento corse da FormFav...";
btnDownloadRc.Content = "Annulla"; btnDownloadRc.Content = "Annulla";
dpRacing.IsEnabled = false; dpRacing.IsEnabled = false;
cmbRaceCode.IsEnabled = false;
btnExportRcCsv.IsEnabled = false; btnExportRcCsv.IsEnabled = false;
// Applica impostazioni correnti al manager
ApplyRacingSettings();
var progress = new Progress<int>(v => pbRacing.Value = v); var progress = new Progress<int>(v => pbRacing.Value = v);
var status = new Progress<string>(s => lblStatusRc.Text = s); var status = new Progress<string>(s => lblStatusRc.Text = s);
var date = dpRacing.SelectedDate ?? DateTime.Today; var date = dpRacing.SelectedDate ?? DateTime.Today;
string raceCode = GetSelectedRaceCode();
var table = await Task.Run(() => var table = await Task.Run(() =>
_racingManager.GetAllRacesForDate(date, raceCode, progress, status, ct), ct); _racingManager.GetAllRacesForDate(date, progress, status, ct), ct);
_racingData = table; _racingData = table;
@@ -606,10 +695,22 @@ namespace HorseRacingPredictor
_racingCts = null; _racingCts = null;
btnDownloadRc.Content = "Scarica Corse"; btnDownloadRc.Content = "Scarica Corse";
dpRacing.IsEnabled = true; dpRacing.IsEnabled = true;
cmbRaceCode.IsEnabled = true;
} }
} }
private void ApplyRacingSettings()
{
_racingManager.UpdateApiKey(txtRacingApiKey.Text.Trim());
var tz = txtRcTimezone?.Text?.Trim();
if (!string.IsNullOrEmpty(tz))
_racingManager.Timezone = tz;
var selected = GetSelectedCountries();
if (selected.Count > 0)
_racingManager.Countries = selected;
}
private void btnExportRcCsv_Click(object sender, RoutedEventArgs e) private void btnExportRcCsv_Click(object sender, RoutedEventArgs e)
{ {
var rcDate = dpRacing.SelectedDate ?? DateTime.Today; var rcDate = dpRacing.SelectedDate ?? DateTime.Today;
@@ -730,46 +831,36 @@ namespace HorseRacingPredictor
return null; return null;
} }
// ???????????????????? SETTINGS ???????????????????? // —————————————————— SETTINGS ——————————————————
private string SettingsFilePath =>
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.ini");
private void LoadSettings() private void LoadSettings()
{ {
try try
{ {
txtRacingApiKey.Text = DefaultRacingApiKey; var s = UserSettings.Load();
if (!File.Exists(SettingsFilePath)) return; txtApiKey.Text = s.ApiKey;
foreach (var line in File.ReadAllLines(SettingsFilePath)) txtFbExportPath.Text = s.FbExportPath;
{ txtFbPrefix.Text = s.FbPrefix;
var idx = line.IndexOf('='); txtFbSuffix.Text = s.FbSuffix;
if (idx < 0) continue; chkFbIncludeDate.IsChecked = s.FbIncludeDate;
var key = line.Substring(0, idx).Trim(); SetComboBoxSelectionByContent(cmbFbDateFormat, s.FbDateFormat);
var val = line.Substring(idx + 1).Trim(); SetComboBoxSelectionByContent(cmbFbFormat, s.FbFormat);
if (key == "ApiKey") txtApiKey.Text = val; txtRcExportPath.Text = s.RcExportPath;
else if (key == "FbExportPath") txtFbExportPath.Text = val; txtRcPrefix.Text = s.RcPrefix;
else if (key == "FbPrefix") txtFbPrefix.Text = val; txtRcSuffix.Text = s.RcSuffix;
else if (key == "FbSuffix") txtFbSuffix.Text = val; chkRcIncludeDate.IsChecked = s.RcIncludeDate;
else if (key == "FbIncludeDate") chkFbIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase); SetComboBoxSelectionByContent(cmbRcDateFormat, s.RcDateFormat);
else if (key == "FbDateFormat") { try { SetComboBoxSelectionByContent(cmbFbDateFormat, val); } catch { } } SetComboBoxSelectionByContent(cmbRcFormat, s.RcFormat);
else if (key == "FbFormat") { try { SetComboBoxSelectionByContent(cmbFbFormat, val); } catch { } }
else if (key == "RcExportPath") txtRcExportPath.Text = val; txtRacingApiKey.Text = string.IsNullOrEmpty(s.RacingApiKey) ? DefaultRacingApiKey : s.RacingApiKey;
else if (key == "RcPrefix") txtRcPrefix.Text = val; if (txtRcTimezone != null) txtRcTimezone.Text = s.RcTimezone;
else if (key == "RcSuffix") txtRcSuffix.Text = val; SetSelectedCountries(s.RcCountries.ToArray());
else if (key == "RcIncludeDate") chkRcIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase);
else if (key == "RcDateFormat") { try { SetComboBoxSelectionByContent(cmbRcDateFormat, val); } catch { } }
else if (key == "RcFormat") { try { SetComboBoxSelectionByContent(cmbRcFormat, val); } catch { } }
else if (key == "RacingApiKey") txtRacingApiKey.Text = val;
}
// Update preview UI after loading values
UpdateFbPreview(); UpdateFbPreview();
UpdateRcPreview(); UpdateRcPreview();
ApplyRacingSettings();
_racingManager.UpdateApiKey(txtRacingApiKey.Text);
} }
catch { } catch { }
} }
@@ -1000,28 +1091,30 @@ namespace HorseRacingPredictor
{ {
try try
{ {
var sb = new StringBuilder(); var s = new UserSettings
sb.AppendLine($"ApiKey={txtApiKey.Text.Trim()}"); {
sb.AppendLine($"FbExportPath={txtFbExportPath.Text.Trim()}"); ApiKey = txtApiKey.Text.Trim(),
sb.AppendLine($"FbPrefix={txtFbPrefix.Text.Trim()}"); FbExportPath = txtFbExportPath.Text.Trim(),
sb.AppendLine($"FbSuffix={txtFbSuffix.Text.Trim()}"); FbPrefix = txtFbPrefix.Text.Trim(),
sb.AppendLine($"FbIncludeDate={(chkFbIncludeDate.IsChecked==true?"1":"0")}"); FbSuffix = txtFbSuffix.Text.Trim(),
sb.AppendLine($"FbDateFormat={(cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}"); FbIncludeDate = chkFbIncludeDate.IsChecked == true,
sb.AppendLine($"FbFormat={(cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}"); FbDateFormat = (cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
sb.AppendLine($"RcExportPath={txtRcExportPath.Text.Trim()}"); FbFormat = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
sb.AppendLine($"RcPrefix={txtRcPrefix.Text.Trim()}"); RcExportPath = txtRcExportPath.Text.Trim(),
sb.AppendLine($"RcSuffix={txtRcSuffix.Text.Trim()}"); RcPrefix = txtRcPrefix.Text.Trim(),
sb.AppendLine($"RcIncludeDate={(chkRcIncludeDate.IsChecked==true?"1":"0")}"); RcSuffix = txtRcSuffix.Text.Trim(),
sb.AppendLine($"RcDateFormat={(cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}"); RcIncludeDate = chkRcIncludeDate.IsChecked == true,
sb.AppendLine($"RcFormat={(cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}"); RcDateFormat = (cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
sb.AppendLine($"RacingApiKey={txtRacingApiKey.Text.Trim()}"); RcFormat = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
File.WriteAllText(SettingsFilePath, sb.ToString(), Encoding.UTF8); RacingApiKey = txtRacingApiKey.Text.Trim(),
RcTimezone = txtRcTimezone?.Text?.Trim() ?? "Australia/Sydney",
RcCountries = new List<string>(GetSelectedCountries())
};
s.Save();
// update previews after save
UpdateFbPreview(); UpdateFbPreview();
UpdateRcPreview(); UpdateRcPreview();
ApplyRacingSettings();
_racingManager.UpdateApiKey(txtRacingApiKey.Text.Trim());
MessageBox.Show("Impostazioni salvate con successo.", MessageBox.Show("Impostazioni salvate con successo.",
"Salvato", MessageBoxButton.OK, MessageBoxImage.Information); "Salvato", MessageBoxButton.OK, MessageBoxImage.Information);
@@ -1,12 +1,16 @@
using System; using System;
using System.Data.SqlClient; using Microsoft.Data.SqlClient;
namespace HorseRacingPredictor.Manager namespace HorseRacingPredictor.Manager
{ {
internal abstract class Database internal abstract class Database
{ {
// La stringa di connessione viene rimossa da qui e definita nelle classi derivate protected readonly string _connectionString;
protected abstract string _connectionString { get; }
protected Database(string connectionString)
{
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
}
protected SqlConnection GetConnection() protected SqlConnection GetConnection()
{ {
@@ -69,7 +73,7 @@ namespace HorseRacingPredictor.Manager
} }
/// <summary> /// <summary>
/// Metodo per verificare se la connessione al database è valida /// Metodo per verificare se la connessione al database è valida
/// </summary> /// </summary>
public bool TestConnection() public bool TestConnection()
{ {
@@ -1,30 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace BettingPredictor.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}
@@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CsvHelper" version="33.1.0" targetFramework="net481" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="Microsoft.Bcl.HashCode" version="6.0.0" targetFramework="net481" />
<package id="Microsoft.Bcl.Numerics" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="Microsoft.CSharp" version="4.7.0" targetFramework="net481" />
<package id="Microsoft.ML" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.ML.CpuMath" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.ML.DataView" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.ML.FastTree" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.Web.WebView2" version="1.0.3800.47" targetFramework="net481" />
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net481" />
<package id="RestSharp" version="112.1.1-alpha.0.4" targetFramework="net481" />
<package id="System.Buffers" version="4.6.1" targetFramework="net481" />
<package id="System.CodeDom" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Collections.Immutable" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.IO.Pipelines" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Memory" version="4.6.3" targetFramework="net481" />
<package id="System.Numerics.Tensors" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net481" />
<package id="System.Reflection.Emit.Lightweight" version="4.7.0" targetFramework="net481" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net481" />
<package id="System.Text.Encodings.Web" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Text.Json" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Threading.Channels" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net481" />
<package id="System.ValueTuple" version="4.6.1" targetFramework="net481" />
</packages>