#:package MySqlConnector@2.3.7 #:package CsvHelper@33.0.1 using System.Globalization; using CsvHelper; using CsvHelper.Configuration; using MySqlConnector; // ── Config ──────────────────────────────────────────────────────────────────── const string Dsn = "Server=127.0.0.1;Port=13306;Database=ewc2025;Uid=root;Pwd=ewc2025root;"; static string FindDataDir() { var dir = Directory.GetCurrentDirectory(); while (dir is not null) { var candidate = Path.Combine(dir, "data"); if (Directory.Exists(candidate) && Directory.GetFiles(candidate, "01_EWC2025*").Length > 0) return candidate; dir = Path.GetDirectoryName(dir); } throw new DirectoryNotFoundException("Cannot find 'data' directory with EWC2025 CSV files."); } var dataDir = FindDataDir(); // Games_Competing and Games_Won in the CSVs use abbreviated names that don't // always match the canonical name stored in the game table. var gameAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Mobile Legends"] = "Mobile Legends: Bang Bang", ["PUBG"] = "PUBG: Battlegrounds", ["Mobile Legends: Bang Bang - Men"] = "Mobile Legends: Bang Bang", ["Mobile Legends: Bang Bang - Women"] = "Mobile Legends: Bang Bang", ["Naraka: Bladepoint - Solo"] = "Naraka: Bladepoint", ["Naraka: Bladepoint - Trios"] = "Naraka: Bladepoint", }; // ── Helpers ─────────────────────────────────────────────────────────────────── await using var conn = new MySqlConnection(Dsn); await conn.OpenAsync(); Console.WriteLine("Connected to MySQL."); List> ReadCsv(string filename) { using var reader = new StreamReader(Path.Combine(dataDir, filename)); using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture) { MissingFieldFound = null, HeaderValidated = null, }); var rows = new List>(); csv.Read(); csv.ReadHeader(); while (csv.Read()) { var row = new Dictionary(); foreach (var h in csv.HeaderRecord!) row[h] = csv.GetField(h) ?? ""; rows.Add(row); } return rows; } async Task Insert(string sql, Dictionary p) { await using var cmd = new MySqlCommand(sql, conn); foreach (var (k, v) in p) cmd.Parameters.AddWithValue(k, v ?? DBNull.Value); await cmd.ExecuteNonQueryAsync(); return cmd.LastInsertedId; } static string? Nullable(string s) => string.IsNullOrWhiteSpace(s) ? null : s.Trim(); static int? NullInt(string s) => int.TryParse(s.Trim(), out var v) ? v : null; static decimal? NullDec(string s) => decimal.TryParse(s.Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out var v) ? v : null; static bool YesNo(string s) => s.Trim().Equals("Yes", StringComparison.OrdinalIgnoreCase); // Strip gendered/variant suffix to get the canonical game name used in file 01 string CanonicalGame(string name) { if (gameAliases.TryGetValue(name.Trim(), out var mapped)) return mapped; return name.Trim(); } // ── Load CSVs ───────────────────────────────────────────────────────────────── var rows01 = ReadCsv("01_EWC2025_Event_Tournament_Summary.csv"); var rows02 = ReadCsv("02_EWC2025_Medalists.csv"); var rows03 = ReadCsv("03_EWC2025_Club_Championship_Standings.csv"); var rows04 = ReadCsv("04_EWC2025_Club_Partner_Program.csv"); var rows05 = ReadCsv("05_EWC2025_Player_Roster.csv"); var rows06 = ReadCsv("06_EWC2025_Prize_Pool_Distribution.csv"); var rows07 = ReadCsv("07_EWC2025_Calendar_Schedule.csv"); var rows08 = ReadCsv("08_EWC2025_Country_Results.csv"); var rows09 = ReadCsv("09_EWC2025_Point_System.csv"); var rows10 = ReadCsv("10_EWC2025_Game_by_Game_Results.csv"); // ── 1. game ─────────────────────────────────────────────────────────────────── Console.WriteLine("[1/13] game"); var gameId = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var r in rows01) { var name = r["Game"].Trim(); if (gameId.ContainsKey(name)) continue; gameId[name] = await Insert( "INSERT INTO game (name, game_type, platform) VALUES (@n, @gt, @pl)", new() { ["@n"] = name, ["@gt"] = r["Game_Type"].Trim(), ["@pl"] = r["Platform"].Trim() }); } // ── 2. country ──────────────────────────────────────────────────────────────── Console.WriteLine("[2/13] country"); var countryId = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var r in rows08) { var name = r["Country"].Trim(); countryId[name] = await Insert( """ INSERT INTO country (name, region, gold_medals, silver_medals, bronze_medals, total_medals, total_players, top_game) VALUES (@n, @reg, @g, @s, @b, @t, @pl, @tg) """, new() { ["@n"] = name, ["@reg"] = r["Region"].Trim(), ["@g"] = int.Parse(r["Gold_Medals"]), ["@s"] = int.Parse(r["Silver_Medals"]), ["@b"] = int.Parse(r["Bronze_Medals"]), ["@t"] = int.Parse(r["Total_Medals"]), ["@pl"] = int.Parse(r["Total_Players"]), ["@tg"] = Nullable(r["Top_Game"]), }); } async Task EnsureCountry(string name) { if (countryId.TryGetValue(name, out var id)) return id; id = await Insert( "INSERT INTO country (name, region) VALUES (@n, 'Unknown')", new() { ["@n"] = name }); countryId[name] = id; return id; } // ── 3. point_system ─────────────────────────────────────────────────────────── Console.WriteLine("[3/13] point_system"); foreach (var r in rows09) { await Insert( "INSERT INTO point_system (placement, points, system_type, description) VALUES (@pl, @pts, @st, @desc)", new() { ["@pl"] = int.Parse(r["Placement"]), ["@pts"] = int.Parse(r["Points"]), ["@st"] = r["System_Type"].Trim(), ["@desc"] = Nullable(r["Description"]), }); } // ── 4. prize_pool_category ──────────────────────────────────────────────────── Console.WriteLine("[4/13] prize_pool_category"); foreach (var r in rows06) { await Insert( "INSERT INTO prize_pool_category (category, amount_usd, percentage, description, num_recipients) VALUES (@c, @a, @p, @d, @n)", new() { ["@c"] = r["Category"].Trim(), ["@a"] = int.Parse(r["Amount_USD"]), ["@p"] = decimal.Parse(r["Percentage"], CultureInfo.InvariantCulture), ["@d"] = Nullable(r["Description"]), ["@n"] = NullInt(r["Num_Recipients"]), }); } // ── 5. organization ─────────────────────────────────────────────────────────── Console.WriteLine("[5/13] organization"); var orgId = new Dictionary(StringComparer.OrdinalIgnoreCase); var partnerDetails = rows04.ToDictionary(r => r["Organization"].Trim(), StringComparer.OrdinalIgnoreCase); var allOrgs = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var r in rows04) allOrgs.Add(r["Organization"].Trim()); foreach (var r in rows03) allOrgs.Add(r["Organization"].Trim()); foreach (var r in rows02) allOrgs.Add(r["Team_Organization"].Trim()); foreach (var r in rows05) { if (!string.IsNullOrWhiteSpace(r["Team"])) allOrgs.Add(r["Team"].Trim()); } foreach (var r in rows10) { allOrgs.Add(r["Team_1"].Trim()); allOrgs.Add(r["Team_2"].Trim()); if (!string.IsNullOrWhiteSpace(r["Winner"])) allOrgs.Add(r["Winner"].Trim()); } foreach (var name in allOrgs.Where(n => !string.IsNullOrWhiteSpace(n))) { long? cid = null; string status = "None"; string? region = null, hq = null, ceo = null; bool? top8 = null; int? founded = null; decimal? followers = null; if (partnerDetails.TryGetValue(name, out var p)) { var cname = p["Country"].Trim(); if (!string.IsNullOrWhiteSpace(cname)) cid = await EnsureCountry(cname); status = p["Club_Partner_Status"].Trim(); region = Nullable(p["Region"]); top8 = YesNo(p["Top_8_2024"]); founded = NullInt(p["Founded"]); hq = Nullable(p["HQ_Location"]); ceo = Nullable(p["CEO"]); followers = NullDec(p["Social_Media_Followers_M"]); } orgId[name] = await Insert( """ INSERT INTO organization (name, region, country_id, club_partner_status, top_8_2024, founded_year, hq_location, ceo, social_media_followers_m) VALUES (@n, @reg, @cid, @st, @t8, @fy, @hq, @ceo, @fol) """, new() { ["@n"] = name, ["@reg"] = region, ["@cid"] = cid, ["@st"] = status, ["@t8"] = top8, ["@fy"] = founded, ["@hq"] = hq, ["@ceo"] = ceo, ["@fol"] = followers, }); } // ── 6. tournament ───────────────────────────────────────────────────────────── Console.WriteLine("[6/13] tournament"); // Key: "GameName|StartDate" — unique because MLBB Men/Women have different start dates var tournamentKey = new Dictionary(); foreach (var r in rows01) { var gameName = r["Game"].Trim(); var gender = r["Gender"].Trim() switch { "Men" => "Men", "Women" => "Women", _ => "Open" }; var id = await Insert( """ INSERT INTO tournament (game_id, event_name, start_date, end_date, prize_pool_usd, num_participants, winner, runner_up, club_championship_points, gender) VALUES (@gid, @en, @sd, @ed, @prize, @num, @w, @ru, @ccp, @gen) """, new() { ["@gid"] = gameId[gameName], ["@en"] = r["Event_Name"].Trim(), ["@sd"] = r["Start_Date"].Trim(), ["@ed"] = r["End_Date"].Trim(), ["@prize"] = int.Parse(r["Prize_Pool_USD"]), ["@num"] = int.Parse(r["Num_Participants"]), ["@w"] = Nullable(r["Winner"]), ["@ru"] = Nullable(r["Runner_Up"]), ["@ccp"] = YesNo(r["Club_Championship_Points"]), ["@gen"] = gender, }); tournamentKey[$"{gameName}|{r["Start_Date"].Trim()}"] = id; } // Unified lookup: given the raw game name from files 02/07/10 (may include suffixes), // return the matching tournament_id. long? ResolveTournament(string rawGame) { var canonical = CanonicalGame(rawGame); // Try direct match via schedule rows (which share the same naming as files 02/10) foreach (var r in rows07) { if (!r["Game"].Trim().Equals(rawGame, StringComparison.OrdinalIgnoreCase)) continue; var key = $"{canonical}|{r["Start_Date"].Trim()}"; if (tournamentKey.TryGetValue(key, out var tid)) return tid; } // Fallback: first tournament whose game name matches canonical foreach (var (k, v) in tournamentKey) if (k.StartsWith($"{canonical}|", StringComparison.OrdinalIgnoreCase)) return v; return null; } // ── 7. schedule ─────────────────────────────────────────────────────────────── Console.WriteLine("[7/13] schedule"); foreach (var r in rows07) { var tid = ResolveTournament(r["Game"].Trim()); if (tid is null) { Console.WriteLine($" WARN: no tournament for schedule row '{r["Game"]}'"); continue; } await Insert( "INSERT INTO schedule (tournament_id, week_number, venue, time_zone, duration_days) VALUES (@tid, @w, @v, @tz, @dur)", new() { ["@tid"] = tid.Value, ["@w"] = int.Parse(r["Week"]), ["@v"] = r["Venue"].Trim(), ["@tz"] = r["Time_Zone"].Trim(), ["@dur"] = int.Parse(r["Duration_Days"]), }); } // ── 8. player ───────────────────────────────────────────────────────────────── Console.WriteLine("[8/13] player"); foreach (var r in rows05) { var cname = r["Country"].Trim(); var gname = r["Game"].Trim(); var tname = r["Team"].Trim(); await Insert( """ INSERT INTO player (player_id, player_name, country_id, region, organization_id, game_id, role, age, experience_years, previous_team, tournament_place, prize_earned_usd, social_media_followers_k) VALUES (@pid, @pn, @cid, @reg, @oid, @gid, @role, @age, @exp, @prev, @place, @prize, @soc) """, new() { ["@pid"] = r["Player_ID"].Trim(), ["@pn"] = r["Player_Name"].Trim(), ["@cid"] = string.IsNullOrWhiteSpace(cname) ? null : (long?)await EnsureCountry(cname), ["@reg"] = Nullable(r["Region"]), ["@oid"] = orgId.TryGetValue(tname, out var oid) ? oid : null, ["@gid"] = gameId.TryGetValue(gname, out var gid) ? gid : null, ["@role"] = Nullable(r["Role"]), ["@age"] = NullInt(r["Age"]), ["@exp"] = NullInt(r["Experience_Years"]), ["@prev"] = Nullable(r["Previous_Team"]), ["@place"] = NullInt(r["Tournament_Place"]), ["@prize"] = NullInt(r["Prize_Earned_USD"]) ?? 0, ["@soc"] = NullInt(r["Social_Media_Followers_K"]) ?? 0, }); } // ── 9. medalist ─────────────────────────────────────────────────────────────── Console.WriteLine("[9/13] medalist"); foreach (var r in rows02) { var tid = ResolveTournament(r["Event"].Trim()); if (tid is null) { Console.WriteLine($" WARN: no tournament for medalist event '{r["Event"]}'"); continue; } var orgName = r["Team_Organization"].Trim(); var cname = r["Country"].Trim(); await Insert( "INSERT INTO medalist (tournament_id, medal, organization_id, player_name, country_id, role) VALUES (@tid, @m, @oid, @pn, @cid, @role)", new() { ["@tid"] = tid.Value, ["@m"] = r["Medal"].Trim(), ["@oid"] = orgId.TryGetValue(orgName, out var oid) ? oid : null, ["@pn"] = r["Player"].Trim(), ["@cid"] = string.IsNullOrWhiteSpace(cname) ? null : (long?)await EnsureCountry(cname), ["@role"] = Nullable(r["Role"]), }); } // ── 10. match_result ────────────────────────────────────────────────────────── Console.WriteLine("[10/13] match_result"); foreach (var r in rows10) { var tid = ResolveTournament(r["Game"].Trim()); if (tid is null) { Console.WriteLine($" WARN: no tournament for match '{r["Game"]}'"); continue; } await Insert( """ INSERT INTO match_result (tournament_id, match_type, team_1, team_2, winner, score, map, duration_minutes, mvp) VALUES (@tid, @mt, @t1, @t2, @w, @sc, @map, @dur, @mvp) """, new() { ["@tid"] = tid.Value, ["@mt"] = r["Match_Type"].Trim(), ["@t1"] = r["Team_1"].Trim(), ["@t2"] = r["Team_2"].Trim(), ["@w"] = Nullable(r["Winner"]), ["@sc"] = Nullable(r["Score"]), ["@map"] = Nullable(r["Map"]), ["@dur"] = NullInt(r["Duration_Minutes"]), ["@mvp"] = Nullable(r["MVP"]), }); } // ── 11. club_championship_standing ──────────────────────────────────────────── Console.WriteLine("[11/13] club_championship_standing"); foreach (var r in rows03) { var name = r["Organization"].Trim(); if (!orgId.TryGetValue(name, out var oid)) { Console.WriteLine($" WARN: org not found for standing '{name}'"); continue; } await Insert( """ INSERT INTO club_championship_standing (organization_id, `rank`, total_points, prize_money_usd, tournament_wins, top_8_finishes, eligible_to_win) VALUES (@oid, @rank, @pts, @prize, @wins, @top8, @elig) """, new() { ["@oid"] = oid, ["@rank"] = int.Parse(r["Rank"]), ["@pts"] = int.Parse(r["Total_Points"]), ["@prize"] = int.Parse(r["Prize_Money_USD"]), ["@wins"] = int.Parse(r["Tournament_Wins"]), ["@top8"] = int.Parse(r["Top_8_Finishes"]), ["@elig"] = YesNo(r["Eligible_to_Win"]), }); } // ── 12. organization_game_competing ─────────────────────────────────────────── Console.WriteLine("[12/13] organization_game_competing"); foreach (var r in rows04) { var name = r["Organization"].Trim(); if (!orgId.TryGetValue(name, out var oid)) continue; foreach (var raw in r["Games_Competing"].Trim('"').Split(',', StringSplitOptions.RemoveEmptyEntries)) { var canonical = CanonicalGame(raw.Trim()); if (!gameId.TryGetValue(canonical, out var gid)) { Console.WriteLine($" WARN: game '{raw.Trim()}' not found for org-competing '{name}'"); continue; } await Insert( "INSERT IGNORE INTO organization_game_competing (organization_id, game_id) VALUES (@oid, @gid)", new() { ["@oid"] = oid, ["@gid"] = gid }); } } // ── 13. organization_game_won ───────────────────────────────────────────────── Console.WriteLine("[13/13] organization_game_won"); foreach (var r in rows03) { var name = r["Organization"].Trim(); if (!orgId.TryGetValue(name, out var oid)) continue; var gamesWon = r["Games_Won"].Trim('"').Trim(); if (gamesWon.Equals("None", StringComparison.OrdinalIgnoreCase)) continue; foreach (var raw in gamesWon.Split(',', StringSplitOptions.RemoveEmptyEntries)) { var canonical = CanonicalGame(raw.Trim()); if (!gameId.TryGetValue(canonical, out var gid)) { Console.WriteLine($" WARN: game '{raw.Trim()}' not found for org-won '{name}'"); continue; } await Insert( "INSERT IGNORE INTO organization_game_won (organization_id, game_id) VALUES (@oid, @gid)", new() { ["@oid"] = oid, ["@gid"] = gid }); } } Console.WriteLine("Done. Database seeded.");