diff --git a/src/SQLite.cs b/src/SQLite.cs index 0ce075e0..d1a07a10 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -2817,23 +2817,54 @@ public IEnumerable ExecuteDeferredQuery (TableMapping map) _conn.Tracer?.Invoke ("Executing Query: " + this); } + var values = new Dictionary> (); var stmt = Prepare (); try { var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; for (int i = 0; i < cols.Length; i++) { var name = SQLite3.ColumnName16 (stmt, i); - cols[i] = map.FindColumn (name); + if (name.Contains (".")) { + var properties = name.Split ('.'); + if (properties.Length == 2) + values[i] = Tuple.Create (properties[0], properties[1]); + } + else { + cols[i] = map.FindColumn (name); + } } while (SQLite3.Step (stmt) == SQLite3.Result.Row) { var obj = Activator.CreateInstance (map.MappedType); for (int i = 0; i < cols.Length; i++) { - if (cols[i] == null) - continue; - var colType = SQLite3.ColumnType (stmt, i); - var val = ReadCol (stmt, i, colType, cols[i].ColumnType); - cols[i].SetValue (obj, val); + if (cols[i] == null) { + if (!values.ContainsKey (i)) + continue; + + var valueStore = values[i]; + string propertyName = valueStore.Item1; + string subPropertyName = valueStore.Item2; + var column = map.Columns.FirstOrDefault (x => x.Name == propertyName); + var property = column?.ColumnType?.GetRuntimeProperty (subPropertyName); + if (column is null || property is null) + continue; + + var colType = SQLite3.ColumnType (stmt, i); + var val = ReadCol (stmt, i, colType, property.PropertyType); + var nestedObject = (obj.GetType ().GetRuntimeProperty (propertyName)).GetValue (obj); + if (nestedObject is null) { + var newObjectType = (obj.GetType ().GetRuntimeProperty (propertyName)).PropertyType; + nestedObject = Activator.CreateInstance (newObjectType); + (obj.GetType ().GetRuntimeProperty (propertyName)).SetValue (obj, nestedObject); + } + var nestedObjectProperty = nestedObject?.GetType ().GetRuntimeProperty (subPropertyName); + nestedObjectProperty?.SetValue (nestedObject, val); + } + else { + var colType = SQLite3.ColumnType (stmt, i); + var val = ReadCol (stmt, i, colType, cols[i].ColumnType); + cols[i].SetValue (obj, val); + } } OnInstanceCreated (obj); yield return (T)obj; diff --git a/tests/NestedObjectPropertyMappingTest.cs b/tests/NestedObjectPropertyMappingTest.cs new file mode 100644 index 00000000..ab0331e1 --- /dev/null +++ b/tests/NestedObjectPropertyMappingTest.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; + +#if NETFX_CORE +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#else +using NUnit.Framework; +#endif + +namespace SQLite.Tests +{ + [TestFixture] + public class NestedObjectPropertyMappingTest + { + SQLiteConnection db; + + int fooId = 1; + int fooReferenceId = 3; + string fooName = "Foo Table"; + string fooDescription = "The main table."; + string fooReferenceName = "Referenced table."; + string fooReferenceDescription = "A table referenced by the main table"; + + class FooTable + { + [PrimaryKey] + public int Id { get; set; } + + public string FooName { get; set; } + + public string FooDescription { get; set; } + + public int FooTableReferenceId { get; set; } + } + + class FooTableReference + { + [PrimaryKey] + public int Id { get; set; } + + public string ReferenceName { get; set; } + + public string ReferenceDescription { get; set; } + } + + class FooDTO + { + public int Id { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public FooNestedObject NestedObject { get; set; } + } + + class FooNestedObject + { + public int Id { get; set; } + + public string NestedName { get; set; } + + public string NestedDescription { get; set; } + + public FooNestedObject NestedChild { get; set; } + } + + public NestedObjectPropertyMappingTest() + { + SetupDb (); + } + + void SetupDb() + { + db = new TestDb (); + + db.CreateTable (); + db.CreateTable (); + + db.Insert (new FooTable { + Id = fooId, + FooName = fooName, + FooDescription = fooDescription, + FooTableReferenceId = fooReferenceId + }); + db.Insert (new FooTableReference { + Id = fooReferenceId, + ReferenceName = fooReferenceName, + ReferenceDescription = fooReferenceDescription, + }); + } + + [Test] + public void SelectNestedObject () + { + string query = + $" select" + + $" fooTable.{nameof (FooTable.Id)} as '{nameof (FooDTO.Id)}'," + + $" fooTable.{nameof (FooTable.FooName)} as '{nameof (FooDTO.Name)}'," + + $" fooTable.{nameof (FooTable.FooDescription)} as '{nameof (FooDTO.Description)}'," + + $" fooTableReference.{nameof (FooTableReference.Id)} as '{nameof (FooDTO.NestedObject)}.{nameof (FooNestedObject.Id)}'," + + $" fooTableReference.{nameof (FooTableReference.ReferenceName)} as '{nameof (FooDTO.NestedObject)}.{nameof (FooNestedObject.NestedName)}'," + + $" fooTableReference.{nameof (FooTableReference.ReferenceDescription)} as '{nameof (FooDTO.NestedObject)}.{nameof (FooNestedObject.NestedDescription)}'" + + $" from {nameof (FooTable)} as fooTable" + + $" join {nameof (FooTableReference)} fooTableReference on fooTable.{nameof (FooTable.FooTableReferenceId)} = fooTableReference.{nameof (FooTableReference.Id)}" + + $" where fooTable.{nameof (FooTable.Id)} = {fooId}"; + var dtoArray = db.Query (query); + var dto = dtoArray[0]; + + Assert.AreEqual (dto.Id, fooId); + Assert.AreEqual (dto.Name, fooName); + Assert.AreEqual (dto.Description, fooDescription); + Assert.IsNotNull (dto.NestedObject); + Assert.AreEqual (dto.NestedObject.Id, fooReferenceId); + Assert.AreEqual (dto.NestedObject.NestedName, fooReferenceName); + Assert.AreEqual (dto.NestedObject.NestedDescription, fooReferenceDescription); + } + + [Test] + public void SelectNestedObjectChildObjectProperty () + { + string query = + $" select" + + $" fooTable.{nameof (FooTable.Id)} as '{nameof (FooDTO.Id)}'," + + $" fooTable.{nameof (FooTable.FooName)} as '{nameof (FooDTO.Name)}'," + + $" fooTable.{nameof (FooTable.FooDescription)} as '{nameof (FooDTO.Description)}'," + + $" fooTableReference.{nameof (FooTableReference.Id)} as '{nameof (FooDTO.NestedObject)}.{nameof (FooNestedObject.Id)}'," + + $" fooTableReference.{nameof (FooTableReference.ReferenceName)} as '{nameof (FooDTO.NestedObject)}.{nameof (FooNestedObject.NestedName)}'," + + $" fooTableReference.{nameof (FooTableReference.ReferenceDescription)} as '{nameof (FooDTO.NestedObject)}.{nameof (FooNestedObject.NestedChild)}.{nameof (FooNestedObject.NestedDescription)}'" + + $" from {nameof (FooTable)} as fooTable" + + $" join {nameof (FooTableReference)} fooTableReference on fooTable.{nameof (FooTable.FooTableReferenceId)} = fooTableReference.{nameof (FooTableReference.Id)}" + + $" where fooTable.{nameof (FooTable.Id)} = {fooId}"; + var dtoArray = db.Query (query); + var dto = dtoArray[0]; + + Assert.AreEqual (dto.Id, fooId); + Assert.AreEqual (dto.Name, fooName); + Assert.AreEqual (dto.Description, fooDescription); + Assert.IsNotNull (dto.NestedObject); + Assert.AreEqual (dto.NestedObject.Id, fooReferenceId); + Assert.AreEqual (dto.NestedObject.NestedName, fooReferenceName); + + //Does not map to a nested object of a nested object - only goes one level deep. + Assert.IsNull (dto.NestedObject.NestedChild); + } + + [Test] + public void SelectNonExistentNestedObjectProperty () + { + string query = + $" select" + + $" fooTable.{nameof (FooTable.Id)} as '{nameof (FooDTO.Id)}'," + + $" fooTable.{nameof (FooTable.FooName)} as '{nameof (FooDTO.Name)}'," + + $" fooTable.{nameof (FooTable.FooDescription)} as '{nameof (FooDTO.Description)}'," + + $" fooTableReference.{nameof (FooTableReference.Id)} as '{nameof (FooDTO.NestedObject)}.{nameof (FooNestedObject.Id)}'," + + $" fooTableReference.{nameof (FooTableReference.ReferenceName)} as '{nameof (FooDTO.NestedObject)}.{nameof (FooNestedObject.NestedName)}'," + + $" fooTableReference.{nameof (FooTableReference.ReferenceDescription)} as '{nameof (FooDTO.NestedObject)}.Foo'" + + $" from {nameof (FooTable)} as fooTable" + + $" join {nameof (FooTableReference)} fooTableReference on fooTable.{nameof (FooTable.FooTableReferenceId)} = fooTableReference.{nameof (FooTableReference.Id)}" + + $" where fooTable.{nameof (FooTable.Id)} = {fooId}"; + var dtoArray = db.Query (query); + var dto = dtoArray[0]; + + Assert.AreEqual (dto.Id, fooId); + Assert.AreEqual (dto.Name, fooName); + Assert.AreEqual (dto.Description, fooDescription); + Assert.IsNotNull (dto.NestedObject); + Assert.AreEqual (dto.NestedObject.Id, fooReferenceId); + Assert.AreEqual (dto.NestedObject.NestedName, fooReferenceName); + Assert.IsNull (dto.NestedObject.NestedDescription); + } + } +} + diff --git a/tests/SQLite.Tests.csproj b/tests/SQLite.Tests.csproj index 77d8dc7a..6ab0361d 100644 --- a/tests/SQLite.Tests.csproj +++ b/tests/SQLite.Tests.csproj @@ -84,6 +84,7 @@ + diff --git a/tests/SQLite.Tests.iOS/SQLiteTestsiOS.csproj b/tests/SQLite.Tests.iOS/SQLiteTestsiOS.csproj index 65c2bb89..c90e67db 100644 --- a/tests/SQLite.Tests.iOS/SQLiteTestsiOS.csproj +++ b/tests/SQLite.Tests.iOS/SQLiteTestsiOS.csproj @@ -153,6 +153,9 @@ QueryTest.cs + + NestedObjectPropertyMappingTest.cs + \ No newline at end of file