Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for mapping columns to nested object properties #815

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions src/SQLite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2817,23 +2817,54 @@ public IEnumerable<T> ExecuteDeferredQuery<T> (TableMapping map)
_conn.Tracer?.Invoke ("Executing Query: " + this);
}

var values = new Dictionary<int, Tuple<string, string>> ();
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)
jasonmcgraw marked this conversation as resolved.
Show resolved Hide resolved
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;
Expand Down
177 changes: 177 additions & 0 deletions tests/NestedObjectPropertyMappingTest.cs
Original file line number Diff line number Diff line change
@@ -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<FooTable> ();
db.CreateTable<FooTableReference> ();

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<FooDTO> (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<FooDTO> (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<FooDTO> (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);
}
}
}

1 change: 1 addition & 0 deletions tests/SQLite.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<Compile Include="BackupTest.cs" />
<Compile Include="ReadmeTest.cs" />
<Compile Include="QueryTest.cs" />
<Compile Include="NestedObjectPropertyMappingTest.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down
3 changes: 3 additions & 0 deletions tests/SQLite.Tests.iOS/SQLiteTestsiOS.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
<Compile Include="..\QueryTest.cs">
<Link>QueryTest.cs</Link>
</Compile>
<Compile Include="..\NestedObjectPropertyMappingTest.cs">
<Link>NestedObjectPropertyMappingTest.cs</Link>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
</Project>