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 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')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{63138155-B7F3-4246-B47B-B8CC2D7A60A4}</ProjectGuid>
<TargetFramework>net10.0-windows</TargetFramework>
<OutputType>WinExe</OutputType>
<RootNamespace>HorseRacingPredictor</RootNamespace>
<AssemblyName>HorseRacingPredictor</AssemblyName>
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<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>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="CsvHelper, Version=33.0.0.0, Culture=neutral, PublicKeyToken=8c4a6d608ce8f59c, processorArchitecture=MSIL">
<HintPath>..\packages\CsvHelper.33.1.0\lib\net48\CsvHelper.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.WebView2.Core, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.WebView2.Wpf, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.Wpf.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.WebView2.WinForms, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.WinForms.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
</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" />
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="11.0.0-preview.2.26159.112" />
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageReference Include="Microsoft.Bcl.Numerics" Version="11.0.0-preview.2.26159.112" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0" />
<PackageReference Include="Microsoft.ML" Version="6.0.0-preview.26160.2" />
<PackageReference Include="Microsoft.ML.CpuMath" Version="6.0.0-preview.26160.2" />
<PackageReference Include="Microsoft.ML.DataView" Version="6.0.0-preview.26160.2" />
<PackageReference Include="Microsoft.ML.FastTree" Version="6.0.0-preview.26160.2" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3908-prerelease" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" />
<PackageReference Include="RestSharp" Version="114.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
<PackageReference Include="System.Numerics.Tensors" Version="11.0.0-preview.2.26159.112" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="VirtualFootball\VirtualMatch.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="CsvHelperStubs.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<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 Label="Compile items now included by globbing that were not in the original project file">
<Compile Remove="UI\NavButton.cs" />
<Compile Remove="UI\ModernTheme.cs" />
<Compile Remove="UI\ModernProgressBar.cs" />
<Compile Remove="UI\ModernButton.cs" />
<Compile Remove="UI\Controls\ModernTextBox.cs" />
<Compile Remove="UI\Controls\ModernTabControl.cs" />
<Compile Remove="UI\Controls\ModernProgressBar.cs" />
<Compile Remove="UI\Controls\ModernPanel.cs" />
<Compile Remove="UI\Controls\ModernLabel.cs" />
<Compile Remove="UI\Controls\ModernDateTimePicker.cs" />
<Compile Remove="UI\Controls\ModernDataGridView.cs" />
<Compile Remove="UI\Controls\ModernButton.cs" />
<Compile Remove="UI\CardPanel.cs" />
<Compile Remove="Program.cs" />
<Compile Remove="Main.Designer.cs" />
<Compile Remove="Main.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<ItemGroup Label="EmbeddedResource items now included by globbing that were not in the original project file">
<EmbeddedResource Remove="Main.resx" />
</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>
@@ -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.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using System.Text;
using RestSharp;
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using System;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -11,7 +11,7 @@ namespace HorseRacingPredictor.Football.Database
try
{
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 = @"
IF EXISTS (SELECT 1 FROM BetType WHERE bet_type_id = @bet_type_id)
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using System;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -11,7 +11,7 @@ namespace HorseRacingPredictor.Football.Database
try
{
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 = @"
IF EXISTS (SELECT 1 FROM Bookmaker WHERE bookmaker_id = @bookmaker_id)
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using System;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -1,6 +1,6 @@
using System;
using System.Data;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -143,7 +143,7 @@ namespace HorseRacingPredictor.Football.Database
try
{
using (var connection = new System.Data.SqlClient.SqlConnection(_connectionString))
using (var connection = new Microsoft.Data.SqlClient.SqlConnection(_connectionString))
{
connection.Open();
@@ -186,7 +186,7 @@ namespace HorseRacingPredictor.Football.Database
ORDER BY
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);
}
@@ -1,6 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -1,6 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using System;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using System.Linq;
using Newtonsoft.Json.Linq;
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using System;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
@@ -8,7 +8,7 @@ using RestSharp;
using System.Text.Json;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;
namespace HorseRacingPredictor.Football
{
@@ -276,7 +276,7 @@ namespace HorseRacingPredictor.Football
}
/// <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>
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"]))
{
int? fixtureId = null;
@@ -572,8 +572,8 @@ namespace HorseRacingPredictor.Football
{
try
{
// In questo metodo non elaboriamo più direttamente le risposte
// Le risposte sono già state salvate nel database dalla classe API
// In questo metodo non elaboriamo più direttamente le risposte
// Le risposte sono già state salvate nel database dalla classe API
// e verranno elaborate dal metodo ProcessUnprocessedApiResponses
// Processa le risposte non elaborate
@@ -608,7 +608,7 @@ namespace HorseRacingPredictor.Football
dataTable.Columns.Add("Quota Trasferta", typeof(string));
dataTable.Columns.Add("Over 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("Doppia Casa/X", typeof(string));
dataTable.Columns.Add("Doppia Casa/Trasf", typeof(string));
@@ -646,7 +646,7 @@ namespace HorseRacingPredictor.Football
{
try
{
// Verifica che le proprietà essenziali esistano
// Verifica che le proprietà essenziali esistano
if (!item.TryGetProperty("fixture", out var fixtureEl) ||
!item.TryGetProperty("league", out var leagueEl) ||
!item.TryGetProperty("teams", out var teamsEl))
@@ -700,7 +700,7 @@ namespace HorseRacingPredictor.Football
row["Quota Trasferta"] = DBNull.Value;
row["Over 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["Doppia Casa/X"] = DBNull.Value;
row["Doppia Casa/Trasf"] = DBNull.Value;
@@ -731,7 +731,7 @@ namespace HorseRacingPredictor.Football
// Crea una copia del DataTable delle partite
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)
{
return combinedTable;
@@ -868,7 +868,7 @@ namespace HorseRacingPredictor.Football
var table = new DataTable();
table.Columns.Add("Errore", typeof(string));
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);
return table;
}
@@ -972,7 +972,7 @@ namespace HorseRacingPredictor.Football
{
string val = GetOddValueString(v, "value");
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;
}
break;
@@ -1079,7 +1079,7 @@ namespace HorseRacingPredictor.Football
var result = CreateEmptyFixturesDataTable();
// 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();
}
catch (Exception ex)
@@ -9,12 +9,12 @@ namespace HorseRacingPredictor.Football.Manager
{
internal class API : HorseRacingPredictor.Manager.API
{
// Configurazione dell'API
protected const string ApiKey = "f3795ccef056c5478d316162517d9970";
protected const string KeyHeader = "x-rapidapi-key";
// Configurazione dell'API caricata da appsettings.json
protected static string ApiKey => AppConfig.FootballApiKey;
protected static string KeyHeader => AppConfig.FootballApiKeyHeader;
protected const string HostHeader = "x-rapidapi-host";
protected const string HostValue = "v3.football.api-sports.io";
protected const string BaseUrl = "https://v3.football.api-sports.io";
protected static string HostValue => AppConfig.FootballApiHost;
protected static string BaseUrl => $"https://{AppConfig.FootballApiHost}";
// Repository per le risposte API
private readonly APIResponse _apiResponseRepository;
@@ -1,5 +1,5 @@
using System;
using System.Data.SqlClient;
using System;
using Microsoft.Data.SqlClient;
using System.Text.Json.Nodes;
using HorseRacingPredictor.Football.Database;
@@ -7,8 +7,7 @@ namespace HorseRacingPredictor.Football.Manager
{
internal class Database : HorseRacingPredictor.Manager.Database
{
// Implementazione della proprietà astratta _connectionString per il database calcio
protected override string _connectionString => "Server=DESKTOP-9O9JHFS;Database=TestBS_Football;User Id=sa;Password=Asti2019;";
public Database() : base(AppConfig.FootballConnectionString) { }
// Usato il modificatore "new" per evitare il warning CS0108
protected new void LogError(string operation, Exception ex)
@@ -28,11 +27,11 @@ namespace HorseRacingPredictor.Football.Manager
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)
{
// Il codice è stato spostato in Football.Main
LogError("l'elaborazione dei dati calcistici", new NotImplementedException("Questo metodo è 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"));
}
/// <summary>
@@ -31,7 +31,9 @@ namespace HorseRacingPredictor.HorseRacing.API
/// Esegue una richiesta GET autenticata con X-API-Key header.
/// Rispetta un intervallo minimo tra richieste e gestisce HTTP 429 con retry.
/// </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();
@@ -67,7 +69,7 @@ namespace HorseRacingPredictor.HorseRacing.API
{
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)
{
System.Diagnostics.Debug.WriteLine(
@@ -81,6 +83,10 @@ namespace HorseRacingPredictor.HorseRacing.API
continue;
}
// 404 Not Found - restituisci null se richiesto
if (response.StatusCode == HttpStatusCode.NotFound && !throwOnNotFound)
return null;
if (!response.IsSuccessful)
{
throw new Exception(
@@ -98,10 +104,10 @@ namespace HorseRacingPredictor.HorseRacing.API
}
/// <summary>
/// Ottiene l'elenco dei meeting per una data
/// Ottiene l'elenco dei meeting per una data (solo meeting australiani).
/// </summary>
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?");
sb.Append($"date={date:yyyy-MM-dd}");
@@ -114,11 +120,38 @@ namespace HorseRacingPredictor.HorseRacing.API
}
/// <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>
public RestResponse GetRaceForm(DateTime date, string track, int raceNumber,
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?");
sb.Append($"date={date:yyyy-MM-dd}");
@@ -130,16 +163,7 @@ namespace HorseRacingPredictor.HorseRacing.API
sb.Append($"&country={country}");
if (!string.IsNullOrEmpty(timezone))
sb.Append($"&timezone={Uri.EscapeDataString(timezone)}");
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);
return sb.ToString();
}
}
}
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.Json;
using System.Threading;
using HorseRacingPredictor.HorseRacing.API;
@@ -10,30 +11,66 @@ namespace HorseRacingPredictor.HorseRacing
/// <summary>
/// Gestore centralizzato per la sezione Corse dei Cavalli.
/// 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>
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 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)
{
_client = new RacingApiClient(apiKey);
}
/// <summary>
/// Aggiorna la API key
/// </summary>
public void UpdateApiKey(string apiKey)
{
_client = new RacingApiClient(apiKey);
_venuesCache = null;
}
/// <summary>
/// Scarica tutti i meeting per una data, poi per ciascun meeting scarica tutte le corse
/// e le restituisce come DataTable con una riga per runner.
/// La progress bar avanza per singola corsa scaricata.
/// Scarica tutte le corse per una data.
/// - Per AU: usa /form/meetings (efficiente, restituisce numero corse)
/// - Per NZ: usa probing venue per venue
/// </summary>
public DataTable GetAllRacesForDate(DateTime date, string raceCode = "gallops",
public DataTable GetAllRacesForDate(DateTime date,
IProgress<int> progress = null, IProgress<string> status = null,
CancellationToken ct = default)
{
@@ -41,75 +78,226 @@ namespace HorseRacingPredictor.HorseRacing
try
{
status?.Report("Connessione a FormFav API...");
progress?.Report(2);
// Filtra solo nazioni supportate
var requestedCountries = Countries
.Select(c => c.ToLowerInvariant())
.Where(c => SupportedCountries.Contains(c))
.Distinct()
.ToList();
// 1. Ottieni l'elenco dei meeting per la data
var meetingsResp = _client.GetMeetings(date, raceCode, ct: ct);
progress?.Report(8);
var meetings = ParseMeetings(meetingsResp.Content);
if (meetings.Count == 0)
if (requestedCountries.Count == 0)
{
status?.Report("Nessun meeting trovato per questa data");
status?.Report("Nessuna nazione supportata selezionata. Usa: AU, NZ");
progress?.Report(100);
return dt;
}
// 2. Calcola il totale corse per un progresso granulare
int totalRaces = 0;
foreach (var m in meetings)
if (!m.Abandoned) totalRaces += m.NumberOfRaces;
bool doAu = requestedCountries.Contains("au");
bool doNz = requestedCountries.Contains("nz");
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");
progress?.Report(100);
return dt;
}
status?.Report("AU: Recupero elenco meeting...");
progress?.Report(2);
status?.Report($"Trovati {meetings.Count} meeting ({totalRaces} corse). Scaricamento...");
int completedRaces = 0;
int errors = 0;
int phaseBase = 0;
int phaseSpan = doNz ? 50 : 95; // Se c'e' anche NZ, AU occupa 0-50%
// 3. Scarica ogni singola corsa
foreach (var meeting in meetings)
{
ct.ThrowIfCancellationRequested();
if (meeting.Abandoned) continue;
for (int raceNum = 1; raceNum <= meeting.NumberOfRaces; raceNum++)
try
{
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} " +
$"({completedRaces + 1}/{totalRaces})");
// Calcola totale corse AU
int totalAuRaces = 0;
foreach (var m in meetings)
if (!m.Abandoned) totalAuRaces += m.NumberOfRaces;
var formResp = _client.GetRaceForm(date, meeting.TrackSlug, raceNum,
raceCode, meeting.Country, ct: ct);
status?.Report($"AU: {meetings.Count} meeting ({totalAuRaces} corse)");
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; }
catch (Exception ex)
else
{
errors++;
System.Diagnostics.Debug.WriteLine(
$"Errore scaricamento {meeting.Track} R{raceNum}: {ex.Message}");
status?.Report("AU: Nessun meeting trovato");
}
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);
string errMsg = errors > 0 ? $" ({errors} errori)" : "";
status?.Report($"Trovati {dt.Rows.Count} corridori in {meetings.Count} meeting{errMsg}");
string errMsg = totalErrors > 0 ? $" ({totalErrors} errori)" : "";
string countries = string.Join("+", requestedCountries.Select(c => c.ToUpper()));
status?.Report($"{countries}: {dt.Rows.Count} corridori in {totalMeetings} meeting{errMsg}");
return dt;
}
catch (OperationCanceledException)
@@ -124,55 +312,85 @@ namespace HorseRacingPredictor.HorseRacing
}
}
#region DataTable creation
#region Venues
private DataTable CreateRunnerTable()
private class VenueInfo
{
var dt = new DataTable();
// Campi corsa
dt.Columns.Add("Ippodromo", 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));
// Campi corridore
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("Età", 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));
// Statistiche overall
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));
// Statistiche pista
dt.Columns.Add("Pista V/P/S", typeof(string));
// Statistiche distanza
dt.Columns.Add("Dist. V/P/S", typeof(string));
// Statistiche condizione
dt.Columns.Add("Cond. V/P/S", typeof(string));
// Stato
dt.Columns.Add("Ritirato", typeof(string));
return dt;
public string Name { get; set; }
public string Slug { get; set; }
public string Country { get; set; }
}
private class ActiveVenue
{
public string Name { get; set; }
public string Slug { get; set; }
public string Country { get; set; }
public string FirstRaceContent { get; set; }
}
private List<VenueInfo> GetFilteredVenues(string country, CancellationToken ct)
{
if (_venuesCache == null)
_venuesCache = ParseVenues(_client.GetVenues(ct).Content);
return _venuesCache
.Where(v => string.Equals(v.Country, country, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrEmpty(v.Name))
.OrderBy(v => v.Name)
.ToList();
}
private static List<VenueInfo> ParseVenues(string json)
{
var venues = new List<VenueInfo>();
if (string.IsNullOrEmpty(json)) return venues;
try
{
using (var doc = JsonDocument.Parse(json))
{
var root = doc.RootElement;
JsonElement arr;
if (root.TryGetProperty("venues", out var venuesEl) &&
venuesEl.ValueKind == JsonValueKind.Array)
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
#region JSON Parsing
#region Meetings parsing (AU)
private class MeetingInfo
{
@@ -242,7 +460,53 @@ namespace HorseRacingPredictor.HorseRacing
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;
@@ -253,6 +517,7 @@ namespace HorseRacingPredictor.HorseRacing
var root = doc.RootElement;
string track = GetString(root, "track", "");
string country = GetString(root, "country", fallbackCountry);
int raceNumber = GetInt(root, "raceNumber");
string raceName = GetString(root, "raceName", "");
string distance = GetString(root, "distance", "");
@@ -263,21 +528,11 @@ namespace HorseRacingPredictor.HorseRacing
string startTime = GetString(root, "startTime", "");
int numberOfRunners = GetInt(root, "numberOfRunners");
// Formatta orario se è un ISO datetime
string orario = "";
if (!string.IsNullOrEmpty(startTime))
{
try
{
var dto = DateTimeOffset.Parse(startTime);
var romeTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
orario = TimeZoneInfo.ConvertTime(dto, romeTz).ToString("HH:mm");
}
catch
{
orario = startTime;
}
}
string orario = FormatStartTime(startTime);
string countryDisplay = country.ToUpperInvariant();
if (CountryNames.TryGetValue(country.ToLowerInvariant(), out var cn))
countryDisplay = cn;
if (!root.TryGetProperty("runners", out var runnersEl) ||
runnersEl.ValueKind != JsonValueKind.Array)
@@ -289,8 +544,8 @@ namespace HorseRacingPredictor.HorseRacing
{
var row = dt.NewRow();
// Campi corsa
row["Ippodromo"] = track;
row["Paese"] = countryDisplay;
row["Corsa N."] = raceNumber;
row["Nome Corsa"] = raceName;
row["Orario"] = orario;
@@ -301,7 +556,6 @@ namespace HorseRacingPredictor.HorseRacing
row["Premio"] = prizeMoney;
row["N. Corridori"] = numberOfRunners;
// Campi corridore
row["Num"] = GetInt(runner, "number");
row["Cavallo"] = GetString(runner, "name", "");
row["Fantino"] = GetString(runner, "jockey", "");
@@ -315,7 +569,7 @@ namespace HorseRacingPredictor.HorseRacing
row["Box"] = GetInt(runner, "barrier") > 0
? GetInt(runner, "barrier").ToString()
: GetString(runner, "barrier", "");
row["Età"] = GetInt(runner, "age") > 0
row["Eta'"] = GetInt(runner, "age") > 0
? GetInt(runner, "age").ToString()
: GetString(runner, "age", "");
row["Forma"] = GetString(runner, "form", "");
@@ -323,21 +577,20 @@ namespace HorseRacingPredictor.HorseRacing
row["Colori"] = GetString(runner, "racingColours", "");
row["Cambio Equip."] = GetString(runner, "gearChange", "");
// Statistiche overall
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["Dist. V/P/S"] = FormatStatSummary(statsEl, "distance");
row["Cond. V/P/S"] = FormatStatSummary(statsEl, "condition");
}
// Ritirato
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;
row["Ritirato"] = scratched ? "Sì" : "";
row["Ritirato"] = scratched ? "Si" : "";
dt.Rows.Add(row);
}
@@ -348,9 +601,30 @@ namespace HorseRacingPredictor.HorseRacing
catch { }
}
/// <summary>
/// Estrae wins, places, starts, winPercent, placePercent da un sotto-oggetto stats
/// </summary>
private static string FormatStartTime(string startTime)
{
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,
DataRow row, string winsCol, string placesCol, string startsCol,
string winPctCol, string placePctCol)
@@ -370,9 +644,6 @@ namespace HorseRacingPredictor.HorseRacing
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)
{
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.Data.SqlClient;
using Microsoft.Data.SqlClient;
using System.Data;
using System.IO;
using BettingPredictor;
@@ -10,16 +10,14 @@ namespace HorseRacingPredictor.Horses
{
internal class Database : HorseRacingPredictor.Manager.Database
{
// Implementazione della proprietà astratta _connectionString per il database cavalli
protected override string _connectionString => "Server=DESKTOP-9O9JHFS;Database=TestBS_Horses;User Id=sa;Password=Asti2019;";
private readonly FileReader fileReaderHorses;
public Database()
// Connection string caricata da appsettings.json
public Database() : base(AppConfig.HorsesConnectionString)
{
fileReaderHorses = new FileReader();
}
private readonly FileReader fileReaderHorses;
public DataTable GetAllHorseRaceData()
{
DataTable horseRaceData = new DataTable();
@@ -644,11 +644,6 @@
</StackPanel>
<StackPanel Orientation="Horizontal">
<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"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
@@ -792,6 +787,73 @@
<TextBox x:Name="txtRacingApiKey" Style="{StaticResource FlatTb}" MaxWidth="510" HorizontalAlignment="Left"/>
<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>
<ColumnDefinition Width="245"/>
<ColumnDefinition Width="22"/>
@@ -32,6 +32,7 @@ namespace HorseRacingPredictor
InitializeComponent();
_footballManager = new Football.Main();
_racingManager = new HorseRacing.Main(DefaultRacingApiKey);
BuildCountryCheckboxes();
// Wire preview update events
txtFbPrefix.TextChanged += (s, e) => UpdateFbPreview();
txtFbSuffix.TextChanged += (s, e) => UpdateFbPreview();
@@ -334,13 +335,111 @@ namespace HorseRacingPredictor
// ???????????????????? 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)
{
// Toggle visibility of API vs CSV controls
if (dpRacing == null || btnDownloadRc == null || btnBrowseCsvRc == null) return;
bool isApi = rbRcApi.IsChecked == true;
dpRacing.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
if (cmbRaceCode != null) cmbRaceCode.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
btnDownloadRc.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
btnBrowseCsvRc.Visibility = isApi ? Visibility.Collapsed : Visibility.Visible;
}
@@ -528,20 +627,9 @@ namespace HorseRacingPredictor
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()
{
// Se è già in corso, annulla
// Se e' gia' in corso, annulla
if (_racingCts != null)
{
_racingCts.Cancel();
@@ -557,20 +645,21 @@ namespace HorseRacingPredictor
try
{
pbRacing.Value = 0;
lblStatusRc.Text = "Scaricamento corse da FormFav";
lblStatusRc.Text = "Scaricamento corse da FormFav...";
btnDownloadRc.Content = "Annulla";
dpRacing.IsEnabled = false;
cmbRaceCode.IsEnabled = false;
btnExportRcCsv.IsEnabled = false;
// Applica impostazioni correnti al manager
ApplyRacingSettings();
var progress = new Progress<int>(v => pbRacing.Value = v);
var status = new Progress<string>(s => lblStatusRc.Text = s);
var date = dpRacing.SelectedDate ?? DateTime.Today;
string raceCode = GetSelectedRaceCode();
var table = await Task.Run(() =>
_racingManager.GetAllRacesForDate(date, raceCode, progress, status, ct), ct);
_racingManager.GetAllRacesForDate(date, progress, status, ct), ct);
_racingData = table;
@@ -606,10 +695,22 @@ namespace HorseRacingPredictor
_racingCts = null;
btnDownloadRc.Content = "Scarica Corse";
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)
{
var rcDate = dpRacing.SelectedDate ?? DateTime.Today;
@@ -730,46 +831,36 @@ namespace HorseRacingPredictor
return null;
}
// ???????????????????? SETTINGS ????????????????????
private string SettingsFilePath =>
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.ini");
// —————————————————— SETTINGS ——————————————————
private void LoadSettings()
{
try
{
txtRacingApiKey.Text = DefaultRacingApiKey;
var s = UserSettings.Load();
if (!File.Exists(SettingsFilePath)) return;
foreach (var line in File.ReadAllLines(SettingsFilePath))
{
var idx = line.IndexOf('=');
if (idx < 0) continue;
var key = line.Substring(0, idx).Trim();
var val = line.Substring(idx + 1).Trim();
txtApiKey.Text = s.ApiKey;
txtFbExportPath.Text = s.FbExportPath;
txtFbPrefix.Text = s.FbPrefix;
txtFbSuffix.Text = s.FbSuffix;
chkFbIncludeDate.IsChecked = s.FbIncludeDate;
SetComboBoxSelectionByContent(cmbFbDateFormat, s.FbDateFormat);
SetComboBoxSelectionByContent(cmbFbFormat, s.FbFormat);
if (key == "ApiKey") txtApiKey.Text = val;
else if (key == "FbExportPath") txtFbExportPath.Text = val;
else if (key == "FbPrefix") txtFbPrefix.Text = val;
else if (key == "FbSuffix") txtFbSuffix.Text = val;
else if (key == "FbIncludeDate") chkFbIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase);
else if (key == "FbDateFormat") { try { SetComboBoxSelectionByContent(cmbFbDateFormat, val); } catch { } }
else if (key == "FbFormat") { try { SetComboBoxSelectionByContent(cmbFbFormat, val); } catch { } }
else if (key == "RcExportPath") txtRcExportPath.Text = val;
else if (key == "RcPrefix") txtRcPrefix.Text = val;
else if (key == "RcSuffix") txtRcSuffix.Text = val;
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;
}
txtRcExportPath.Text = s.RcExportPath;
txtRcPrefix.Text = s.RcPrefix;
txtRcSuffix.Text = s.RcSuffix;
chkRcIncludeDate.IsChecked = s.RcIncludeDate;
SetComboBoxSelectionByContent(cmbRcDateFormat, s.RcDateFormat);
SetComboBoxSelectionByContent(cmbRcFormat, s.RcFormat);
txtRacingApiKey.Text = string.IsNullOrEmpty(s.RacingApiKey) ? DefaultRacingApiKey : s.RacingApiKey;
if (txtRcTimezone != null) txtRcTimezone.Text = s.RcTimezone;
SetSelectedCountries(s.RcCountries.ToArray());
// Update preview UI after loading values
UpdateFbPreview();
UpdateRcPreview();
_racingManager.UpdateApiKey(txtRacingApiKey.Text);
ApplyRacingSettings();
}
catch { }
}
@@ -1000,28 +1091,30 @@ namespace HorseRacingPredictor
{
try
{
var sb = new StringBuilder();
sb.AppendLine($"ApiKey={txtApiKey.Text.Trim()}");
sb.AppendLine($"FbExportPath={txtFbExportPath.Text.Trim()}");
sb.AppendLine($"FbPrefix={txtFbPrefix.Text.Trim()}");
sb.AppendLine($"FbSuffix={txtFbSuffix.Text.Trim()}");
sb.AppendLine($"FbIncludeDate={(chkFbIncludeDate.IsChecked==true?"1":"0")}");
sb.AppendLine($"FbDateFormat={(cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}");
sb.AppendLine($"FbFormat={(cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}");
sb.AppendLine($"RcExportPath={txtRcExportPath.Text.Trim()}");
sb.AppendLine($"RcPrefix={txtRcPrefix.Text.Trim()}");
sb.AppendLine($"RcSuffix={txtRcSuffix.Text.Trim()}");
sb.AppendLine($"RcIncludeDate={(chkRcIncludeDate.IsChecked==true?"1":"0")}");
sb.AppendLine($"RcDateFormat={(cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}");
sb.AppendLine($"RcFormat={(cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}");
sb.AppendLine($"RacingApiKey={txtRacingApiKey.Text.Trim()}");
File.WriteAllText(SettingsFilePath, sb.ToString(), Encoding.UTF8);
var s = new UserSettings
{
ApiKey = txtApiKey.Text.Trim(),
FbExportPath = txtFbExportPath.Text.Trim(),
FbPrefix = txtFbPrefix.Text.Trim(),
FbSuffix = txtFbSuffix.Text.Trim(),
FbIncludeDate = chkFbIncludeDate.IsChecked == true,
FbDateFormat = (cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
FbFormat = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
RcExportPath = txtRcExportPath.Text.Trim(),
RcPrefix = txtRcPrefix.Text.Trim(),
RcSuffix = txtRcSuffix.Text.Trim(),
RcIncludeDate = chkRcIncludeDate.IsChecked == true,
RcDateFormat = (cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
RcFormat = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
RacingApiKey = txtRacingApiKey.Text.Trim(),
RcTimezone = txtRcTimezone?.Text?.Trim() ?? "Australia/Sydney",
RcCountries = new List<string>(GetSelectedCountries())
};
s.Save();
// update previews after save
UpdateFbPreview();
UpdateRcPreview();
_racingManager.UpdateApiKey(txtRacingApiKey.Text.Trim());
ApplyRacingSettings();
MessageBox.Show("Impostazioni salvate con successo.",
"Salvato", MessageBoxButton.OK, MessageBoxImage.Information);
@@ -1,12 +1,16 @@
using System;
using System.Data.SqlClient;
using System;
using Microsoft.Data.SqlClient;
namespace HorseRacingPredictor.Manager
{
internal abstract class Database
{
// La stringa di connessione viene rimossa da qui e definita nelle classi derivate
protected abstract string _connectionString { get; }
protected readonly string _connectionString;
protected Database(string connectionString)
{
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
}
protected SqlConnection GetConnection()
{
@@ -69,7 +73,7 @@ namespace HorseRacingPredictor.Manager
}
/// <summary>
/// Metodo per verificare se la connessione al database è valida
/// Metodo per verificare se la connessione al database è valida
/// </summary>
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>