Skip to content

Commit 7330281

Browse files
authored
Merge pull request #78 from Maschmi/groupjoin
New feature: GroupJoin
2 parents 640bd49 + 3eb203d commit 7330281

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-0
lines changed

src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

+69
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,75 @@ static IEnumerable<GroupResult> GroupByManyInternal<TElement>(IEnumerable<TEleme
431431
}
432432
#endregion GroupByMany
433433

434+
#region GroupJoin
435+
/// <summary>
436+
/// Correlates the elements of two sequences based on equality of keys and groups the results. The default equality comparer is used to compare keys.
437+
/// </summary>
438+
/// <param name="outer">The first sequence to join.</param>
439+
/// <param name="inner">The sequence to join to the first sequence.</param>
440+
/// <param name="outerKeySelector">A dynamic function to extract the join key from each element of the first sequence.</param>
441+
/// <param name="innerKeySelector">A dynamic function to extract the join key from each element of the second sequence.</param>
442+
/// <param name="resultSelector">A dynamic function to create a result element from an element from the first sequence and a collection of matching elements from the second sequence.</param>
443+
/// <param name="args">An object array that contains zero or more objects to insert into the predicates as parameters. Similar to the way String.Format formats strings.</param>
444+
/// <returns>An <see cref="IQueryable"/> obtained by performing a grouped join on two sequences.</returns>
445+
public static IQueryable GroupJoin([NotNull] this IQueryable outer, [NotNull] IEnumerable inner, [NotNull] string outerKeySelector, [NotNull] string innerKeySelector, [NotNull] string resultSelector, params object[] args)
446+
{
447+
Check.NotNull(outer, nameof(outer));
448+
Check.NotNull(inner, nameof(inner));
449+
Check.NotEmpty(outerKeySelector, nameof(outerKeySelector));
450+
Check.NotEmpty(innerKeySelector, nameof(innerKeySelector));
451+
Check.NotEmpty(resultSelector, nameof(resultSelector));
452+
453+
Type outerType = outer.ElementType;
454+
Type innerType = inner.AsQueryable().ElementType;
455+
456+
bool createParameterCtor = outer.IsLinqToObjects();
457+
LambdaExpression outerSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, outerType, null, outerKeySelector, args);
458+
LambdaExpression innerSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, innerType, null, innerKeySelector, args);
459+
460+
Type outerSelectorReturnType = outerSelectorLambda.Body.Type;
461+
Type innerSelectorReturnType = innerSelectorLambda.Body.Type;
462+
463+
// If types are not the same, try to convert to Nullable and generate new LambdaExpression
464+
if (outerSelectorReturnType != innerSelectorReturnType)
465+
{
466+
if (ExpressionParser.IsNullableType(outerSelectorReturnType) && !ExpressionParser.IsNullableType(innerSelectorReturnType))
467+
{
468+
innerSelectorReturnType = ExpressionParser.ToNullableType(innerSelectorReturnType);
469+
innerSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, innerType, innerSelectorReturnType, innerKeySelector, args);
470+
}
471+
else if (!ExpressionParser.IsNullableType(outerSelectorReturnType) && ExpressionParser.IsNullableType(innerSelectorReturnType))
472+
{
473+
outerSelectorReturnType = ExpressionParser.ToNullableType(outerSelectorReturnType);
474+
outerSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, outerType, outerSelectorReturnType, outerKeySelector, args);
475+
}
476+
477+
// If types are still not the same, throw an Exception
478+
if (outerSelectorReturnType != innerSelectorReturnType)
479+
{
480+
throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.IncompatibleTypes, outerType, innerType), -1);
481+
}
482+
}
483+
484+
ParameterExpression[] parameters =
485+
{
486+
Expression.Parameter(outerType, "outer"),
487+
Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(inner.AsQueryable().ElementType), "inner")
488+
};
489+
490+
LambdaExpression resultSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, parameters, null, resultSelector, args);
491+
492+
return outer.Provider.CreateQuery(Expression.Call(
493+
typeof(Queryable),
494+
"GroupJoin", new[] { outer.ElementType, innerType, outerSelectorLambda.Body.Type, resultSelectorLambda.Body.Type },
495+
outer.Expression,
496+
Expression.Constant(inner),
497+
Expression.Quote(outerSelectorLambda),
498+
Expression.Quote(innerSelectorLambda),
499+
Expression.Quote(resultSelectorLambda)));
500+
}
501+
#endregion
502+
434503
#region Join
435504
/// <summary>
436505
/// Correlates the elements of two sequences based on matching keys. The default equality comparer is used to compare keys.

