From d266f7f2c7df05d30fb858e22fd61b888d7e383a Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 25 Oct 2026 13:16:36 +0100 Subject: [PATCH] DataTableOverview: move to .NET 10, follow the article's order, add print and typed-query helpers Both projects target net10.0 and the test packages move to xunit 2.9.3, xunit.runner.visualstudio 4.0.1, Microsoft.NET.Test.Sdk 18.11.0 and coverlet.collector 10.1.0. Program.cs adds the row before the primary key, as the article does, and gains AddPrimaryKey, AddNewRowFromValues, PrintTable and GetTickersBelow. The second carrot row in the CSV gets the right Sales value, ReadFields() results are null-checked, and seven tests cover the new code and the two failure cases. --- .../DataTableOverview.csproj | 5 +- .../DataTableOverview/Program.cs | 59 ++++++---- .../DataTableOverview/SalesDataProcessor.cs | 6 +- .../DataTableOverview/vegetable-sales.csv | 2 +- .../DataTableOverview/Tests/ProgramTests.cs | 102 ++++++++++++++++-- .../Tests/SalesDataProcessorTests.cs | 13 ++- .../DataTableOverview/Tests/Tests.csproj | 10 +- 7 files changed, 152 insertions(+), 45 deletions(-) diff --git a/csharp-intermediate-topics/DataTableOverview/DataTableOverview/DataTableOverview.csproj b/csharp-intermediate-topics/DataTableOverview/DataTableOverview/DataTableOverview.csproj index 48284b519e..1bf2c3bf8b 100644 --- a/csharp-intermediate-topics/DataTableOverview/DataTableOverview/DataTableOverview.csproj +++ b/csharp-intermediate-topics/DataTableOverview/DataTableOverview/DataTableOverview.csproj @@ -2,14 +2,11 @@ Exe - net7.0 + net10.0 enable enable - - - PreserveNewest diff --git a/csharp-intermediate-topics/DataTableOverview/DataTableOverview/Program.cs b/csharp-intermediate-topics/DataTableOverview/DataTableOverview/Program.cs index 87ae8054ad..2e21b00cbb 100644 --- a/csharp-intermediate-topics/DataTableOverview/DataTableOverview/Program.cs +++ b/csharp-intermediate-topics/DataTableOverview/DataTableOverview/Program.cs @@ -9,10 +9,12 @@ public static void Main(string[] args) var dt = CreateMainDataTable(); PopulateDataTable(dt); + AddPrimaryKey(dt); + PrintConstraints(dt); PrintData(dt); DataTable dtClone = dt.Clone(); - PrintColumns(dtClone); + PrintColumns(dtClone); DataTable dtCopy = dt.Copy(); PrintData(dtCopy); @@ -20,6 +22,8 @@ public static void Main(string[] args) AddNewRow(dt); FilterDataTable(dt); + PrintTable(dt); + SalesDataProcessor.ProcessSales(); } @@ -32,45 +36,41 @@ public static DataTable CreateMainDataTable() dt.Columns.Add("Date", typeof(DateTime)); dt.Columns.Add("Price", typeof(double)); - // add primary key - var pkCol = new DataColumn("Key", typeof(int)); - dt.Columns.Add(pkCol); - - var pkCols = new DataColumn[1]; - pkCols[0] = pkCol; - - dt.PrimaryKey = pkCols; - return dt; } public static void PopulateDataTable(DataTable dt) { - // add row // 1) declare and instantiate row - DataRow row; - row = dt.NewRow(); + DataRow row = dt.NewRow(); // 2) set column values row["Ticker"] = "MSFT"; row["Date"] = new DateTime(2023, 3, 3); - row["Price"] = 255.29; - row["Key"] = 0; + row["Price"] = 255.29; // by Ordinal/index row[0] = "MSFT"; row[1] = new DateTime(2023, 3, 3); row[2] = 255.29; - row[3] = 0; // 3) add populated row to table dt.Rows.Add(row); } + public static void AddPrimaryKey(DataTable dt) + { + var pkCol = new DataColumn("Key", typeof(int)); + dt.Columns.Add(pkCol); + + dt.Rows[0]["Key"] = 0; + + dt.PrimaryKey = [pkCol]; + } + public static void AddNewRow(DataTable dt) { - DataRow row; - row = dt.NewRow(); + DataRow row = dt.NewRow(); row["Ticker"] = "AMZN"; row["Date"] = new DateTime(2023, 3, 6); @@ -80,6 +80,11 @@ public static void AddNewRow(DataTable dt) dt.Rows.Add(row); } + public static void AddNewRowFromValues(DataTable dt) + { + dt.Rows.Add("AMZN", new DateTime(2023, 3, 6), 93.75, 1); + } + public static void PrintConstraints(DataTable dt) { foreach (Constraint constraint in dt.Constraints) @@ -124,5 +129,21 @@ public static void PrintData(DataTable dt) } } } + + public static void PrintTable(DataTable dt) + { + var header = dt.Columns.Cast().Select(col => col.ColumnName); + Console.WriteLine(string.Join(" | ", header)); + + foreach (DataRow row in dt.Rows) + Console.WriteLine(string.Join(" | ", row.ItemArray)); + } + + public static IEnumerable GetTickersBelow(DataTable dt, double price) + { + return dt.AsEnumerable() + .Where(row => row.Field("Price") < price) + .Select(row => row.Field("Ticker")); + } } -} \ No newline at end of file +} diff --git a/csharp-intermediate-topics/DataTableOverview/DataTableOverview/SalesDataProcessor.cs b/csharp-intermediate-topics/DataTableOverview/DataTableOverview/SalesDataProcessor.cs index 15c93de76d..627081d66f 100644 --- a/csharp-intermediate-topics/DataTableOverview/DataTableOverview/SalesDataProcessor.cs +++ b/csharp-intermediate-topics/DataTableOverview/DataTableOverview/SalesDataProcessor.cs @@ -16,21 +16,21 @@ public static void ProcessSales() public static DataTable CsvToDataTable(string filePath) { - DataTable dtRes = new DataTable(); + var dtRes = new DataTable(); using (var parser = new TextFieldParser(filePath)) { parser.TextFieldType = FieldType.Delimited; parser.TrimWhiteSpace = true; parser.SetDelimiters(","); - var columns = parser.ReadFields(); + var columns = parser.ReadFields() ?? []; foreach (var col in columns) dtRes.Columns.Add(col); while (!parser.EndOfData) { - var row = parser.ReadFields(); + var row = parser.ReadFields() ?? []; dtRes.LoadDataRow(row, true); } } diff --git a/csharp-intermediate-topics/DataTableOverview/DataTableOverview/vegetable-sales.csv b/csharp-intermediate-topics/DataTableOverview/DataTableOverview/vegetable-sales.csv index 3842b2ebbf..6f622e0a21 100644 --- a/csharp-intermediate-topics/DataTableOverview/DataTableOverview/vegetable-sales.csv +++ b/csharp-intermediate-topics/DataTableOverview/DataTableOverview/vegetable-sales.csv @@ -5,7 +5,7 @@ Pepper,2,2,4 Cucumber,4,1,4 Lettuce,6,0.5,3 Asparagus,2,2.5,5 -Carrot,1,3,1 +Carrot,1,3,3 Broccoli,2,0.75,1.5 Pepper,4,2,8 Cucumber,7,1,7 diff --git a/csharp-intermediate-topics/DataTableOverview/Tests/ProgramTests.cs b/csharp-intermediate-topics/DataTableOverview/Tests/ProgramTests.cs index 38216d363e..05499fef7c 100644 --- a/csharp-intermediate-topics/DataTableOverview/Tests/ProgramTests.cs +++ b/csharp-intermediate-topics/DataTableOverview/Tests/ProgramTests.cs @@ -5,13 +5,23 @@ namespace Tests { + [Collection("Console output")] public class ProgramTests { + private static DataTable CreateKeyedTable() + { + var dt = Program.CreateMainDataTable(); + Program.PopulateDataTable(dt); + Program.AddPrimaryKey(dt); + + return dt; + } + [Fact] public static void WhenCreateMainDataTable_ThenColumnsPopulateCorrectly() { var dt = Program.CreateMainDataTable(); - var expectedColumns = new string[] { "Ticker", "Date", "Price", "Key" }; + var expectedColumns = new string[] { "Ticker", "Date", "Price" }; var actualColumns = dt.Columns.Cast() .Select(x => x.ColumnName) @@ -21,13 +31,25 @@ public static void WhenCreateMainDataTable_ThenColumnsPopulateCorrectly() } [Fact] - public static void WhenCreateMaindataTable_ThenPrimaryKeyEqualsKey() + public static void WhenAddPrimaryKey_ThenPrimaryKeyEqualsKey() { - var dt = Program.CreateMainDataTable(); + var dt = CreateKeyedTable(); var pkActual = dt.PrimaryKey; Assert.Equal("Key", pkActual[0].Caption); + Assert.Equal(0, dt.Rows[0]["Key"]); + } + + [Fact] + public static void WhenPrimaryKeySetOnColumnWithEmptyValues_ThenDataExceptionThrown() + { + var dt = Program.CreateMainDataTable(); + Program.PopulateDataTable(dt); + var pkCol = new DataColumn("Key", typeof(int)); + dt.Columns.Add(pkCol); + + Assert.Throws(() => dt.PrimaryKey = [pkCol]); } [Fact] @@ -43,17 +65,36 @@ public static void WhenPopulateDataTable_ThenDataTableContainsMsftRecord() [Fact] public static void WhenAddNewRow_ThenDataTableContainsAmznRecord() { - var dt = Program.CreateMainDataTable(); + var dt = CreateKeyedTable(); Program.AddNewRow(dt); - Assert.Contains("AMZN", Convert.ToString(dt.Rows[0][0])); + Assert.Contains("AMZN", Convert.ToString(dt.Rows[1][0])); + } + + [Fact] + public static void WhenAddNewRowFromValues_ThenDataTableContainsAmznRecord() + { + var dt = CreateKeyedTable(); + + Program.AddNewRowFromValues(dt); + + Assert.Equal("AMZN", dt.Rows[1]["Ticker"]); + Assert.Equal(1, dt.Rows[1]["Key"]); + } + + [Fact] + public static void WhenRowAddedWithDuplicateKey_ThenConstraintExceptionThrown() + { + var dt = CreateKeyedTable(); + + Assert.Throws(() => dt.Rows.Add("AMZN", new DateTime(2023, 3, 6), 93.75, 0)); } [Fact] public static void WhenPrintConstraints_ThenReturnsConstraintOneUniqueConstraint() { - var dt = Program.CreateMainDataTable(); + var dt = CreateKeyedTable(); var stringWriter = new StringWriter(); Console.SetOut(stringWriter); @@ -65,10 +106,21 @@ public static void WhenPrintConstraints_ThenReturnsConstraintOneUniqueConstraint Assert.Contains("System.Data.UniqueConstraint", output); } + [Fact] + public static void WhenCloneCalled_ThenAllColumnsAndNoRowsCopied() + { + var dt = CreateKeyedTable(); + + var dtClone = dt.Clone(); + + Assert.Equal(4, dtClone.Columns.Count); + Assert.Empty(dtClone.Rows); + } + [Fact] public static void WhenFilterDataTable_ThenPrintsAmznDataRow() { - var dt = Program.CreateMainDataTable(); + var dt = CreateKeyedTable(); Program.AddNewRow(dt); var stringWriter = new StringWriter(); @@ -78,13 +130,13 @@ public static void WhenFilterDataTable_ThenPrintsAmznDataRow() var output = stringWriter.ToString(); Assert.Contains("AMZN", output); + Assert.DoesNotContain("MSFT", output); } [Fact] public static void WhenPrintData_ThenPrintsExpectedData() { - var dt = Program.CreateMainDataTable(); - Program.PopulateDataTable(dt); + var dt = CreateKeyedTable(); var stringWriter = new StringWriter(); Console.SetOut(stringWriter); @@ -95,7 +147,35 @@ public static void WhenPrintData_ThenPrintsExpectedData() Assert.Contains("MSFT", output); Assert.Contains(Convert.ToString(new DateTime(2023, 3, 3)), output); Assert.Contains("255.29", output); - Assert.Contains("0", output); + Assert.Contains("Key: 0", output); + } + + [Fact] + public static void WhenPrintTable_ThenPrintsHeaderAndOneLinePerRow() + { + var dt = CreateKeyedTable(); + Program.AddNewRow(dt); + + var stringWriter = new StringWriter(); + Console.SetOut(stringWriter); + + Program.PrintTable(dt); + var lines = stringWriter.ToString().Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + + Assert.Equal("Ticker | Date | Price | Key", lines[0]); + Assert.Equal(3, lines.Length); + Assert.StartsWith("AMZN | ", lines[2]); + } + + [Fact] + public static void WhenGetTickersBelow_ThenReturnsOnlyCheaperTickers() + { + var dt = CreateKeyedTable(); + Program.AddNewRow(dt); + + var tickers = Program.GetTickersBelow(dt, 100); + + Assert.Equal(new[] { "AMZN" }, tickers); } } -} \ No newline at end of file +} diff --git a/csharp-intermediate-topics/DataTableOverview/Tests/SalesDataProcessorTests.cs b/csharp-intermediate-topics/DataTableOverview/Tests/SalesDataProcessorTests.cs index 0fd8f6d3d5..a9d1a8f9e7 100644 --- a/csharp-intermediate-topics/DataTableOverview/Tests/SalesDataProcessorTests.cs +++ b/csharp-intermediate-topics/DataTableOverview/Tests/SalesDataProcessorTests.cs @@ -1,10 +1,10 @@ using DataTableOverview; -using Microsoft.VisualStudio.TestPlatform.TestHost; using Xunit; using System; namespace Tests { + [Collection("Console output")] public class SalesDataProcessorTests { [Fact] @@ -32,6 +32,15 @@ public void WhenProcessSales_ThenPrintsSelectedRows() Assert.Contains("Carrot", output); } + + [Fact] + public void WhenNumericFilterRunsOnCsvColumn_ThenEvaluateExceptionThrown() + { + var filePath = AppDomain.CurrentDomain.BaseDirectory + "vegetable-sales.csv"; + var veggieSales = SalesDataProcessor.CsvToDataTable(filePath); + + Assert.Equal(typeof(string), veggieSales.Columns["Price"]!.DataType); + Assert.Throws(() => veggieSales.Select("Price > 1")); + } } } - diff --git a/csharp-intermediate-topics/DataTableOverview/Tests/Tests.csproj b/csharp-intermediate-topics/DataTableOverview/Tests/Tests.csproj index f805d3f2a2..3cd329e537 100644 --- a/csharp-intermediate-topics/DataTableOverview/Tests/Tests.csproj +++ b/csharp-intermediate-topics/DataTableOverview/Tests/Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable @@ -9,13 +9,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all