Skip to content

Commit

Permalink
Enable searching wothing multiple child properties
Browse files Browse the repository at this point in the history
  • Loading branch information
ninjanye committed Nov 9, 2015
1 parent 7e80004 commit e1c4cd2
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Linq;
using NinjaNye.SearchExtensions.Tests.Integration.Models;
using NUnit.Framework;

namespace NinjaNye.SearchExtensions.Tests.Integration.Fluent.SearchTests.SearchChildren
{
[TestFixture]
internal class SearchChildrenWithChainingTests : IDisposable
{
private readonly TestContext _context = new TestContext();

[Test]
public void SearchChildren_SearchStringAndInteger_ResultsMatchBothOccurences()
{
//Arrange

//Act
var result = _context.TestModels.SearchChildren(x => x.Children)
.With(c => c.IntegerOne)
.EqualTo(101)
.With(c => c.StringOne)
.EqualTo("child test")
.ToList();

//Assert
Assert.That(result.Count, Is.EqualTo(1));
Assert.That(result.Any(tm => tm.Id == Guid.Parse("F672552D-2787-468D-8D2E-DE1E88F83E21")));
}

[Test]
public void SearchChildren_SearchIntegerAndString_ResultsMatchBothOccurences()
{
//Arrange

//Act
var result = _context.TestModels.SearchChildren(x => x.Children)
.With(c => c.StringOne)
.EqualTo("child test")
.With(c => c.IntegerOne)
.EqualTo(101)
.ToList();

//Assert
Assert.That(result.Count, Is.EqualTo(1));
Assert.That(result.Any(tm => tm.Id == Guid.Parse("F672552D-2787-468D-8D2E-DE1E88F83E21")));
}


public void Dispose()
{
this._context.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<Compile Include="Fluent\SearchTests\SearchChildren\LessThanTests.cs" />
<Compile Include="Fluent\SearchTests\SearchChildren\GreaterThanTests.cs" />
<Compile Include="Fluent\SearchTests\SearchChildren\EqualToTests.cs" />
<Compile Include="Fluent\SearchTests\SearchChildren\SearchChildrenWithChainingTests.cs" />
<Compile Include="Fluent\SearchTests\SearchChildren\StringTests.cs" />
<Compile Include="Fluent\SearchTests\SearchChildren\SearchChildrenTests.cs" />
<Compile Include="Fluent\StructSearchTests\BetweenTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<Compile Include="SearchExtensionTests\IEnumerableTests\IntegerSearchTests.cs" />
<Compile Include="SearchExtensionTests\IEnumerableTests\IsEqualTests.cs" />
<Compile Include="SearchExtensionTests\IEnumerableTests\LevenshteinSearch.cs" />
<Compile Include="SearchExtensionTests\IEnumerableTests\SearchChildrenChaingingTests.cs" />
<Compile Include="SearchExtensionTests\IEnumerableTests\SearchChildrenForStringTests.cs" />
<Compile Include="SearchExtensionTests\IEnumerableTests\SearchChildrenTests.cs" />
<Compile Include="SearchExtensionTests\IEnumerableTests\StartsWithTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace NinjaNye.SearchExtensions.Tests.SearchExtensionTests.IEnumerableTests
{
[TestFixture]
public class SearchChildrenChaingingTests
{
private ParentTestData _parent;
private List<ParentTestData> _testData;
private TestData _dataOne;
private TestData _dataFour;
private TestData _dataTwo;
private TestData _dataThree;
private ParentTestData _otherParent;

[SetUp]
public void SetUp()
{
this._dataOne = new TestData {Name = "chris", Description = "child data", Number = 1, Age = 20};
this._dataTwo = new TestData {Name = "fred", Description = "child data", Number = 20, Age = 30};
this._dataThree = new TestData {Name = "teddy", Description = "child data", Number = 2, Age = 40};
this._dataFour = new TestData {Name = "josh", Description = "child data", Number = 20, Age = 50};
this._parent = new ParentTestData
{
Children = new List<TestData> {this._dataOne, this._dataTwo},
};
this._otherParent = new ParentTestData
{
Children = new List<TestData> {this._dataThree, this._dataFour},
};
this._testData = new List<ParentTestData> {this._parent, this._otherParent};
}

[Test]
public void SearchChildren_SearchStringAndInteger_ResultsMatchBothOccurences()
{
//Arrange

//Act
var result = _testData.SearchChildren(x => x.Children)
.With(c => c.Number)
.EqualTo(20)
.With(c => c.Name)
.EqualTo("josh")
.ToList();

//Assert
Assert.That(result.Count, Is.EqualTo(1));
CollectionAssert.Contains(result, _otherParent);
}

}
}
59 changes: 32 additions & 27 deletions NinjaNye.SearchExtensions/EnumerableChildSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,26 @@ public class EnumerableChildSearch<TParent, TChild, TProperty> : IEnumerable<TPa
private readonly IEnumerable<TParent> _parent;
private readonly Expression<Func<TParent, IEnumerable<TChild>>>[] _childProperties;
private readonly Expression<Func<TChild, TProperty>>[] _properties;
private Expression _completeExpression;
private readonly ParameterExpression _parentParameter;
private readonly ParameterExpression _childParameter = Expression.Parameter(typeof(TChild), "child");
private Expression _completeExpression;

public EnumerableChildSearch(IEnumerable<TParent> parent, Expression<Func<TParent, IEnumerable<TChild>>>[] childProperties, Expression<Func<TChild, TProperty>>[] properties)
{
this._parent = parent;
this._childProperties = childProperties;
this._parentParameter = childProperties[0].Parameters[0];

_childProperties = this.AlignParameters(childProperties, this._parentParameter).ToArray();
_properties = this.AlignParameters(properties, this._childParameter).ToArray();
}

var swappedProperties = new List<Expression<Func<TChild, TProperty>>>();
foreach (var property in properties)
private IEnumerable<Expression<Func<TSource, TResult>>> AlignParameters<TSource, TResult>(Expression<Func<TSource, TResult>>[] properties, ParameterExpression parameterExpression)
{
for (int i = 0; i < properties.Length; i++)
{
var swappedProperty = SwapExpressionVisitor.Swap(property, property.Parameters.Single(), this._childParameter);
swappedProperties.Add(swappedProperty);
var property = properties[i];
yield return SwapExpressionVisitor.Swap(property, property.Parameters.Single(), parameterExpression);
}

_properties = swappedProperties.ToArray();
}

/// <summary>
Expand Down Expand Up @@ -104,33 +108,34 @@ public EnumerableChildSearch<TParent, TChild, TProperty> EqualTo(params TPropert
return this;
}

public EnumerableChildSearch<TParent, TChild, TAnotherProperty> With<TAnotherProperty>(params Expression<Func<TChild, TAnotherProperty>>[] properties)
{
return new EnumerableChildSearch<TParent, TChild, TAnotherProperty>(this.UpdatedSource(), _childProperties, properties);
}

public IEnumerator<TParent> GetEnumerator()
{
return this._completeExpression == null ? this._parent.GetEnumerator()
: this.GetEnumeratorWithoutChecks();
return this.UpdatedSource().GetEnumerator();
}

private IEnumerator<TParent> GetEnumeratorWithoutChecks()
private IEnumerable<TParent> UpdatedSource()
{
var finalExpression = Expression.Lambda<Func<TChild, bool>>(this._completeExpression, this._childParameter).Compile();
foreach (var parent in this._parent.ToList())
if (_completeExpression == null)
{
return _parent;
}

var anyMethodInfo = ExpressionMethods.AnyQueryableMethod.MakeGenericMethod(typeof(TChild));
Expression finalExpression = null;
foreach (var childProperty in _childProperties)
{
foreach (var childProperty in _childProperties)
{
var children = childProperty.Compile().Invoke(parent);
if (children == null)
{
break;
}
var isMatch = children.Any(c => finalExpression.Invoke(c));
if (isMatch)
{
yield return parent;
break;
}
}
var anyExpression = Expression.Lambda<Func<TChild, bool>>(this._completeExpression, this._childParameter);
var anyChild = Expression.Call(null, anyMethodInfo, childProperty.Body, anyExpression);
finalExpression = ExpressionHelper.JoinOrExpression(finalExpression, anyChild);
}

var final = Expression.Lambda<Func<TParent, bool>>(finalExpression, this._parentParameter).Compile();
return this._parent.Where(final);
}

IEnumerator IEnumerable.GetEnumerator()
Expand Down
21 changes: 14 additions & 7 deletions NinjaNye.SearchExtensions/QueryableChildSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace NinjaNye.SearchExtensions
{
public class QueryableChildSearch<TParent, TChild, TProperty> : IQueryable<TParent>
{
private readonly IQueryable<TParent> _parent;
private IQueryable<TParent> _parent;
private readonly Expression<Func<TParent, IEnumerable<TChild>>>[] _childProperties;
private Expression<Func<TChild, TProperty>>[] _properties;
private readonly ParameterExpression _childParameter = Expression.Parameter(typeof(TChild), "child");
Expand Down Expand Up @@ -96,6 +96,11 @@ public QueryableChildSearch<TParent, TChild, TProperty> Between(TProperty minval
return this;
}

public QueryableChildSearch<TParent, TChild, TAnotherProperty> With<TAnotherProperty>(params Expression<Func<TChild, TAnotherProperty>>[] properties)
{
return new QueryableChildSearch<TParent, TChild, TAnotherProperty>(this.UpdatedSource(), _childProperties, properties);
}

private Expression<Func<TChild, TProperty>>[] AlignParameters(Expression<Func<TChild, TProperty>>[] properties)
{
var swappedProperties = new List<Expression<Func<TChild, TProperty>>>();
Expand All @@ -110,21 +115,23 @@ private Expression<Func<TChild, TProperty>>[] AlignParameters(Expression<Func<TC

public IEnumerator<TParent> GetEnumerator()
{
return this._completeExpression == null ? this._parent.GetEnumerator()
: this.BuildEnumerator();
return this.UpdatedSource().GetEnumerator();
}

private IEnumerator<TParent> BuildEnumerator()
private IQueryable<TParent> UpdatedSource()
{
var childProperty = this._childProperties[0];
if (_completeExpression == null)
{
return _parent;
}

var childProperty = this._childProperties[0];
var methodInfo = ExpressionMethods.AnyQueryableMethod.MakeGenericMethod(typeof (TChild));
var anyExpression = Expression.Lambda<Func<TChild, bool>>(this._completeExpression, this._childParameter);

var anyChild = Expression.Call(null, methodInfo, childProperty.Body, anyExpression);
var finalExpression = Expression.Lambda<Func<TParent, bool>>(anyChild, childProperty.Parameters[0]);

return this._parent.Where(finalExpression).GetEnumerator();
return this._parent.Where(finalExpression);
}

IEnumerator IEnumerable.GetEnumerator()
Expand Down
22 changes: 15 additions & 7 deletions NinjaNye.SearchExtensions/QueryableChildStringSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ public QueryableChildStringSearch<TParent, TChild> Matching(SearchType searchTyp
return this;
}

public QueryableChildSearch<TParent, TChild, TAnotherProperty> With<TAnotherProperty>(params Expression<Func<TChild, TAnotherProperty>>[] properties)
{
return new QueryableChildSearch<TParent, TChild, TAnotherProperty>(this.UpdatedSource(), _childProperties, properties);
}


private Expression<Func<TChild, string>>[] AlignParameters(Expression<Func<TChild, string>>[] properties)
{
var swappedProperties = new List<Expression<Func<TChild, string>>>();
Expand All @@ -116,21 +122,23 @@ private Expression<Func<TChild, string>>[] AlignParameters(Expression<Func<TChil

public IEnumerator<TParent> GetEnumerator()
{
return this._completeExpression == null ? this._parent.GetEnumerator()
: this.BuildEnumerator();
return this.UpdatedSource().GetEnumerator();
}

private IEnumerator<TParent> BuildEnumerator()
private IQueryable<TParent> UpdatedSource()
{
var childProperty = this._childProperties[0];
if (_completeExpression == null)
{
return _parent;
}

var methodInfo = ExpressionMethods.AnyQueryableMethod.MakeGenericMethod(typeof (TChild));
var childProperty = this._childProperties[0];
var methodInfo = ExpressionMethods.AnyQueryableMethod.MakeGenericMethod(typeof(TChild));
var anyExpression = Expression.Lambda<Func<TChild, bool>>(this._completeExpression, this._childParameter);

var anyChild = Expression.Call(null, methodInfo, childProperty.Body, anyExpression);
var finalExpression = Expression.Lambda<Func<TParent, bool>>(anyChild, childProperty.Parameters[0]);

return this._parent.Where(finalExpression).GetEnumerator();
return this._parent.Where(finalExpression);
}

IEnumerator IEnumerable.GetEnumerator()
Expand Down

0 comments on commit e1c4cd2

Please sign in to comment.