test/EntityFramework.DynamicLinq.Tests.net452/EntityFramework.DynamicLinq.Tests.net452.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@
245245
<Compile Include="..\System.Linq.Dynamic.Core.Tests\QueryableTests.GroupByMany.cs">
246246
<Link>QueryableTests.GroupByMany.cs</Link>
247247
</Compile>
248+
<Compile Include="..\System.Linq.Dynamic.Core.Tests\QueryableTests.GroupJoin.cs">
249+
<Link>QueryableTests.GroupJoin.cs</Link>
250+
</Compile>
248251
<Compile Include="..\System.Linq.Dynamic.Core.Tests\QueryableTests.Join.cs">
249252
<Link>QueryableTests.Join.cs</Link>
250253
</Compile>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
using System.Collections.Generic;
2+
using System.Linq.Dynamic.Core.Exceptions;
3+
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
4+
using NFluent;
5+
using Xunit;
6+
7+
namespace System.Linq.Dynamic.Core.Tests
8+
{
9+
public partial class QueryableTests
10+
{
11+
[Fact]
12+
public void GroupJoin()
13+
{
14+
//Arrange
15+
Person magnus = new Person { Name = "Hedlund, Magnus" };
16+
Person terry = new Person { Name = "Adams, Terry" };
17+
Person charlotte = new Person { Name = "Weiss, Charlotte" };
18+
19+
Pet barley = new Pet { Name = "Barley", Owner = terry };
20+
Pet boots = new Pet { Name = "Boots", Owner = terry };
21+
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
22+
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };
23+
24+
var people = new List<Person> { magnus, terry, charlotte };
25+
var petsList = new List<Pet> { barley, boots, whiskers, daisy };
26+
27+
//Act
28+
var realQuery = people.AsQueryable().GroupJoin(
29+
petsList,
30+
person => person,
31+
pet => pet.Owner,
32+
(person, pets) => new { OwnerName = person.Name, Pets = pets });
33+
34+
var dynamicQuery = people.AsQueryable().GroupJoin(
35+
petsList,
36+
"it",
37+
"Owner",
38+
"new(outer.Name as OwnerName, inner as Pets)");
39+
40+
//Assert
41+
var realResult = realQuery.ToArray();
42+
43+
#if NETSTANDARD
44+
var dynamicResult = dynamicQuery.ToDynamicArray<DynamicClass>();
45+
46+
Assert.Equal(realResult.Length, dynamicResult.Length);
47+
for (int i = 0; i < realResult.Length; i++)
48+
{
49+
Assert.Equal(realResult[i].OwnerName, dynamicResult[i].GetDynamicPropertyValue<string>("OwnerName"));
50+
for (int j = 0; j < realResult[i].Pets.Count(); j++)
51+
{
52+
Assert.Equal(realResult[i].Pets.ElementAt(j).Name, dynamicResult[i].GetDynamicPropertyValue<IEnumerable<Pet>>("Pets").ElementAt(j).Name);
53+
}
54+
}
55+
#else
56+
var dynamicResult = dynamicQuery.ToDynamicArray();
57+
58+
Assert.Equal(realResult.Length, dynamicResult.Length);
59+
for (int i = 0; i < realResult.Length; i++)
60+
{
61+
Assert.Equal(realResult[i].OwnerName, ((dynamic) dynamicResult[i]).OwnerName);
62+
for (int j = 0; j < realResult[i].Pets.Count(); j++)
63+
{
64+
Assert.Equal(realResult[i].Pets.ElementAt(j).Name, (((IEnumerable<Pet>)((dynamic)dynamicResult[i]).Pets)).ElementAt(j).Name);
65+
}
66+
}
67+
#endif
68+
}
69+
70+
[Fact]
71+
public void GroupJoinOnNullableType_RightNullable()
72+
{
73+
//Arrange
74+
Person magnus = new Person { Id = 1, Name = "Hedlund, Magnus" };
75+
Person terry = new Person { Id = 2, Name = "Adams, Terry" };
76+
Person charlotte = new Person { Id = 3, Name = "Weiss, Charlotte" };
77+
78+
Pet barley = new Pet { Name = "Barley", NullableOwnerId = terry.Id };
79+
Pet boots = new Pet { Name = "Boots", NullableOwnerId = terry.Id };
80+
Pet whiskers = new Pet { Name = "Whiskers", NullableOwnerId = charlotte.Id };
81+
Pet daisy = new Pet { Name = "Daisy", NullableOwnerId = magnus.Id };
82+
83+
var people = new List<Person> { magnus, terry, charlotte };
84+
var petsList = new List<Pet> { barley, boots, whiskers, daisy };
85+
86+
//Act
87+
var realQuery = people.AsQueryable().GroupJoin(
88+
petsList,
89+
person => person.Id,
90+
pet => pet.NullableOwnerId,
91+
(person, pets) => new { OwnerName = person.Name, Pets = pets });
92+
93+
var dynamicQuery = people.AsQueryable().GroupJoin(
94+
petsList,
95+
"it.Id",
96+
"NullableOwnerId",
97+
"new(outer.Name as OwnerName, inner as Pets)");
98+
99+
//Assert
100+
var realResult = realQuery.ToArray();
101+
var dynamicResult = dynamicQuery.ToDynamicArray<DynamicClass>();
102+
103+
Assert.Equal(realResult.Length, dynamicResult.Length);
104+
for (int i = 0; i < realResult.Length; i++)
105+
{
106+
Assert.Equal(realResult[i].OwnerName, dynamicResult[i].GetDynamicPropertyValue<string>("OwnerName"));
107+
for (int j = 0; j < realResult[i].Pets.Count(); j++)
108+
{
109+
Assert.Equal(realResult[i].Pets.ElementAt(j).Name, dynamicResult[i].GetDynamicPropertyValue<IEnumerable<Pet>>("Pets").ElementAt(j).Name);
110+
}
111+
}
112+
}
113+
114+
[Fact]
115+
public void GroupJoinOnNullableType_LeftNullable()
116+
{
117+
//Arrange
118+
Person magnus = new Person { NullableId = 1, Name = "Hedlund, Magnus" };
119+
Person terry = new Person { NullableId = 2, Name = "Adams, Terry" };
120+
Person charlotte = new Person { NullableId = 3, Name = "Weiss, Charlotte" };
121+
122+
Pet barley = new Pet { Name = "Barley", OwnerId = terry.Id };
123+
Pet boots = new Pet { Name = "Boots", OwnerId = terry.Id };
124+
Pet whiskers = new Pet { Name = "Whiskers", OwnerId = charlotte.Id };
125+
Pet daisy = new Pet { Name = "Daisy", OwnerId = magnus.Id };
126+
127+
var people = new List<Person> { magnus, terry, charlotte };
128+
var petsList = new List<Pet> { barley, boots, whiskers, daisy };
129+
130+
//Act
131+
var realQuery = people.AsQueryable().GroupJoin(
132+
petsList,
133+
person => person.NullableId,
134+
pet => pet.OwnerId,
135+
(person, pets) => new { OwnerName = person.Name, Pets = pets });
136+
137+
var dynamicQuery = people.AsQueryable().GroupJoin(
138+
petsList,
139+
"it.NullableId",
140+
"OwnerId",
141+
"new(outer.Name as OwnerName, inner as Pets)");
142+
143+
//Assert
144+
var realResult = realQuery.ToArray();
145+
var dynamicResult = dynamicQuery.ToDynamicArray<DynamicClass>();
146+
147+
Assert.Equal(realResult.Length, dynamicResult.Length);
148+
for (int i = 0; i < realResult.Length; i++)
149+
{
150+
Assert.Equal(realResult[i].OwnerName, dynamicResult[i].GetDynamicPropertyValue<string>("OwnerName"));
151+
for (int j = 0; j < realResult[i].Pets.Count(); j++)
152+
{
153+
Assert.Equal(realResult[i].Pets.ElementAt(j).Name, dynamicResult[i].GetDynamicPropertyValue<IEnumerable<Pet>>("Pets").ElementAt(j).Name);
154+
}
155+
}
156+
}
157+
158+
[Fact]
159+
public void GroupJoinOnNullableType_NotSameTypesThrowsException()
160+
{
161+
var person = new Person { Id = 1, Name = "Hedlund, Magnus" };
162+
var people = new List<Person> { person };
163+
var pets = new List<Pet> { new Pet { Name = "Daisy", OwnerId = person.Id } };
164+
165+
Check.ThatCode(() =>
166+
people.AsQueryable()
167+
.GroupJoin(
168+
pets,
169+
"it.Id",
170+
"Name", // This is wrong
171+
"new(outer.Name as OwnerName, inner as Pets)")).Throws<ParseException>();
172+
}
173+
}
174+
}

0 commit comments

Comments
 (0)