diff --git a/app/Simpler.Tests/Core/Mocks/MockPerson.cs b/app/Simpler.Tests/Core/Mocks/MockPerson.cs index 7bd338a..a97ec1d 100644 --- a/app/Simpler.Tests/Core/Mocks/MockPerson.cs +++ b/app/Simpler.Tests/Core/Mocks/MockPerson.cs @@ -4,6 +4,9 @@ public class MockPerson { public string Name { get; set; } public int? Age { get; set; } + public MockPet Pet { get; set; } + public MockVehicle[] Vehicles { get; set; } public MockEnum MockEnum { get; set; } + public dynamic Other { get; set; } } } diff --git a/app/Simpler.Tests/Core/Mocks/MockPet.cs b/app/Simpler.Tests/Core/Mocks/MockPet.cs new file mode 100644 index 0000000..dd441bd --- /dev/null +++ b/app/Simpler.Tests/Core/Mocks/MockPet.cs @@ -0,0 +1,8 @@ +namespace Simpler.Tests.Core.Mocks +{ + public class MockPet + { + public string Name { get; set; } + public int? Age { get; set; } + } +} diff --git a/app/Simpler.Tests/Core/Mocks/MockVehicle.cs b/app/Simpler.Tests/Core/Mocks/MockVehicle.cs new file mode 100644 index 0000000..c2167a3 --- /dev/null +++ b/app/Simpler.Tests/Core/Mocks/MockVehicle.cs @@ -0,0 +1,8 @@ +namespace Simpler.Tests.Core.Mocks +{ + public class MockVehicle + { + public string Make { get; set; } + public string Model { get; set; } + } +} diff --git a/app/Simpler.Tests/Data/PropertyMappingTree/ArrayElementNodeTest.cs b/app/Simpler.Tests/Data/PropertyMappingTree/ArrayElementNodeTest.cs new file mode 100644 index 0000000..93735ff --- /dev/null +++ b/app/Simpler.Tests/Data/PropertyMappingTree/ArrayElementNodeTest.cs @@ -0,0 +1,63 @@ +using NUnit.Framework; +using Simpler.Data.PropertyMappingTree; +using Simpler.Tests.Core.Mocks; + +namespace Simpler.Tests.Data.PropertyMappingTree +{ + [TestFixture] + public class ArrayElementNodeTest + { + [Test] + public void should_set_value_for_array_child_node() + { + //arrange + var array = new MockPet[1]; + var propertyParseTreeArrayChildNode = new ArrayElementNode + { + Name = "0", + PropertyType = typeof (MockPet) + }; + + // Act + propertyParseTreeArrayChildNode.SetValue(array, null); + + // Assert + Assert.That(array[0], Is.Not.Null); + } + + [Test] + public void should_get_value_for_array_child_node() + { + //arrange + var array = new [] { new MockPet {Name = "Doug"} }; + var propertyParseTreeArrayChildNode = new ArrayElementNode + { + Name = "0", + PropertyType = typeof (MockPet) + }; + + // Act + var value = (MockPet)propertyParseTreeArrayChildNode.GetValue(array); + + // Assert + Assert.That(value, Is.Not.Null); + Assert.That(value.Name, Is.EqualTo("Doug")); + } + + [Test] + public void should_create_object_for_array_child_node() + { + //arrange + var propertyParseTreeArrayChildNode = new ArrayElementNode + { + PropertyType = typeof (MockPet) + }; + + // Act + var value = propertyParseTreeArrayChildNode.CreateObject(null); + + // Assert + Assert.That(value, Is.TypeOf(typeof(MockPet))); + } + } +} \ No newline at end of file diff --git a/app/Simpler.Tests/Data/PropertyMappingTree/ArrayNodeTest.cs b/app/Simpler.Tests/Data/PropertyMappingTree/ArrayNodeTest.cs new file mode 100644 index 0000000..013ee08 --- /dev/null +++ b/app/Simpler.Tests/Data/PropertyMappingTree/ArrayNodeTest.cs @@ -0,0 +1,66 @@ +using NUnit.Framework; +using Simpler.Data.PropertyMappingTree; +using Simpler.Tests.Core.Mocks; + +namespace Simpler.Tests.Data.PropertyParseTree +{ + [TestFixture] + public class ArrayNodeTest + { + [Test] + public void should_set_value_for_array_node() + { + //arrange + var obj = new MockPerson(); + var propertyParseTreeArrayNode = new ArrayNode + { + Name = "Vehicles", + PropertyInfo = obj.GetType().GetProperty("Vehicles"), + PropertyType = typeof (MockVehicle[]) + }; + + // Act + propertyParseTreeArrayNode.SetValue(obj, null); + + // Assert + Assert.That(obj.Vehicles, Is.Not.Null); + } + + [Test] + public void should_get_value_for_array() + { + //arrange + var obj = new MockPerson {Vehicles = new MockVehicle[0]}; + var propertyParseTreeArrayNode = new ArrayNode + { + Name = "Vehicles", + PropertyInfo = obj.GetType().GetProperty("Vehicles"), + PropertyType = typeof (MockVehicle[]) + }; + + // Act + var value = propertyParseTreeArrayNode.GetValue(obj); + + // Assert + Assert.That(value, Is.Not.Null); + Assert.That(value, Is.EqualTo(obj.Vehicles)); + } + + [Test] + public void should_create_object_for_array() + { + //arrange + var propertyParseTreeArrayNode = new ArrayNode + { + PropertyType = typeof (MockVehicle[]) + }; + + // Act + var value = propertyParseTreeArrayNode.CreateObject(null); + + // Assert + Assert.That(value, Is.Not.Null); + Assert.That(value, Is.TypeOf(typeof(MockVehicle[]))); + } + } +} \ No newline at end of file diff --git a/app/Simpler.Tests/Data/PropertyMappingTree/DynamicNodeTest.cs b/app/Simpler.Tests/Data/PropertyMappingTree/DynamicNodeTest.cs new file mode 100644 index 0000000..0a5fe11 --- /dev/null +++ b/app/Simpler.Tests/Data/PropertyMappingTree/DynamicNodeTest.cs @@ -0,0 +1,68 @@ +using System.Dynamic; +using NUnit.Framework; +using Simpler.Data.PropertyMappingTree; +using Simpler.Tests.Core.Mocks; + +namespace Simpler.Tests.Data.PropertyParseTree +{ + [TestFixture] + public class DynamicNodeTest + { + [Test] + public void should_set_value_for_dynamic_node() + { + //arrange + var obj = new MockPerson(); + var propertyParseTreeArrayNode = new DynamicNode() + { + Name = "Other", + PropertyInfo = obj.GetType().GetProperty("Other"), + PropertyType = typeof (object) + }; + + // Act + propertyParseTreeArrayNode.SetValue(obj, null); + + // Assert + Assert.That(obj.Other, Is.Not.Null); + } + + [Test] + public void should_get_value_for_dynamic_node() + { + //arrange + var obj = new MockPerson{Other = new ExpandoObject()}; + var propertyParseTreeArrayNode = new DynamicNode() + { + Name = "Other", + PropertyInfo = obj.GetType().GetProperty("Other"), + PropertyType = typeof(object) + }; + + // Act + var value = propertyParseTreeArrayNode.GetValue(obj); + + // Assert + Assert.That(value, Is.Not.Null); + Assert.That(value, Is.EqualTo(obj.Other)); + } + + [Test] + public void should_create_object_for_dynamic_node() + { + //arrange + var propertyParseTreeArrayNode = new DynamicNode() + { + Name = "Other", + PropertyType = typeof(object) + }; + + // Act + var value = propertyParseTreeArrayNode.CreateObject(); + + // Assert + Assert.That(value, Is.Not.Null); + Assert.That(value, Is.TypeOf(typeof(ExpandoObject))); + } + } +} \ No newline at end of file diff --git a/app/Simpler.Tests/Data/PropertyMappingTree/DynamicPropertyNodeTest.cs b/app/Simpler.Tests/Data/PropertyMappingTree/DynamicPropertyNodeTest.cs new file mode 100644 index 0000000..91c5d12 --- /dev/null +++ b/app/Simpler.Tests/Data/PropertyMappingTree/DynamicPropertyNodeTest.cs @@ -0,0 +1,65 @@ +using System.Dynamic; +using NUnit.Framework; +using Simpler.Data.PropertyMappingTree; + +namespace Simpler.Tests.Data.PropertyParseTree +{ + [TestFixture] + public class DynamicPropertyNodeTest + { + [Test] + public void should_set_value_for_dynamic_node() + { + //arrange + dynamic obj = new ExpandoObject(); + var propertyParseTreeArrayNode = new DynamicPropertyNode() + { + Name = "City", + PropertyType = typeof (string) + }; + + // Act + propertyParseTreeArrayNode.SetValue(obj, "Anchorage"); + + // Assert + Assert.That(obj.City, Is.EqualTo("Anchorage")); + } + + [Test] + public void should_get_value_for_dynamic_node() + { + //arrange + dynamic obj = new ExpandoObject(); + obj.City = "Anchorage"; + var propertyParseTreeArrayNode = new DynamicPropertyNode() + { + Name = "City", + PropertyType = typeof(string) + }; + + // Act + var value = propertyParseTreeArrayNode.GetValue(obj); + + // Assert + Assert.That(value, Is.Not.Null); + Assert.That(value, Is.EqualTo(obj.City)); + } + + [Test] + public void should_create_object_for_dynamic_node() + { + //arrange + var propertyParseTreeArrayNode = new DynamicPropertyNode() + { + Name = "City", + PropertyType = typeof(string) + }; + + // Act + var value = propertyParseTreeArrayNode.CreateObject("Anchorage"); + + // Assert + Assert.That(value, Is.EqualTo("Anchorage")); + } + } +} \ No newline at end of file diff --git a/app/Simpler.Tests/Data/PropertyMappingTree/ObjectNodeTest.cs b/app/Simpler.Tests/Data/PropertyMappingTree/ObjectNodeTest.cs new file mode 100644 index 0000000..e29e92e --- /dev/null +++ b/app/Simpler.Tests/Data/PropertyMappingTree/ObjectNodeTest.cs @@ -0,0 +1,65 @@ +using NUnit.Framework; +using Simpler.Data.PropertyMappingTree; +using Simpler.Tests.Core.Mocks; + +namespace Simpler.Tests.Data.PropertyParseTree +{ + [TestFixture] + public class ObjectNodeTest + { + [Test] + public void should_set_value_for_object_node() + { + //arrange + var obj = new MockPerson(); + var propertyParseTreeObjectNode = new ObjectNode() + { + Name = "Name", + PropertyInfo = obj.GetType().GetProperty("Name"), + PropertyType = typeof(string) + }; + + // Act + propertyParseTreeObjectNode.SetValue(obj, "Richard"); + + // Assert + Assert.That(obj.Name, Is.EqualTo("Richard")); + } + + [Test] + public void should_get_value_for_object_node() + { + //arrange + var obj = new MockPerson{ Name = "Richard" }; + var propertyParseTreeObjectNode = new ObjectNode() + { + Name = "Name", + PropertyInfo = obj.GetType().GetProperty("Name"), + PropertyType = typeof(string) + }; + + // Act + var value = propertyParseTreeObjectNode.GetValue(obj); + + // Assert + Assert.That(value, Is.EqualTo("Richard")); + } + + [Test] + public void should_create_object_for_object_node() + { + //arrange + var propertyParseTreeObjectNode = new ObjectNode() + { + Name = "Name", + PropertyType = typeof(string) + }; + + // Act + var value = propertyParseTreeObjectNode.CreateObject("Richard"); + + // Assert + Assert.That(value, Is.EqualTo("Richard")); + } + } +} \ No newline at end of file diff --git a/app/Simpler.Tests/Data/Tasks/BuildDynamicTest.cs b/app/Simpler.Tests/Data/Tasks/BuildDynamicTest.cs deleted file mode 100644 index 3cf13ba..0000000 --- a/app/Simpler.Tests/Data/Tasks/BuildDynamicTest.cs +++ /dev/null @@ -1,33 +0,0 @@ -using NUnit.Framework; -using Simpler.Data.Tasks; -using System.Data; -using Moq; - -namespace Simpler.Tests.Data.Tasks -{ - [TestFixture] - public class BuildDynamicTest - { - [Test] - public void should_populate_dynamic_object_using_all_columns_in_the_data_record() - { - // Arrange - var task = Task.New(); - - var mockDataRecord = new Mock(); - mockDataRecord.Setup(dataRecord => dataRecord.FieldCount).Returns(2); - mockDataRecord.Setup(dataRecord => dataRecord.GetName(0)).Returns("Name"); - mockDataRecord.Setup(dataRecord => dataRecord["Name"]).Returns("John Doe"); - mockDataRecord.Setup(dataRecord => dataRecord.GetName(1)).Returns("Age"); - mockDataRecord.Setup(dataRecord => dataRecord["Age"]).Returns(21); - task.In.DataRecord = mockDataRecord.Object; - - // Act - task.Execute(); - - // Assert - Assert.That(task.Out.Object.Name, Is.EqualTo("John Doe")); - Assert.That(task.Out.Object.Age, Is.EqualTo(21)); - } - } -} \ No newline at end of file diff --git a/app/Simpler.Tests/Data/Tasks/BuildObjectTest.cs b/app/Simpler.Tests/Data/Tasks/BuildObjectTest.cs new file mode 100644 index 0000000..2ccad22 --- /dev/null +++ b/app/Simpler.Tests/Data/Tasks/BuildObjectTest.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using NUnit.Framework; +using Simpler.Data.Tasks; +using Moq; +using System.Data; +using Simpler.Tests.Core.Mocks; + +namespace Simpler.Tests.Data.Tasks +{ + [TestFixture] + public class BuildObjectTest + { + public static Simpler.Data.PropertyMappingTree.AbstractNode get_parse_tree_from_data_table(DataTable table, Type type) + { + var buildPropertyParseTree = new Simpler.Data.Tasks.BuildPropertyMappingTree(); + buildPropertyParseTree.In.Columns = table.Columns.Cast().Select((x, i) => new { x.ColumnName, i }).ToDictionary(x => x.ColumnName, x => x.i); + buildPropertyParseTree.In.InitialType = type; + buildPropertyParseTree.Execute(); + return buildPropertyParseTree.Out.PropertyMappingTree; + } + + [Test] + public void should_create_root_typed_object() + { + //Arrange + var table = new DataTable(); + table.Columns.Add("Name", Type.GetType("System.String")); + table.Rows.Add(new object[] { "John Doe" }); + + var dataReader = table.CreateDataReader(); + dataReader.Read(); + + var task = Task.New>(); + task.In.DataRecord = dataReader; + task.In.PropertyParse = get_parse_tree_from_data_table(table, typeof(MockPerson)); + + //Act + task.Execute(); + + //Assert + Assert.That(task.Out.Object, Is.Not.Null); + Assert.That(task.Out.Object, Is.TypeOf(typeof(MockPerson))); + } + + [Test] + public void should_create_root_dynamic_object() + { + //Arrange + var table = new DataTable(); + table.Columns.Add("Name", Type.GetType("System.String")); + table.Rows.Add(new object[] { "John Doe" }); + + var dataReader = table.CreateDataReader(); + dataReader.Read(); + + var task = Task.New>(); + task.In.DataRecord = dataReader; + task.In.PropertyParse = get_parse_tree_from_data_table(table, typeof(object)); + + //Act + task.Execute(); + + //Assert + Assert.That(task.Out.Object, Is.Not.Null); + Assert.That(task.Out.Object, Is.TypeOf(typeof(ExpandoObject))); + } + + [Test] + public void should_assign_simple_properties() + { + //Arrange + var table = new DataTable(); + table.Columns.Add("Name", Type.GetType("System.String")); + table.Rows.Add(new object[] { "John Doe" }); + + var dataReader = table.CreateDataReader(); + dataReader.Read(); + + var task = Task.New>(); + task.In.DataRecord = dataReader; + task.In.PropertyParse = get_parse_tree_from_data_table(table, typeof(MockPerson)); + + //Act + task.Execute(); + + //Assert + Assert.That(task.Out.Object, Is.Not.Null); + Assert.That(task.Out.Object.Name, Is.EqualTo("John Doe")); + } + + [Test] + public void should_assign_nested_properties() + { + //Arrange + var table = new DataTable(); + table.Columns.Add("PetName", Type.GetType("System.String")); + table.Rows.Add(new object[] { "Spot" }); + + var dataReader = table.CreateDataReader(); + dataReader.Read(); + + var task = Task.New>(); + task.In.DataRecord = dataReader; + task.In.PropertyParse = get_parse_tree_from_data_table(table, typeof(MockPerson)); + + //Act + task.Execute(); + + //Assert + Assert.That(task.Out.Object, Is.Not.Null); + Assert.That(task.Out.Object.Pet.Name, Is.EqualTo("Spot")); + } + + [Test] + public void should_assign_array_properties() + { + //Arrange + var table = new DataTable(); + table.Columns.Add("Vehicles0Make", Type.GetType("System.String")); + table.Rows.Add(new object[] { "Jeep" }); + + var dataReader = table.CreateDataReader(); + dataReader.Read(); + + var task = Task.New>(); + task.In.DataRecord = dataReader; + task.In.PropertyParse = get_parse_tree_from_data_table(table, typeof(MockPerson)); + + //Act + task.Execute(); + + //Assert + Assert.That(task.Out.Object, Is.Not.Null); + Assert.That(task.Out.Object.Vehicles[0].Make, Is.EqualTo("Jeep")); + } + + [Test] + public void should_assign_dynamic_properties() + { + //Arrange + var table = new DataTable(); + table.Columns.Add("OtherCity", Type.GetType("System.String")); + table.Rows.Add(new object[] { "Anchorage" }); + + var dataReader = table.CreateDataReader(); + dataReader.Read(); + + var task = Task.New>(); + task.In.DataRecord = dataReader; + task.In.PropertyParse = get_parse_tree_from_data_table(table, typeof(MockPerson)); + + //Act + task.Execute(); + + //Assert + Assert.That(task.Out.Object, Is.Not.Null); + Assert.That(task.Out.Object.Other.City, Is.EqualTo("Anchorage")); + } + } +} \ No newline at end of file diff --git a/app/Simpler.Tests/Data/Tasks/BuildObjectsTest.cs b/app/Simpler.Tests/Data/Tasks/BuildObjectsTest.cs index 2027f79..501558c 100644 --- a/app/Simpler.Tests/Data/Tasks/BuildObjectsTest.cs +++ b/app/Simpler.Tests/Data/Tasks/BuildObjectsTest.cs @@ -22,11 +22,20 @@ static IDataReader SetupReader() } [Test] - public void should_return_an_object_for_each_record_returned_by_the_select_command() + public void should_return_an_object_for_each_record() { // Arrange var task = Task.New>(); - task.In.Reader = SetupReader(); + + var table = new DataTable(); + table.Columns.Add("Name", Type.GetType("System.String")); + table.Columns.Add("Age", Type.GetType("System.Int32")); + table.Columns.Add("PetName", Type.GetType("System.String")); + table.Columns.Add("Vehicles0Make", Type.GetType("System.String")); + table.Rows.Add(new object[] { "John Doe", "21", "Doug", "Dodge" }); + table.Rows.Add(new object[] { "Jane Doe", "19", "Spot", "Jeep" }); + + task.In.Reader = table.CreateDataReader(); // Act task.Execute(); @@ -34,43 +43,14 @@ public void should_return_an_object_for_each_record_returned_by_the_select_comma // Assert Assert.That(task.Out.Objects.Count(), Is.EqualTo(2)); Assert.That(task.Out.Objects[0].Name, Is.EqualTo("John Doe")); + Assert.That(task.Out.Objects[0].Age, Is.EqualTo(21)); + Assert.That(task.Out.Objects[0].Pet.Name, Is.EqualTo("Doug")); + Assert.That(task.Out.Objects[0].Vehicles[0].Make, Is.EqualTo("Dodge")); Assert.That(task.Out.Objects[1].Name, Is.EqualTo("Jane Doe")); + Assert.That(task.Out.Objects[1].Age, Is.EqualTo(19)); + Assert.That(task.Out.Objects[1].Pet.Name, Is.EqualTo("Spot")); + Assert.That(task.Out.Objects[1].Vehicles[0].Make, Is.EqualTo("Jeep")); } - [Test] - public void should_build_typed_objects_if_given_strong_type() - { - // Arrange - var task = Task.New>(); - task.In.Reader = SetupReader(); - - task.BuildTyped = Fake.Task>(bt => bt.Out.Object = new MockPerson()); - task.BuildDynamic = Fake.Task(); - - // Act - task.Execute(); - - // Assert - Assert.That(task.BuildTyped.Stats.ExecuteCount, Is.GreaterThan(0)); - Assert.That(task.BuildDynamic.Stats.ExecuteCount, Is.EqualTo(0)); - } - - [Test] - public void should_build_dynamic_objects_if_given_dynamic_type() - { - // Arrange - var task = Task.New>(); - task.In.Reader = SetupReader(); - - task.BuildTyped = Fake.Task>(); - task.BuildDynamic = Fake.Task(bd => bd.Out.Object = new MockPerson()); - - // Act - task.Execute(); - - // Assert - Assert.That(task.BuildTyped.Stats.ExecuteCount, Is.EqualTo(0)); - Assert.That(task.BuildDynamic.Stats.ExecuteCount, Is.GreaterThan(0)); - } } } \ No newline at end of file diff --git a/app/Simpler.Tests/Data/Tasks/BuildPropertyMappingTree.cs b/app/Simpler.Tests/Data/Tasks/BuildPropertyMappingTree.cs new file mode 100644 index 0000000..007be8a --- /dev/null +++ b/app/Simpler.Tests/Data/Tasks/BuildPropertyMappingTree.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Simpler.Data.Tasks; +using Simpler.Tests.Core.Mocks; + +namespace Simpler.Tests.Data.Tasks +{ + [TestFixture] + public class BuildPropertyMappingTree + { + [Test] + public void should_build_a_tree_for_primitives() + { + // Arrange + var task = Task.New(); + task.In.Columns = new Dictionary + { + { "Name", 0 }, { "Age", 1 } + }; + task.In.InitialType = typeof(MockPerson); + + // Act + task.Execute(); + + // Assert + Assert.That(task.Out.PropertyMappingTree.Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Name"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Age"], Is.Not.Null); + } + + [Test] + public void should_build_a_tree_for_nested_objects() + { + // Arrange + var task = Task.New(); + task.In.Columns = new Dictionary + { + {"PetName", 0}, {"PetAge", 1} + }; + task.In.InitialType = typeof(MockPerson); + + // Act + task.Execute(); + + // Assert + Assert.That(task.Out.PropertyMappingTree.Children.Count(), Is.EqualTo(1)); + Assert.That(task.Out.PropertyMappingTree["Pet"].Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Pet"]["Name"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Pet"]["Age"], Is.Not.Null); + } + + [Test] + public void should_build_a_tree_for_arrays() + { + // Arrange + var task = Task.New(); + task.In.Columns = new Dictionary + { + {"Vehicles0Make", 0}, {"Vehicles0Model", 1}, {"Vehicles1Make", 2}, {"Vehicles1Model", 3} + }; + task.In.InitialType = typeof(MockPerson); + + // Act + task.Execute(); + + // Assert + Assert.That(task.Out.PropertyMappingTree.Children.Count(), Is.EqualTo(1)); + Assert.That(task.Out.PropertyMappingTree["Vehicles"].Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["0"].Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["0"]["Make"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["0"]["Model"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["1"].Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["1"]["Make"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["1"]["Model"], Is.Not.Null); + } + + [Test] + public void should_build_a_tree_for_dynamics() + { + // Arrange + var task = Task.New(); + task.In.Columns = new Dictionary + { + {"OtherCity", 0}, {"OtherState", 1} + }; + task.In.InitialType = typeof(MockPerson); + + // Act + task.Execute(); + + // Assert + Assert.That(task.Out.PropertyMappingTree.Children.Count(), Is.EqualTo(1)); + Assert.That(task.Out.PropertyMappingTree["Other"].Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Other"]["State"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Other"]["City"], Is.Not.Null); + } + + [Test] + public void should_build_a_tree() + { + // Arrange + var task = Task.New(); + task.In.Columns = new Dictionary + { + { "Name", 0 }, { "Age", 1 }, {"PetName" , 2}, {"PetAge", 3}, {"Vehicles0Make", 4}, + {"Vehicles0Model", 5}, {"Vehicles1Make", 6}, {"Vehicles1Model", 7}, {"OtherCity", 8} + }; + task.In.InitialType = typeof(MockPerson); + // Act + task.Execute(); + + // Assert + Assert.That(task.Out.PropertyMappingTree.Children.Count(), Is.EqualTo(5)); + Assert.That(task.Out.PropertyMappingTree["Name"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Age"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Pet"].Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Pet"]["Name"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Pet"]["Age"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Vehicles"].Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["0"].Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["0"]["Make"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["0"]["Model"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["1"].Children.Count(), Is.EqualTo(2)); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["1"]["Make"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Vehicles"]["1"]["Model"], Is.Not.Null); + Assert.That(task.Out.PropertyMappingTree["Other"].Children.Count(), Is.EqualTo(1)); + Assert.That(task.Out.PropertyMappingTree["Other"]["City"], Is.Not.Null); + } + } +} \ No newline at end of file diff --git a/app/Simpler.Tests/Data/Tasks/BuildTypedTest.cs b/app/Simpler.Tests/Data/Tasks/BuildTypedTest.cs index ff0c7ad..fa8cabd 100644 --- a/app/Simpler.Tests/Data/Tasks/BuildTypedTest.cs +++ b/app/Simpler.Tests/Data/Tasks/BuildTypedTest.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System.Collections.Generic; +using NUnit.Framework; using Simpler.Data.Tasks; using System.Data; using Moq; @@ -13,7 +14,12 @@ public class BuildTypedTest public void should_build_an_instance_of_given_type() { // Arrange - var task = Task.New>(); + var task = Task.New>(); + var mapTask = Task.New(); + mapTask.In.InitialType = typeof(MockPerson); + mapTask.In.Columns = new Dictionary { { "Name", 0 } }; + mapTask.Execute(); + task.In.PropertyParse = mapTask.Out.PropertyMappingTree; var mockDataRecord = new Mock(); task.In.DataRecord = mockDataRecord.Object; @@ -29,14 +35,19 @@ public void should_build_an_instance_of_given_type() public void should_populate_typed_object_using_all_columns_in_the_data_record() { // Arrange - var task = Task.New>(); + var task = Task.New>(); + var mapTask = Task.New(); + mapTask.In.InitialType = typeof(MockPerson); + mapTask.In.Columns = new Dictionary { { "Name", 0 }, { "Age", 1 } }; + mapTask.Execute(); + task.In.PropertyParse = mapTask.Out.PropertyMappingTree; var mockDataRecord = new Mock(); mockDataRecord.Setup(dataRecord => dataRecord.FieldCount).Returns(2); mockDataRecord.Setup(dataRecord => dataRecord.GetName(0)).Returns("Name"); - mockDataRecord.Setup(dataRecord => dataRecord["Name"]).Returns("John Doe"); + mockDataRecord.Setup(dataRecord => dataRecord.GetValue(0)).Returns("John Doe"); mockDataRecord.Setup(dataRecord => dataRecord.GetName(1)).Returns("Age"); - mockDataRecord.Setup(dataRecord => dataRecord["Age"]).Returns(21); + mockDataRecord.Setup(dataRecord => dataRecord.GetValue(1)).Returns(21); task.In.DataRecord = mockDataRecord.Object; // Act @@ -51,28 +62,29 @@ public void should_populate_typed_object_using_all_columns_in_the_data_record() public void should_throw_exception_if_a_data_record_column_is_not_a_property_of_the_object_class() { // Arrange - var task = Task.New>(); - - var mockDataRecord = new Mock(); - mockDataRecord.Setup(dataRecord => dataRecord.FieldCount).Returns(1); - mockDataRecord.Setup(dataRecord => dataRecord.GetName(0)).Returns("SomeOtherColumn"); - mockDataRecord.Setup(dataRecord => dataRecord["SomeOtherColumn"]).Returns("whatever"); - task.In.DataRecord = mockDataRecord.Object; + var mapTask = Task.New(); + mapTask.In.InitialType = typeof(MockPerson); + mapTask.In.Columns = new Dictionary { { "Name", 0 }, { "TheCakeIsALie", 1 } }; // Act & Assert - Assert.Throws(typeof(CheckException), task.Execute); + Assert.Throws(typeof(CheckException), mapTask.Execute); } [Test] public void should_allow_object_to_have_properties_that_dont_have_matching_columns_in_the_data_record() { // Arrange - var task = Task.New>(); + var task = Task.New>(); + var mapTask = Task.New(); + mapTask.In.InitialType = typeof(MockPerson); + mapTask.In.Columns = new Dictionary { { "Name", 0 } }; + mapTask.Execute(); + task.In.PropertyParse = mapTask.Out.PropertyMappingTree; var mockDataRecord = new Mock(); mockDataRecord.Setup(dataRecord => dataRecord.FieldCount).Returns(1); mockDataRecord.Setup(dataRecord => dataRecord.GetName(0)).Returns("Name"); - mockDataRecord.Setup(dataRecord => dataRecord["Name"]).Returns("John Doe"); + mockDataRecord.Setup(dataRecord => dataRecord.GetValue(0)).Returns("John Doe"); task.In.DataRecord = mockDataRecord.Object; @@ -88,12 +100,17 @@ public void should_allow_object_to_have_properties_that_dont_have_matching_colum public void should_build_enum_properties() { // Arrange - var task = Task.New>(); + var task = Task.New>(); + var mapTask = Task.New(); + mapTask.In.InitialType = typeof(MockPerson); + mapTask.In.Columns = new Dictionary { { "MockEnum", 0 } }; + mapTask.Execute(); + task.In.PropertyParse = mapTask.Out.PropertyMappingTree; var mockDataRecord = new Mock(); mockDataRecord.Setup(dataRecord => dataRecord.FieldCount).Returns(1); mockDataRecord.Setup(dataRecord => dataRecord.GetName(0)).Returns("MockEnum"); - mockDataRecord.Setup(dataRecord => dataRecord["MockEnum"]).Returns("One"); + mockDataRecord.Setup(dataRecord => dataRecord.GetValue(0)).Returns("One"); task.In.DataRecord = mockDataRecord.Object; // Act diff --git a/app/Simpler.Tests/Data/Tasks/FetchTest.cs b/app/Simpler.Tests/Data/Tasks/FetchTest.cs deleted file mode 100644 index e263008..0000000 --- a/app/Simpler.Tests/Data/Tasks/FetchTest.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Linq; -using NUnit.Framework; -using Simpler.Data.Tasks; -using Moq; -using System.Data; -using Simpler.Tests.Core.Mocks; - -namespace Simpler.Tests.Data.Tasks -{ - [TestFixture] - public class FetchTest - { - static IDataReader SetupReader() - { - var table = new DataTable(); - table.Columns.Add("Name", Type.GetType("System.String")); - table.Columns.Add("Age", Type.GetType("System.Int32")); - table.Rows.Add(new object[] { "John Doe", "21" }); - table.Rows.Add(new object[] { "Jane Doe", "19" }); - return table.CreateDataReader(); - } - - [Test] - public void should_return_an_object_for_each_record_returned_by_the_select_command() - { - // Arrange - var task = Task.New>(); - task.In.Reader = SetupReader(); - - // Act - task.Execute(); - - // Assert - Assert.That(task.Out.ObjectsFetched.Count(), Is.EqualTo(2)); - Assert.That(task.Out.ObjectsFetched[0].Name, Is.EqualTo("John Doe")); - Assert.That(task.Out.ObjectsFetched[1].Name, Is.EqualTo("Jane Doe")); - } - - [Test] - public void should_build_typed_objects_if_given_strong_type() - { - // Arrange - var task = Task.New>(); - task.In.Reader = SetupReader(); - - task.BuildTyped = Fake.Task>(bt => bt.Out.Object = new MockObject()); - task.BuildDynamic = Fake.Task(); - - // Act - task.Execute(); - - // Assert - Assert.That(task.BuildTyped.Stats.ExecuteCount, Is.GreaterThan(0)); - Assert.That(task.BuildDynamic.Stats.ExecuteCount, Is.EqualTo(0)); - } - - [Test] - public void should_build_dynamic_objects_if_given_dynamic_type() - { - // Arrange - var task = Task.New>(); - task.In.Reader = SetupReader(); - - task.BuildTyped = Fake.Task>(); - task.BuildDynamic = Fake.Task(bd => bd.Out.Object = new MockObject()); - - // Act - task.Execute(); - - // Assert - Assert.That(task.BuildTyped.Stats.ExecuteCount, Is.EqualTo(0)); - Assert.That(task.BuildDynamic.Stats.ExecuteCount, Is.GreaterThan(0)); - } - } -} \ No newline at end of file diff --git a/app/Simpler.Tests/Data/Tasks/ParseColumnTest.cs b/app/Simpler.Tests/Data/Tasks/ParseColumnTest.cs new file mode 100644 index 0000000..81227a1 --- /dev/null +++ b/app/Simpler.Tests/Data/Tasks/ParseColumnTest.cs @@ -0,0 +1,137 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Simpler.Data; +using Simpler.Data.PropertyMappingTree; +using Simpler.Data.Tasks; +using Simpler.Tests.Core.Mocks; + +namespace Simpler.Tests.Data.Tasks +{ + [TestFixture] + public class ParseColumnTest + { + [Test] + public void should_throw_exception_if_column_name_does_not_match_any_properties() + { + //Arrange + var task = Task.New(); + task.In.RootNode = new ObjectNode + { + PropertyType = typeof(MockPerson) + }; + task.In.ColumnName = "TheCakeIsALie"; + + //Act + Assert.Throws(() => task.Execute()); + } + + [Test] + public void should_throw_exception_if_column_name_is_only_a_partial_match() + { + //Arrange + var task = Task.New(); + task.In.RootNode = new ObjectNode + { + PropertyType = typeof(MockPerson) + }; + task.In.ColumnName = "PetTheCakeIsALie"; + + //Act + Assert.Throws(() => task.Execute()); + } + + [Test] + public void should_parse_column_names() + { + //Arrange + var task = Task.New(); + task.In.RootNode = new ObjectNode + { + PropertyType = typeof (MockPerson) + }; + task.In.ColumnName = "Name"; + + //Act + task.Execute(); + + //Assert + Assert.That(task.In.RootNode["Name"], Is.Not.Null); + + //Check the node types + Assert.That(task.In.RootNode["Name"], Is.TypeOf(typeof(ObjectNode))); + } + + [Test] + public void should_parse_column_names_with_nested_objects() + { + //Arrange + var task = Task.New(); + task.In.RootNode = new ObjectNode + { + PropertyType = typeof(MockPerson) + }; + task.In.ColumnName = "PetName"; + + //Act + task.Execute(); + + //Assert + Assert.That(task.In.RootNode["Pet"], Is.Not.Null); + Assert.That(task.In.RootNode["Pet"]["Name"], Is.Not.Null); + + //Check the node types + Assert.That(task.In.RootNode["Pet"], Is.TypeOf(typeof(ObjectNode))); + Assert.That(task.In.RootNode["Pet"]["Name"], Is.TypeOf(typeof(ObjectNode))); + } + + [Test] + public void should_parse_column_names_with_arrays() + { + //Arrange + var task = Task.New(); + task.In.RootNode = new ObjectNode + { + PropertyType = typeof(MockPerson) + }; + task.In.ColumnName = "Vehicles0Make"; + + //Act + task.Execute(); + + //Assert + Assert.That(task.In.RootNode["Vehicles"], Is.Not.Null); + Assert.That(task.In.RootNode["Vehicles"]["0"], Is.Not.Null); + Assert.That(task.In.RootNode["Vehicles"]["0"]["Make"], Is.Not.Null); + + //Check the node types + Assert.That(task.In.RootNode["Vehicles"], Is.TypeOf(typeof(ArrayNode))); + Assert.That(task.In.RootNode["Vehicles"]["0"], Is.TypeOf(typeof(ArrayElementNode))); + Assert.That(task.In.RootNode["Vehicles"]["0"]["Make"], Is.TypeOf(typeof(ObjectNode))); + } + + [Test] + public void should_parse_column_names_that_are_dynamic() + { + //Arrange + var task = Task.New(); + task.In.RootNode = new ObjectNode + { + PropertyType = typeof(MockPerson) + }; + task.In.ColumnName = "OtherTheLastManStanding"; + + //Act + task.Execute(); + + //Assert + Assert.That(task.In.RootNode["Other"], Is.Not.Null); + Assert.That(task.In.RootNode["Other"]["TheLastManStanding"], Is.Not.Null); + + //Check the node types + Assert.That(task.In.RootNode["Other"], Is.TypeOf(typeof(DynamicNode))); + Assert.That(task.In.RootNode["Other"]["TheLastManStanding"], Is.TypeOf(typeof(DynamicPropertyNode))); + } + + } +} \ No newline at end of file diff --git a/app/Simpler.Tests/Simpler.Tests.csproj b/app/Simpler.Tests/Simpler.Tests.csproj index c03d0ac..4a7ee05 100644 --- a/app/Simpler.Tests/Simpler.Tests.csproj +++ b/app/Simpler.Tests/Simpler.Tests.csproj @@ -51,8 +51,10 @@ - + + + @@ -65,15 +67,22 @@ + + + + + - + + + diff --git a/app/Simpler/Data/PropertyMappingTree/AbstractNode.cs b/app/Simpler/Data/PropertyMappingTree/AbstractNode.cs new file mode 100644 index 0000000..8632516 --- /dev/null +++ b/app/Simpler/Data/PropertyMappingTree/AbstractNode.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Simpler.Data.PropertyMappingTree +{ + /// + /// + /// + public abstract class AbstractNode + { + /// + /// The property type of the current node + /// + public Type PropertyType { get; set; } + + + /// + /// A List of Node that represents the property parse tree nodes assigned to the current property parse tree node. + /// + public List Children { get; set; } + + /// + /// Creates an instance of the specified type using the constructor that best matches the specified parameters. + /// + /// The default value for the newly created object + /// A reference to the newly created object. + public abstract object CreateObject(object value = null); + + /// + /// Initializes a new instance of the RootNode class. + /// + public AbstractNode() + { + Children = new List(); + } + + public AbstractNode this[string index] + { + get + { + return Children.FirstOrDefault(x => x.Name == index); + } + + set + { + var listIndex = Children.FindIndex(x => x.Name == index); + value.Name = index; + if (listIndex == -1) + { + Children.Add(value); + } + else + { + Children[listIndex] = value; + } + } + } + + /// + /// + /// + public bool IsDynamicProperty { + get + { + return PropertyType.FullName == "System.Object"; + } + } + + + /// + /// the name of the column from the data reader this node references. + /// + public string Name { get; set; } + + /// + /// The index of the column from the data reader this node references. + /// + public int? Index { get; set; } + + /// + /// Sets the property value of a specified object. + /// + /// The object whose property value will be set. + /// The new property value. + public abstract void SetValue(object obj, object value); + + /// + /// Returns the property value of a specified object. + /// + /// The object whose property value will be returned. + /// The property value of the specified object. + public abstract object GetValue(object obj); + } +} diff --git a/app/Simpler/Data/PropertyMappingTree/ArrayElementNode.cs b/app/Simpler/Data/PropertyMappingTree/ArrayElementNode.cs new file mode 100644 index 0000000..f4b51c7 --- /dev/null +++ b/app/Simpler/Data/PropertyMappingTree/ArrayElementNode.cs @@ -0,0 +1,44 @@ +using System; + +namespace Simpler.Data.PropertyMappingTree +{ + /// + /// + /// + public class ArrayElementNode : AbstractNode + { + public override object CreateObject(object value = null) + { + if (value == null) + { + return Activator.CreateInstance(PropertyType); + } + + //I think this can be moved into a constructor + var propertyType = PropertyType; + if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + propertyType = Nullable.GetUnderlyingType(propertyType); + } + //end of thought + + if (propertyType.IsEnum) + { + value = Enum.Parse(propertyType, value.ToString()); + } + + return Convert.ChangeType(value, propertyType); + } + + public override void SetValue(object instance, object value) + { + //this int parse can be moved into a constructor + ((Array)instance).SetValue(CreateObject(value), int.Parse(Name)); + } + + public override object GetValue(object instance) + { + return ((Array) instance).GetValue(int.Parse(Name)); + } + } +} diff --git a/app/Simpler/Data/PropertyMappingTree/ArrayNode.cs b/app/Simpler/Data/PropertyMappingTree/ArrayNode.cs new file mode 100644 index 0000000..4862627 --- /dev/null +++ b/app/Simpler/Data/PropertyMappingTree/ArrayNode.cs @@ -0,0 +1,15 @@ +using System; + +namespace Simpler.Data.PropertyMappingTree +{ + /// + /// + /// + public class ArrayNode : ObjectNode + { + public override object CreateObject(object value = null) + { + return Activator.CreateInstance(PropertyType, new object[] { Children.Count }); + } + } +} diff --git a/app/Simpler/Data/PropertyMappingTree/DynamicNode.cs b/app/Simpler/Data/PropertyMappingTree/DynamicNode.cs new file mode 100644 index 0000000..4c3ba9d --- /dev/null +++ b/app/Simpler/Data/PropertyMappingTree/DynamicNode.cs @@ -0,0 +1,15 @@ +using System.Dynamic; + +namespace Simpler.Data.PropertyMappingTree +{ + /// + /// + /// + public class DynamicNode : ObjectNode + { + public override object CreateObject(object value = null) + { + return new ExpandoObject(); + } + } +} diff --git a/app/Simpler/Data/PropertyMappingTree/DynamicPropertyNode.cs b/app/Simpler/Data/PropertyMappingTree/DynamicPropertyNode.cs new file mode 100644 index 0000000..5dca5bd --- /dev/null +++ b/app/Simpler/Data/PropertyMappingTree/DynamicPropertyNode.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; + +namespace Simpler.Data.PropertyMappingTree +{ + /// + /// + /// + public class DynamicPropertyNode : AbstractNode + { + public override object CreateObject(object value = null) + { + return value; + } + + public override void SetValue(dynamic instance, object value) + { + var instanceValue = CreateObject(value); + ((ExpandoObject)instance as IDictionary)[Name] = instanceValue; + } + + public override object GetValue(dynamic instance) + { + return ((ExpandoObject)instance as IDictionary)[Name]; + } + } +} diff --git a/app/Simpler/Data/PropertyMappingTree/ObjectNode.cs b/app/Simpler/Data/PropertyMappingTree/ObjectNode.cs new file mode 100644 index 0000000..3793589 --- /dev/null +++ b/app/Simpler/Data/PropertyMappingTree/ObjectNode.cs @@ -0,0 +1,55 @@ +using System; +using System.Reflection; + +namespace Simpler.Data.PropertyMappingTree +{ + /// + /// + /// + public class ObjectNode : AbstractNode + { + /// + /// + /// + public PropertyInfo PropertyInfo { get; set; } + + public override object CreateObject(object value = null) + { + var propertyType = PropertyType; + if (value == null && propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + return null; + } + + if (value == null) + { + return Activator.CreateInstance(PropertyType, null); + } + + //I think this can be moved into a constructor + if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + propertyType = Nullable.GetUnderlyingType(propertyType); + } + //end of thought + + if (propertyType.IsEnum) + { + value = Enum.Parse(propertyType, value.ToString()); + } + + return Convert.ChangeType(value, propertyType); + } + + public override void SetValue(object instance, object value) + { + var instanceValue = CreateObject(value); + PropertyInfo.SetValue(instance, instanceValue, null); + } + + public override object GetValue(object instance) + { + return PropertyInfo.GetValue(instance, null); + } + } +} diff --git a/app/Simpler/Data/Tasks/BuildDynamic.cs b/app/Simpler/Data/Tasks/BuildDynamic.cs deleted file mode 100644 index f01ffe3..0000000 --- a/app/Simpler/Data/Tasks/BuildDynamic.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Dynamic; - -namespace Simpler.Data.Tasks -{ - public class BuildDynamic : InOutTask - { - public class Input - { - public IDataRecord DataRecord { get; set; } - } - - public class Output - { - public dynamic Object { get; set; } - } - - public override void Execute() - { - Out.Object = new ExpandoObject(); - var dictionary = Out.Object as IDictionary; - for (var i = 0; i < In.DataRecord.FieldCount; i++) - { - var columnName = In.DataRecord.GetName(i); - var columnValue = In.DataRecord[columnName]; - if (columnValue.GetType() != typeof(DBNull)) - { - dictionary.Add(columnName, columnValue); - } - } - } - } -} diff --git a/app/Simpler/Data/Tasks/BuildObject.cs b/app/Simpler/Data/Tasks/BuildObject.cs new file mode 100644 index 0000000..e33e099 --- /dev/null +++ b/app/Simpler/Data/Tasks/BuildObject.cs @@ -0,0 +1,59 @@ +using System; +using System.Data; +using System.Dynamic; +using Simpler.Data.PropertyMappingTree; + +namespace Simpler.Data.Tasks +{ + public class BuildObject : InOutTask.Input, BuildObject.Output> + { + public class Input + { + public IDataRecord DataRecord { get; set; } + public AbstractNode PropertyParse { get; set; } + } + + public class Output + { + public T Object { get; set; } + } + + public void Parse(AbstractNode node, object obj) + { + //if the node does not have a index then just set the value to the instance. + if (node.Index == null) + { + node.SetValue(obj, null); + } + else + { + //get the value from the data reader and set the value + var value = In.DataRecord.GetValue((int)node.Index); + if (value != null && value.GetType() != typeof(DBNull)) + { + node.SetValue(obj, value); + } + } + + //get the newly created object + var childObj = node.GetValue(obj); + + //continue down the parse tree + foreach (var childNode in node.Children) + { + Parse(childNode, childObj); + } + } + + public override void Execute() + { + //create the root object + var obj = In.PropertyParse.CreateObject(); + foreach (var node in In.PropertyParse.Children) + { + Parse(node, obj); + } + Out.Object = (T)obj; + } + } +} \ No newline at end of file diff --git a/app/Simpler/Data/Tasks/BuildObjects.cs b/app/Simpler/Data/Tasks/BuildObjects.cs index 2cf58d9..f6e588b 100644 --- a/app/Simpler/Data/Tasks/BuildObjects.cs +++ b/app/Simpler/Data/Tasks/BuildObjects.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Linq; namespace Simpler.Data.Tasks { @@ -16,36 +17,32 @@ public class Output public T[] Objects { get; set; } } - public BuildTyped BuildTyped { get; set; } - public BuildDynamic BuildDynamic { get; set; } + public FindColumns FindColumns { get; set; } + public BuildPropertyMappingTree BuildPropertyMappingTree { get; set; } + public BuildObject BuildObject { get; set; } public override void Execute() { - Func buildObject; - if (typeof (T).FullName == "System.Object") - { - buildObject = reader => - { - BuildDynamic.In.DataRecord = reader; - BuildDynamic.Execute(); - return BuildDynamic.Out.Object; - }; - } - else - { - buildObject = reader => - { - BuildTyped.In.DataRecord = reader; - BuildTyped.Execute(); - return BuildTyped.Out.Object; - }; - } - var objectList = new List(); - while (In.Reader.Read()) + + //read the first record off and determine the column mappings + In.Reader.Read(); + + FindColumns.In.Reader = In.Reader; + FindColumns.Execute(); + + BuildPropertyMappingTree.In.Columns = FindColumns.Out.Columns; + BuildPropertyMappingTree.In.InitialType = typeof (T); + BuildPropertyMappingTree.Execute(); + + do { - objectList.Add(buildObject(In.Reader)); - } + BuildObject.In.PropertyParse = BuildPropertyMappingTree.Out.PropertyMappingTree; + BuildObject.In.DataRecord = In.Reader; + BuildObject.Execute(); + objectList.Add(BuildObject.Out.Object); + } while (In.Reader.Read()); + Out.Objects = objectList.ToArray(); } } diff --git a/app/Simpler/Data/Tasks/BuildPropertyMappingTree.cs b/app/Simpler/Data/Tasks/BuildPropertyMappingTree.cs new file mode 100644 index 0000000..d911572 --- /dev/null +++ b/app/Simpler/Data/Tasks/BuildPropertyMappingTree.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Reflection; +using Simpler.Data.PropertyMappingTree; + +namespace Simpler.Data.Tasks +{ + public class BuildPropertyMappingTree : InOutTask + { + public class Input + { + public Dictionary Columns { get; set; } + public Type InitialType { get; set; } + } + + public class Output + { + public AbstractNode PropertyMappingTree { get; set; } + } + + public override void Execute() + { + var parseColumn = new ParseColumn(); + + if (In.InitialType.FullName == "System.Object") + { + parseColumn.In.RootNode = new DynamicNode{ PropertyType = In.InitialType }; + } + else + { + parseColumn.In.RootNode = new ObjectNode{ PropertyType = In.InitialType }; + } + + //order by the longest column names first + var columns = In.Columns.OrderByDescending(x => x.Key.Length); + foreach (var column in columns) + { + parseColumn.In.ColumnName = column.Key; + parseColumn.In.ColumnIndex = column.Value; + parseColumn.Execute(); + } + Out.PropertyMappingTree = parseColumn.In.RootNode; + } + } +} diff --git a/app/Simpler/Data/Tasks/BuildTyped.cs b/app/Simpler/Data/Tasks/BuildTyped.cs deleted file mode 100644 index 523e686..0000000 --- a/app/Simpler/Data/Tasks/BuildTyped.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Data; - -namespace Simpler.Data.Tasks -{ - public class BuildTyped : InOutTask.Input, BuildTyped.Output> - { - public class Input - { - public IDataRecord DataRecord { get; set; } - } - - public class Output - { - public T Object { get; set; } - } - - public override void Execute() - { - Out.Object = (T)Activator.CreateInstance(typeof(T)); - var objectType = typeof(T); - - for (var i = 0; i < In.DataRecord.FieldCount; i++) - { - var columnName = In.DataRecord.GetName(i); - var propertyInfo = objectType.GetProperty(columnName); - - Check.That(propertyInfo != null, - "The DataRecord contains column '{0}' that is not a property of the '{1}' class.", - columnName, - objectType.FullName); - - var columnValue = In.DataRecord[columnName]; - if (columnValue.GetType() != typeof(DBNull)) - { - var propertyType = propertyInfo.PropertyType; - - if (propertyType.IsEnum) - { - propertyInfo.SetValue(Out.Object,Enum.Parse(propertyType,columnValue.ToString()),null); - continue; - } - - if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - propertyType = Nullable.GetUnderlyingType(propertyInfo.PropertyType); - } - - columnValue = Convert.ChangeType(columnValue, propertyType); - propertyInfo.SetValue(Out.Object, columnValue, null); - } - } - } - } -} diff --git a/app/Simpler/Data/Tasks/FindColumns.cs b/app/Simpler/Data/Tasks/FindColumns.cs new file mode 100644 index 0000000..cd6efbc --- /dev/null +++ b/app/Simpler/Data/Tasks/FindColumns.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Reflection; +using Simpler.Data.PropertyMappingTree; + +namespace Simpler.Data.Tasks +{ + public class FindColumns : InOutTask + { + public class Input + { + public IDataReader Reader { get; set; } + } + + public class Output + { + public Dictionary Columns { get; set; } + } + + public override void Execute() + { + Out.Columns = new Dictionary(); + for (var i = 0; i < In.Reader.FieldCount; i++) + { + var columnName = In.Reader.GetName(i); + Check.That(!Out.Columns.ContainsKey(columnName), "The DataRecord contains a duplicate column '{0}'.", columnName); + Out.Columns[columnName] = i; + } + } + } +} diff --git a/app/Simpler/Data/Tasks/ParseColumn.cs b/app/Simpler/Data/Tasks/ParseColumn.cs new file mode 100644 index 0000000..de24c34 --- /dev/null +++ b/app/Simpler/Data/Tasks/ParseColumn.cs @@ -0,0 +1,103 @@ +using System; +using System.Linq; +using Simpler.Data.PropertyMappingTree; + +namespace Simpler.Data.Tasks +{ + public class ParseColumn : InTask + { + public class Input + { + public int ColumnIndex { get; set; } + public string ColumnName { get; set; } + public AbstractNode RootNode { get; set; } + } + + private void ParseColumnName(AbstractNode node, string path = "") + { + var remainingPath = In.ColumnName.Substring(path.Length); + //check if we have reached the end of the path + if (remainingPath.Length == 0) + { + node.Index = In.ColumnIndex; + return; + } + + var propertyType = node.PropertyType; + + //if the parent is a dyanmic no need to look at properties just set it + if (node is DynamicNode) + { + node[remainingPath] = new DynamicPropertyNode(); + ParseColumnName(node[remainingPath], path + remainingPath); + return; + } + + //if the parent is an array we need to pull the index off and create a TreeNode. + if (node is ArrayNode) + { + //pull the index off the remainingPath + var index = new String(remainingPath.TakeWhile(Char.IsDigit).ToArray()); + + //create the TreeNode if it doesn't exist + if(node.Children.All(x => x.Name != index)) + { + node[index] = new ArrayElementNode + { + PropertyType = propertyType.GetElementType() + }; + } + + ParseColumnName(node[index], path + index); + return; + } + + //attempt to find an exact match + var propertyInfo = propertyType.GetProperty(remainingPath); + if (propertyInfo == null) + { + //if we can't find an exact match find a property that starts with the remmaining path + propertyInfo = propertyType.GetProperties().FirstOrDefault(x => remainingPath.StartsWith(x.Name)); + } + + Check.That(propertyInfo != null, "The DataRecord contains a column '{0}' that does not match a property or nested property.", In.ColumnName); + + //check if the node already exisits in the parse tree + if (!node.Children.Any(x => x.Name == propertyInfo.Name)) + { + //determine the type of the node and add it to the parse tree + if (propertyInfo.PropertyType.FullName == "System.Object") + { + node[propertyInfo.Name] = new DynamicNode + { + PropertyInfo = propertyInfo, + PropertyType = propertyInfo.PropertyType + }; + } + else if (propertyInfo.PropertyType.IsArray) + { + node[propertyInfo.Name] = new ArrayNode + { + PropertyInfo = propertyInfo, + PropertyType = propertyInfo.PropertyType + }; + } + else + { + node[propertyInfo.Name] = new ObjectNode + { + PropertyInfo = propertyInfo, + PropertyType = propertyInfo.PropertyType + }; + } + } + + ParseColumnName(node[propertyInfo.Name], path + propertyInfo.Name); + } + + public override void Execute() + { + ParseColumnName(In.RootNode); + } + } +} diff --git a/app/Simpler/Simpler.csproj b/app/Simpler/Simpler.csproj index 32f9920..ebf795e 100644 --- a/app/Simpler/Simpler.csproj +++ b/app/Simpler/Simpler.csproj @@ -52,10 +52,19 @@ + + + + + + - + + + + @@ -65,7 +74,6 @@ -