diff --git a/.gitignore b/.gitignore
index 0b26c11d..e1293c09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
*.user
*.userosscache
*.sln.docstates
+*.db
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/CodeReviews.Console.HabitTracker.sln b/CodeReviews.Console.HabitTracker.sln
new file mode 100644
index 00000000..fab62861
--- /dev/null
+++ b/CodeReviews.Console.HabitTracker.sln
@@ -0,0 +1,24 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.2.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HabitTracker.natholder", "HabitTracker.natholder\HabitTracker.natholder.csproj", "{AA4088A3-C32F-4B29-48E0-339A2F9967C6}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {AA4088A3-C32F-4B29-48E0-339A2F9967C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AA4088A3-C32F-4B29-48E0-339A2F9967C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AA4088A3-C32F-4B29-48E0-339A2F9967C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AA4088A3-C32F-4B29-48E0-339A2F9967C6}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {D67F1646-947A-4847-A048-583B3565ABC1}
+ EndGlobalSection
+EndGlobal
diff --git a/HabitTracker.natholder/HabitTracker.natholder.csproj b/HabitTracker.natholder/HabitTracker.natholder.csproj
new file mode 100644
index 00000000..6ed0cec0
--- /dev/null
+++ b/HabitTracker.natholder/HabitTracker.natholder.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/HabitTracker.natholder/Program.cs b/HabitTracker.natholder/Program.cs
new file mode 100644
index 00000000..d4d9bd15
--- /dev/null
+++ b/HabitTracker.natholder/Program.cs
@@ -0,0 +1,269 @@
+using Microsoft.Data.Sqlite;
+
+namespace HabitTracker
+{
+
+ public class Runs
+ {
+ public int Id { get; set; }
+ public DateTime Date { get; set; }
+ public double Miles { get; set; }
+ }
+ class Program
+ {
+ static string dbFileName = "HabitTracker.db";
+ static string connectionString = $"Data Source ={dbFileName}";
+ static void Main(string[] args)
+ {
+ InitDB();
+ MenuLoop();
+ }
+
+ static void InitDB()
+ {
+ try
+ {
+ using var connection = new SqliteConnection(connectionString);
+ connection.Open();
+ Console.WriteLine($"Database {dbFileName} created or opened successfully.");
+ CreateTable(connection);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"An error occurred: {ex.Message}");
+ }
+ }
+ static void MenuLoop()
+ {
+ string menuText = @"
+ __ ____ __ ______ __
+ / |/ (_) /__ _____ /_ __/________ ______/ /_____ _____
+ / /|_/ / / / _ \/ ___/ / / / ___/ __ `/ ___/ //_/ _ \/ ___/
+ / / / / / / __(__ ) / / / / / /_/ / /__/ ,< / __/ /
+/_/ /_/_/_/\___/____/ /_/ /_/ \__,_/\___/_/|_|\___/_/";
+ bool running = true;
+ string? userInput;
+ int number;
+ Console.WriteLine(menuText);
+ while (running)
+ {
+ ShowMenu();
+ userInput = Console.ReadLine();
+ if (int.TryParse(userInput, out number) && number > 0 && number <= 6)
+ {
+ switch (userInput)
+ {
+ case "1":
+ SelectRecords();
+ break;
+ case "2":
+ Insert();
+ break;
+ case "3":
+ Update();
+ break;
+ case "4":
+ Delete();
+ break;
+ default:
+ running = false;
+ break;
+ }
+ }
+ else
+ {
+ Console.WriteLine("Invalid input. Please try again.");
+ }
+ }
+ }
+ static void CreateTable(SqliteConnection connection)
+ {
+ string CreateTableQuery = @"
+ CREATE TABLE IF NOT EXISTS Habits (
+ Id INTEGER PRIMARY KEY AUTOINCREMENT,
+ Date DATETIME,
+ Miles DOUBLE
+ )";
+
+ using var command = new SqliteCommand(CreateTableQuery, connection);
+ command.ExecuteNonQuery();
+ Console.WriteLine("Table 'Habits' created or already exists.");
+ }
+
+ static void Insert()
+ {
+ DateTime date = GetDate();
+ double miles = GetMiles();
+ using var connection = new SqliteConnection(connectionString);
+ connection.Open();
+ var insert = connection.CreateCommand();
+ insert.CommandText = $"INSERT INTO Habits (Date, Miles) VALUES ('{date}', {miles})";
+ insert.ExecuteNonQuery();
+ connection.Close();
+ }
+
+ static void SelectRecords()
+ {
+ Console.Clear();
+ using var connection = new SqliteConnection(connectionString);
+ connection.Open();
+ var select = connection.CreateCommand();
+ select.CommandText = $"SELECT * FROM Habits";
+ List tableData = [];
+ SqliteDataReader reader = select.ExecuteReader();
+ if (reader.HasRows)
+ {
+ while (reader.Read())
+ {
+ tableData.Add(
+ new Runs
+ {
+ Id = reader.GetInt32(0),
+ Date = reader.GetDateTime(1),
+ Miles = reader.GetDouble(2)
+ }
+ );
+ }
+ }
+ else
+ {
+ Console.WriteLine("No rows found.");
+ }
+ connection.Close();
+
+ Console.WriteLine("---------------------------------------");
+ Console.WriteLine(" Id | Date | Miles");
+ Console.WriteLine("---------------------------------------");
+ foreach (var row in tableData)
+ {
+ Console.WriteLine($"| {row.Id} | {row.Date.ToString("MM-dd-yyyy")} | {row.Miles} |");
+ }
+ Console.WriteLine("---------------------------------------");
+
+ }
+
+ static void Delete()
+ {
+ int id = GetId();
+ using var connection = new SqliteConnection(connectionString);
+ connection.Open();
+ var insert = connection.CreateCommand();
+ insert.CommandText = $"DELETE FROM Habits WHERE Id = {id}";
+ int rowsAffected = insert.ExecuteNonQuery();
+
+ if (rowsAffected > 0)
+ {
+ Console.WriteLine($"Successfully deleted record with ID: {id}");
+ }
+ else
+ {
+ Console.WriteLine($"No record found with ID: {id}");
+ }
+
+ connection.Close();
+ }
+
+ static void Update()
+ {
+ Console.Clear();
+ SelectRecords();
+
+ Console.WriteLine("What is the id of the record you want to update?");
+ int id = GetId();
+
+ Console.WriteLine("Enter new values for this record:");
+ DateTime newDate = GetDate();
+ double newMiles = GetMiles();
+
+ using var connection = new SqliteConnection(connectionString);
+ connection.Open();
+ var update = connection.CreateCommand();
+ update.CommandText = $"UPDATE Habits SET Date = @date, Miles = @miles WHERE Id = {id}";
+ update.Parameters.AddWithValue("@date", newDate);
+ update.Parameters.AddWithValue("@miles", newMiles);
+ int rowsAffected = update.ExecuteNonQuery();
+
+ if (rowsAffected > 0)
+ {
+ Console.WriteLine($"Successfully updated record with ID: {id}");
+ }
+ else
+ {
+ Console.WriteLine($"No record found with ID: {id}");
+ }
+
+ connection.Close();
+ }
+
+
+ static void ShowMenu()
+ {
+ Console.WriteLine("1. View");
+ Console.WriteLine("2. Insert");
+ Console.WriteLine("3. Update");
+ Console.WriteLine("4. Delete");
+ Console.WriteLine("5. Quit");
+ }
+
+ static double GetMiles()
+ {
+ double miles;
+ string? input;
+ Console.WriteLine("How many miles did you run?");
+ input = Console.ReadLine();
+ if (double.TryParse(input, out miles))
+ {
+ return miles;
+ }
+ else
+ {
+ Console.WriteLine("Please enter a valid number");
+ return GetMiles();
+ }
+ }
+
+ static int GetId()
+ {
+ int id;
+ string? input;
+ Console.WriteLine("Enter the id of the record you would like to delete.");
+ input = Console.ReadLine();
+ if (int.TryParse(input, out id))
+ {
+ return id;
+ }
+ else
+ {
+ Console.WriteLine("Please enter a valid number");
+ return GetId();
+ }
+ }
+
+ static DateTime GetDate()
+ {
+ string? input;
+ DateTime date;
+ Console.WriteLine("What day was your run? (type 'td' for today's date, or enter date as MM/DD/YYYY):");
+ input = Console.ReadLine();
+
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ Console.WriteLine("Please enter a valid date or 'td' for today.");
+ return GetDate(); // Recursive call for empty input
+ }
+
+ if (input?.ToLower() == "td")
+ {
+ return DateTime.Today;
+ }
+
+ if (DateTime.TryParse(input, out date))
+ {
+ return date;
+ }
+
+ Console.WriteLine("Invalid date format. Please try again (e.g., 02/16/2026) or type 'td' for today.");
+ return GetDate(); // Recursive call for invalid date
+ }
+ }
+}
diff --git a/HabitTracker.natholder/readme.md b/HabitTracker.natholder/readme.md
new file mode 100644
index 00000000..e01a100c
--- /dev/null
+++ b/HabitTracker.natholder/readme.md
@@ -0,0 +1,50 @@
+# HabitTracker Console App
+
+A console application to log occurrences of miles (runs). The app stores entries in a SQLite database using ADO.NET and supports inserting, viewing, updating and deleting records.
+
+---
+
+## Quick overview
+
+- Record format: `Date` (when the habit occurred) and `Miles` (quantity).
+- Database: `HabitTracker.db` is created automatically when the app runs.
+- Storage access: ADO.NET only (no EF / Dapper). Parameterized SQL is used to prevent injection.
+
+---
+
+## How to run
+
+1. Open a terminal in the project folder `HabitTracker.natholder`.
+2. Build & run:
+
+ ```bash
+ dotnet run
+ ```
+
+3. The app will create `HabitTracker.db` (if missing) and the `Habits` table.
+4. Use the menu to Insert, View, Update, or Delete records.
+
+---
+
+## Features
+
+- Creates a sqlite db and habit table on first run or if they have been deleted.
+- Insert records with validated values..
+- View (select) all records
+- Update an existing record
+- Delete a record
+- input validation and error handling
+
+---
+
+## Thought process / retrospective
+
+I think that once i got started and made a little bit of progress, that the rest of the project came a lot easier. I had some trouble implementing the view command and had to use the video tutorial. I feel like I am learning though, and am continuting to improve.
+
+---
+
+## Potential improvements (next steps)
+
+- Add unit tests for parsing/validation logic.
+
+---