Saving C# WinForms Tournament Data to a Database — A Deep Dive with Tim Corey
In Lesson 19 of the “C# App From Start to Finish” series, Tim Corey walks through one of the most important milestones of the project: saving a fully built WinForms tournament to a database (and text files). WinForms is a framework for building Windows desktop applications, making it an ideal choice for demonstrating data persistence in a real-world context.
This article is based entirely on Lesson 19 – Create Tournament Form (Part 5) and follows Tim’s own step-by-step reasoning, debugging process, and coding patterns.
Rather than presenting theory, Tim walks through real application code, showing how models are saved, rehydrated, debugged, and corrected when things inevitably break. This makes the lesson especially valuable for understanding practical data persistence in WinForms.
Introduction
At the very beginning, Tim introduces Lesson 19 and explains that today is finally the day the Create Tournament form is completed. He acknowledges that this form has been one of the biggest so far, with many moving parts that can feel overwhelming. In this lesson, you will learn how to apply WinForms' event-driven model and rapid application development features to complete a real-world form.
Tim reassures viewers that the complexity has been broken into manageable pieces and that today’s focus is the final logic required to save tournament data—either to a SQL database or a text file—and then the feature is done. WinForms is ideal for rapid application development (RAD) and building internal business tools or lightweight utilities, and its relatively simple event-driven model and vast amount of online resources make it easy for new developers to learn.
Creating a New Project in Visual Studio
Getting started with a Windows Forms app is straightforward with Visual Studio. Begin by opening Visual Studio and selecting “Create a new project” from the start window. In the “Create a new project” dialog, use the search bar to type “Windows Forms App”—this quickly brings up the template you need. To narrow down your options, filter by C# as the language and Windows as the platform.
Once you’ve found the “Windows Forms App (.NET Framework)” project type, select it and click Next. In the “Configure your new project” window, give your project a name (for example, HelloWorld), choose a location, and then click Create. Visual Studio will generate a new solution and open your main form, which serves as the graphical user interface for your application. This form is where you’ll design your user interface and add controls, laying the foundation for your Windows desktop application. The template includes all the necessary files and references to the .NET Framework, so you can focus on building your app’s features and functionality right away.
Reviewing the Create Tournament Button Logic
Tim begins with a review. He explains that everything starts from the Create Tournament button in the program. In a C# WinForms program, event handlers are written to execute code in response to specific user actions, such as clicking the Create Tournament button. Up to this point, the application has already:
Validated user input
Built the tournament object
- Created rounds and matchups in memory
The missing piece, Tim explains, is persisting that data. Previously, the app could save tournaments, prizes, and entries—but round information was missing. That’s what this lesson fixes.
Saving the Tournament in SQL: The Big Picture
Tim navigates to the SQL connector’s CreateTournament method and reminds viewers that saving data follows a clear pattern:
Save the tournament itself
Get the tournament ID
Save prizes
Save entries
- Now: save rounds and matchups
Note: It is important to follow this save order to ensure that all related data is correctly associated and the application's data integrity is maintained.
Tim emphasizes that patterns are good, and this lesson follows the same pattern established earlier.
WinForms also supports the creation of reusable UI elements like buttons, text boxes, and labels, which helps maintain consistency in the application's data structure.
Understanding the Data Structure Complexity
Here, Tim pauses to explain why saving rounds is more complex.
A tournament has Rounds
Each round is a list of MatchupModel
- Each matchup contains MatchupEntryModel objects
Tim explains that this is a list of a list, and that complexity is normal at this stage. He encourages viewers not to panic—this is just a natural result of modeling real-world data.
He also notes that WinForms applications are built using components, such as data grids and UI controls, which help developers create feature-rich desktop applications efficiently. All visual elements in the Windows Forms class library derive from the Control class, providing a consistent foundation for building and customizing the user interface.
Why Save Order Matters
One of the most critical explanations in the video happens here.
Tim explains that data must be saved in order, because:
A matchup entry cannot be saved until the parent matchup ID exists
- A round cannot reference the next round until the previous one has IDs
He explains that because objects reference the same memory addresses, once the ID is populated in one place, all references update automatically. However, developers may need to modify object references as IDs are assigned, which is a typical part of the event-driven process in C# WinForms applications where the application responds to user actions and data changes.
Ensuring All Models Have IDs
Tim points out an important fix: MatchupEntryModel does not yet have an ID property.
Even if it’s not immediately needed, Tim adds it anyway, explaining that every database-backed model should have an ID for consistency and future use. By default, Windows Forms applications use files like Form1.Designer.cs to automatically generate UI code, ensuring standard structure and behavior in the application.
Breaking the Logic into Simple Steps
This section serves as a tutorial for saving data in a C WinForms application.
Tim uses one of his favorite teaching analogies: eating an elephant one bite at a time.
He breaks the saving logic into clear steps:
Loop through each round
Inside each round, loop through matchups
Save each matchup
Loop through matchup entries
- Save each entry
Each step is simple on its own—and together, they’re powerful.
The straightforward drag-and-drop visual designer in Visual Studio makes WinForms excellent for building internal tools, prototypes, and simple applications.
Looping Through Rounds and Matchups
Tim shows how to loop through a list of rounds, where each round itself is a list of MatchupModel.
He intentionally avoids var at first and explains why: explicit types help developers understand what they’re looping through, especially when working with nested lists. Clear code is especially important when working with different programming languages in Windows Forms development, as it improves maintainability and supports localization for multiple languages.
He also shares an important philosophy:
“The best code is code that a junior developer can understand.”
Windows Forms applications are event-driven and supported by Microsoft's .NET Framework.
Creating the Matchups Insert Stored Procedure
Tim switches to SQL and creates spMatchups_Insert.
He explains the fields clearly:
Tournament ID
Matchup Round
- Winner ID (nullable)
Additional fields, such as date, could be included to track when matchups occur.
Tim deliberately does not set a winner at this stage. Even if a matchup is a bye, he prefers to handle winners later using the same logic as real matches.
Windows Forms provides access to native Windows User Interface Common Controls by wrapping the existing Windows API in managed code.
Saving Matchup Entries and Handling NULLs
Tim creates another stored procedure: spMatchupEntries_Insert.
He explains:
Scores are NULL because games haven’t been played
NULL is not the same as zero
- TeamCompeting can be NULL in later rounds
He gives a memorable example using football and golf to explain why zero is a real value, while NULL means no value exists yet.
Application settings can be configured in your Windows Forms project to handle different data scenarios, such as how NULL values are displayed or processed in controls.
Windows Forms is included as part of Microsoft .NET, .NET Framework, or Mono.
Features and Functionality of a .NET Framework Application
A Windows Forms app built on the .NET Framework offers a powerful set of features for creating modern Windows desktop applications. With Windows Forms (WinForms), you have access to a rich graphical user interface (GUI) library that makes it easy to design intuitive and interactive user interfaces. WinForms is a free, open-source framework included with Microsoft .NET, .NET Framework, and Mono, providing seamless integration with native Windows controls and the Windows API through managed code.
The Visual Studio IDE enhances your development experience with tools like the Windows Forms Designer, which allows you to drag and drop common controls—such as buttons, text boxes, and labels—directly onto your form. This visual approach speeds up UI design and lets you customize properties and events using the Properties window. With built-in support for code editing, debugging, and project management, Visual Studio streamlines the process of building, testing, and refining your Windows desktop applications. Whether you’re creating business tools, utilities, or educational software, the .NET Framework and WinForms provide a robust platform for delivering feature-rich, responsive, and user-friendly applications.
Debugging with Edit and Continue
When an exception occurs, Tim doesn’t panic—he teaches.
He uses Visual Studio’s Edit and Continue feature to:
Pause execution
Insert null checks
- Resume execution without restarting the app
Keyboard shortcuts, such as using 'Ctrl' combinations (for example, 'Ctrl + Alt + X' to open the Toolbox), can speed up debugging and access to UI controls in Visual Studio.
He shows how to:
Check if TeamCompeting is null
Pass null to SQL instead of accessing .ID
- Apply the same logic to ParentMatchup
This is a real-world debugging workflow, exactly as Tim uses it.
Visual Studio offers a wide range of features and tools for C# development, including the Windows Forms Designer.
Verifying the Data in SQL
After fixing two small bugs, Tim queries the database.
He confirms:
Matchups are saved correctly
Round numbers are accurate
Bye logic works
- Parent-child relationships are correct
Notice the correct relationships and data in the database after saving, as this visual feedback is important for verifying that the application logic is functioning as intended.
Tim points out that only two errors occurred in a very complex feature—and credits that success to planning, patterns, and breaking work into small pieces.
Windows Forms provides a platform to write client applications for desktop, laptop, and tablet PCs, making it a versatile choice for Windows development.
Compiling and Preparing Lookup Methods
Tim begins by ensuring the project still compiles before tackling unfinished parts. At 1:17:08, he explains that the system will pass strings into methods and receive one or more MatchupEntryModel objects in return, pulled from text files.
At 1:17:40, Tim implements a method called LookupTeamById(int id). He explains that when data is stored in a text file, only the ID is saved, not the full object. Therefore, when loading data, the application must rehydrate that ID back into a complete TeamModel.
Tim points out that this is not new logic—he simply reuses the same pattern already used for loading all teams from the file. He emphasizes that this reuse is intentional and a core benefit of structuring code properly. Developers can also refer to existing methods and official documentation for additional guidance when implementing similar logic.
Windows Forms is seen as a replacement for the earlier and more complex C++ based Microsoft Foundation Class Library for GUI development.
Centralizing File Paths with GlobalConfig
At 1:20:27, Tim pauses and calls out a design problem: file names are being passed everywhere, even though they are constants. He openly says this feels “ridiculous.”
To fix this, Tim moves file-name constants into GlobalConfig, making them public. At 1:21:33, he explains that this avoids passing file paths through multiple layers of methods. The Solution Explorer in Visual Studio helps manage these project files and the overall structure, making it easier to locate and update such constants.
However, at 1:22:24, Tim is honest about a flaw: these constants now exist in two places—GlobalConfig and the TextConnector. He explicitly calls this a violation of DRY (Don’t Repeat Yourself) and notes that this should be refactored later, but today is not that day.
It’s also important to note that Windows Forms does not provide a default application framework like the Microsoft Foundation Class (MFC).
Converting Strings into Matchup Entry Models
At 1:24:37, Tim starts implementing ConvertStringToMatchupEntryModel. He explains that matchup entries are stored as pipe-delimited IDs, so the first step is splitting the string on the pipe (|). Unlike web applications that use a page as a primary UI container for navigation and layout, WinForms uses forms to organize the user interface.
By 1:25:17, he loops through each ID, looks it up from the matchup entry file, and adds the corresponding model to an output list. The pattern remains consistent:
load → convert → filter by ID → return model
Tim stresses that consistency makes debugging possible later.
Windows Forms applications can be developed using .NET programming languages such as C# or Visual Basic.
Building ConvertToMatchupEntryModels Extension
At 1:27:56, Tim creates an extension method ConvertToMatchupEntryModels(List< string>). To guide the process, he copies an existing conversion method (for PersonModel) and uses it purely as a pattern reference.
By 1:30:28, Tim maps columns explicitly:
Column 0 → ID
Column 1 → TeamCompeting ID
Column 2 → Score
- Column 3 → ParentMatchup ID
He explains that the stored value is just an ID, so the full object must be rebuilt using lookup methods. Some UI components and themes in WinForms are inspired by Microsoft Office, such as Office 2019 Colorful and Office 2019 Black, to provide a familiar user experience.
Third-party vendors like DevExpress offer comprehensive UI component suites for WinForms.
Handling Parent Matchups Safely
At 1:38:49, Tim identifies a major issue: parent matchups may not exist (especially in the first round). Calling First() on a missing ID would crash the app.
His fix at 1:39:09 uses int.TryParse. If parsing fails, ParentMatchup is set to null. Tim carefully explains that this is exactly what should happen for first-round matchups. New features and fixes are often included in each release of WinForms or third-party libraries, improving how such scenarios are handled.
He reinforces that crashes are acceptable only when invalid data should never exist, such as an invalid team ID.
The DevExpress WinForms Subscription includes over 190 UI controls and libraries, providing extensive options for building robust applications.
Saving Matchups and Entries to Files
At 1:44:00, Tim moves into saving data. He loads all matchups, determines the next available ID, assigns it, and then moves to saving matchup entries. After saving your data, you can test the application by clicking the Start button or pressing F5 in Visual Studio to run your WinForms application.
By 1:46:44, Tim highlights the power of reusable methods—loading and converting files is now “boring,” which he says is a good thing.
He saves matchup entries first so each entry gets an ID, then saves the matchup itself, which stores those entry IDs as pipe-delimited strings.
Additionally, the DevExpress WinForms Subscription includes a reporting suite with an end-user report designer, which can enhance your WinForms applications.
Writing Matchup Entries to the Database
At 1:49:43, Tim builds the save logic line-by-line. He carefully reverses the earlier conversion pattern:
ID
TeamCompeting ID (or empty string)
Score
- ParentMatchup ID (or empty string)
After explaining the save logic, it's important to note that WinForms is supported on various .NET platforms, ensuring compatibility and ongoing updates.
At 1:52:02, Tim explains why empty strings are essential: they preserve column order without breaking parsing logic.
Additionally, DevExpress WinForms controls support DirectX hardware acceleration for improved performance.
Saving Matchups and Winners
At 1:55:02, Tim saves matchups themselves. Because matchups can have multiple entries, he converts entry lists into pipe-delimited strings using a reused helper method. Developers can use the Toolbox in Visual Studio to add controls like buttons and labels to their Windows Forms, making it easier to design the user interface.
At 1:58:30, he applies the same null-handling logic for the winner field. If no winner exists yet, an empty string is saved.
Windows Forms enables developers to create applications for desktops, tablets, and PCs.
Rehydrating Rounds When Loading Tournaments
At 2:02:03, Tim addresses the final TODO: loading round information. He explains that rounds are stored as lists of matchup IDs, split first by pipes, then by carets (^).
By 2:09:13, he reconstructs the full nested structure:
IDs → MatchupModels
MatchupModels → Round lists
- Round lists → TournamentModel
After explaining the reconstruction, Tim notes that, unlike some other frameworks, WinForms uses a different approach to organizing UI and data, which can affect how developers manage nested structures.
Tim emphasizes that this nesting explains why the code feels complex—and why stepping through it manually is so important.
Additionally, it's worth mentioning that the DevExpress WinForms Subscription includes customizable themes and skins for WinForms applications.
Debugging Real Errors
From 2:11:22 onward, Tim intentionally runs the app and debugs:
NullReferenceException
Input string not in correct format
- Sequence contains no elements
Rather than hiding mistakes, Tim shows exactly how to trace them, inspect values, and reason about execution order. WinForms was first released years ago and has evolved over time.
At 2:20:30, he identifies a circular save issue: matchups needed to exist before entries could reference them. His solution is pragmatic—save twice. He openly admits it’s not elegant but says:
“Working code is better than refactored code that still has a bug.”
Windows Forms is now available as an open-source project for .NET Core on GitHub.
Deploying a Windows Forms App
After building and testing your Windows Forms app in Visual Studio, the next step is deployment—making your application available to users. Visual Studio offers several deployment options, with ClickOnce being one of the most convenient for Windows desktop applications. ClickOnce allows you to publish your app to a network file share, web server, or even a CD/DVD, making installation straightforward for end users.
To deploy, simply use the publishing tools in Visual Studio to package your app. Before publishing, thoroughly test your application using Visual Studio’s debugging tools to catch and fix any errors or issues in your code. Once deployed, users can install your app by running the setup file or clicking a shortcut, and the app will execute, displaying its graphical user interface for immediate interaction. Whether you choose ClickOnce, Windows Installer, or a third-party deployment tool, the process ensures your users can access the full functionality and features of your Windows Forms app with minimal hassle. This streamlined deployment process helps you deliver reliable, professional Windows desktop applications to your audience.
Closing Thoughts
By the end of this lesson, Tim Corey demonstrates a complete, working data persistence system using text files as a database. More importantly, he shows how real-world bugs emerge, how patterns guide fixes, and why understanding execution order matters more than perfection.
This lesson is not about fancy databases—it’s about thinking like a developer.

