Skip to content

Commit 935a319

Browse files
Runtime integration with System.Linq.Dynamic.Core.DynamicClass (#439)
1 parent 82b02e1 commit 935a319

File tree

12 files changed

+256
-31
lines changed

12 files changed

+256
-31
lines changed

net/DevExtreme.AspNet.Data.Tests.NET4/DevExtreme.AspNet.Data.Tests.NET4.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
<Reference Include="System" />
5656
<Reference Include="System.ComponentModel.DataAnnotations" />
5757
<Reference Include="System.Core" />
58-
<Reference Include="System.Linq.Dynamic.Core, Version=1.0.10.0, Culture=neutral, PublicKeyToken=0f07ec44de6ac832, processorArchitecture=MSIL">
59-
<HintPath>..\packages\System.Linq.Dynamic.Core.1.0.10\lib\net46\System.Linq.Dynamic.Core.dll</HintPath>
58+
<Reference Include="System.Linq.Dynamic.Core, Version=1.2.0.0, Culture=neutral, PublicKeyToken=0f07ec44de6ac832, processorArchitecture=MSIL">
59+
<HintPath>..\packages\System.Linq.Dynamic.Core.1.2.0\lib\net46\System.Linq.Dynamic.Core.dll</HintPath>
6060
</Reference>
6161
<Reference Include="System.Web.Extensions" />
6262
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">

net/DevExtreme.AspNet.Data.Tests.NET4/packages.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
33
<package id="Newtonsoft.Json" version="11.0.1" targetFramework="net461" />
4-
<package id="System.Linq.Dynamic.Core" version="1.0.10" targetFramework="net461" />
4+
<package id="System.Linq.Dynamic.Core" version="1.2.0" targetFramework="net461" />
55
<package id="xunit" version="2.3.1" targetFramework="net461" />
66
<package id="xunit.abstractions" version="2.0.1" targetFramework="net461" />
77
<package id="xunit.analyzers" version="0.8.0" targetFramework="net461" />

net/DevExtreme.AspNet.Data.Tests/AnonTypeNewTweaksTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
namespace DevExtreme.AspNet.Data.Tests {
99

1010
public class AnonTypeNewTweaksTests {
11-
static readonly ICollection<Expression> EMPTY_EXPR_LIST = Array.Empty<Expression>();
12-
static readonly ICollection<Expression> PARTIAL_EXPR_LIST = new[] {
11+
static readonly IReadOnlyCollection<Expression> EMPTY_EXPR_LIST = Array.Empty<Expression>();
12+
static readonly IReadOnlyCollection<Expression> PARTIAL_EXPR_LIST = new[] {
1313
Expression.Constant(0),
1414
Expression.Constant(1),
1515
Expression.Constant(2),

net/DevExtreme.AspNet.Data.Tests/DevExtreme.AspNet.Data.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<ProjectReference Include="..\DevExtreme.AspNet.Data\DevExtreme.AspNet.Data.csproj" />
99
<ProjectReference Include="..\DevExtreme.AspNet.Data.Tests.Common\DevExtreme.AspNet.Data.Tests.Common.csproj" />
1010
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
11-
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.10" />
11+
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.0" />
1212
<PackageReference Include="xunit" Version="2.3.1" />
1313
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
1414
</ItemGroup>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using DevExtreme.AspNet.Data.ResponseModel;
2+
using System;
3+
using System.Linq;
4+
using Xunit;
5+
6+
namespace DevExtreme.AspNet.Data.Tests {
7+
8+
public class DynamicClassTests {
9+
10+
[Fact]
11+
public void Totals() {
12+
var summaryInfo = new SummaryInfo {
13+
Selector = "p",
14+
SummaryType = "sum"
15+
};
16+
17+
var source = new[] {
18+
new { p = 1 },
19+
new { p = 2 }
20+
};
21+
22+
var loadOptions = new SampleLoadOptions {
23+
GuardNulls = false,
24+
RemoteGrouping = true,
25+
TotalSummary = Enumerable
26+
.Repeat(summaryInfo, 123)
27+
.ToArray()
28+
};
29+
30+
var loadResult = DataSourceLoader.Load(source, loadOptions);
31+
32+
Assert.Equal(3m, loadResult.summary[122]);
33+
34+
Assert.Contains(loadOptions.ExpressionLog, line =>
35+
line.Contains(".Select(g => new <>f__AnonymousType") &&
36+
line.Contains("I100 = g.Sum(obj => obj.p)")
37+
);
38+
}
39+
40+
[Fact]
41+
public void GroupSummary() {
42+
var summaryInfo = new SummaryInfo {
43+
Selector = "p",
44+
SummaryType = "sum"
45+
};
46+
47+
var loadOptions = new SampleLoadOptions {
48+
GuardNulls = false,
49+
RemoteGrouping = true,
50+
Group = new[] {
51+
new GroupingInfo { Selector = "g", IsExpanded = false },
52+
},
53+
GroupSummary = Enumerable
54+
.Repeat(summaryInfo, 123)
55+
.ToArray()
56+
};
57+
58+
var source = new[] {
59+
new { g = 1, p = 1 },
60+
new { g = 1, p = 2 },
61+
};
62+
63+
var loadResult = DataSourceLoader.Load(source, loadOptions);
64+
var group = loadResult.data.Cast<Group>().First();
65+
66+
Assert.Equal(3m, group.summary[122]);
67+
68+
Assert.Contains(loadOptions.ExpressionLog, line =>
69+
line.Contains(".Select(g => new <>f__AnonymousType") &&
70+
line.Contains("I100 = g.Sum(obj => obj.p)")
71+
);
72+
}
73+
74+
[Fact]
75+
public void Select() {
76+
var select = Enumerable.Repeat("p", 123).ToArray();
77+
78+
var source = new[] {
79+
new { p = 1 }
80+
};
81+
82+
var loadOptions = new SampleLoadOptions {
83+
GuardNulls = false,
84+
RemoteSelect = true,
85+
Select = select
86+
};
87+
88+
var loadResult = DataSourceLoader.Load(source, loadOptions);
89+
90+
Assert.Contains(loadOptions.ExpressionLog, line =>
91+
line.Contains(".Select(obj => new <>f__AnonymousType") &&
92+
line.Contains("I100 = obj.p")
93+
);
94+
}
95+
96+
}
97+
98+
}

net/DevExtreme.AspNet.Data/DataSourceLoaderImpl.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public async Task<LoadResult> LoadAsync() {
8787
}
8888

8989
var loadKeysExpr = CreateBuilder().BuildLoadExpr(true, selectOverride: Context.PrimaryKey);
90-
var keyTuples = await ExecExprAsync<AnonType>(loadKeysExpr);
90+
var keyTuples = await ExecExprAnonAsync(loadKeysExpr);
9191

9292
loadExpr = CreateBuilder().BuildLoadExpr(false, filterOverride: FilterFromKeys(keyTuples));
9393
} else {
@@ -118,7 +118,7 @@ await ExecExprAsync<S>(loadExpr),
118118

119119
async Task<IEnumerable<ExpandoObject>> ExecWithSelectAsync(Expression loadExpr) {
120120
if(Context.UseRemoteSelect)
121-
return SelectHelper.ConvertRemoteResult(await ExecExprAsync<AnonType>(loadExpr), Context.FullSelect);
121+
return SelectHelper.ConvertRemoteResult(await ExecExprAnonAsync(loadExpr), Context.FullSelect);
122122

123123
return SelectHelper.Evaluate(await ExecExprAsync<S>(loadExpr), Context.FullSelect);
124124
}
@@ -180,7 +180,7 @@ Task<int> ExecCountAsync(Expression expr) {
180180
async Task<RemoteGroupingResult> ExecRemoteGroupingAsync(bool remotePaging, bool suppressGroups, bool suppressTotals) {
181181
return RemoteGroupTransformer.Run(
182182
Source.ElementType,
183-
await ExecExprAsync<AnonType>(CreateBuilder().BuildLoadGroupsExpr(remotePaging, suppressGroups, suppressTotals)),
183+
await ExecExprAnonAsync(CreateBuilder().BuildLoadGroupsExpr(remotePaging, suppressGroups, suppressTotals)),
184184
!suppressGroups && Context.HasGroups ? Context.Group.Count : 0,
185185
!suppressTotals ? Context.TotalSummary : null,
186186
!suppressGroups ? Context.GroupSummary : null
@@ -207,6 +207,11 @@ async Task<IEnumerable<R>> ExecExprAsync<R>(Expression expr) {
207207
return result;
208208
}
209209

210+
async Task<IEnumerable<AnonType>> ExecExprAnonAsync(Expression expr) {
211+
return (await ExecExprAsync<object>(expr))
212+
.Select(i => i is AnonType anon ? anon : new DynamicClassAdapter(i));
213+
}
214+
210215
IList FilterFromKeys(IEnumerable<AnonType> keyTuples) {
211216
var result = new List<object>();
212217
var key = Context.PrimaryKey;

net/DevExtreme.AspNet.Data/RemoteGrouping/RemoteGroupExpressionCompiler.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,17 @@ public Expression Compile(Expression target) {
4848
}
4949
}
5050

51-
var groupKeyLambda = Expression.Lambda(AnonType.CreateNewExpression(groupKeyExprList, _anonTypeNewTweaks), groupByParam);
51+
var groupKeyTypeFacade = new AnonTypeFacade(groupKeyExprList);
52+
var groupKeyLambda = Expression.Lambda(groupKeyTypeFacade.CreateNewExpression(_anonTypeNewTweaks), groupByParam);
5253
var groupingType = typeof(IGrouping<,>).MakeGenericType(groupKeyLambda.ReturnType, ItemType);
5354

5455
target = Expression.Call(typeof(Queryable), nameof(Queryable.GroupBy), new[] { ItemType, groupKeyLambda.ReturnType }, target, Expression.Quote(groupKeyLambda));
5556

5657
for(var i = 0; i < groupKeyExprList.Count; i++) {
5758
var orderParam = Expression.Parameter(groupingType, "g");
58-
var orderAccessor = Expression.Field(
59+
var orderAccessor = groupKeyTypeFacade.CreateMemberAccessor(
5960
Expression.Property(orderParam, "Key"),
60-
AnonType.IndexToField(i)
61+
i
6162
);
6263

6364
target = Expression.Call(
@@ -69,25 +70,27 @@ public Expression Compile(Expression target) {
6970
);
7071
}
7172

72-
return MakeAggregatingProjection(target, groupingType, groupKeyExprList.Count);
73+
return MakeAggregatingProjection(target, groupingType, groupKeyTypeFacade);
7374
}
7475

75-
Expression MakeAggregatingProjection(Expression target, Type groupingType, int groupCount) {
76+
Expression MakeAggregatingProjection(Expression target, Type groupingType, AnonTypeFacade groupKeyTypeFacade) {
7677
var param = Expression.Parameter(groupingType, "g");
78+
var groupCount = groupKeyTypeFacade.MemberCount;
7779

7880
var projectionExprList = new List<Expression> {
7981
Expression.Call(typeof(Enumerable), nameof(Enumerable.Count), new[] { ItemType }, param)
8082
};
8183

8284
for(var i = 0; i < groupCount; i++)
83-
projectionExprList.Add(Expression.Field(Expression.Property(param, "Key"), AnonType.IndexToField(i)));
85+
projectionExprList.Add(groupKeyTypeFacade.CreateMemberAccessor(Expression.Property(param, "Key"), i));
8486

8587
projectionExprList.AddRange(MakeAggregates(param, _totalSummary));
8688

8789
if(groupCount > 0)
8890
projectionExprList.AddRange(MakeAggregates(param, _groupSummary));
8991

90-
var projectionLambda = Expression.Lambda(AnonType.CreateNewExpression(projectionExprList, _anonTypeNewTweaks), param);
92+
var projectionTypeFacade = new AnonTypeFacade(projectionExprList);
93+
var projectionLambda = Expression.Lambda(projectionTypeFacade.CreateNewExpression(_anonTypeNewTweaks), param);
9194

9295
return Expression.Call(typeof(Queryable), nameof(Queryable.Select), new[] { param.Type, projectionLambda.ReturnType }, target, Expression.Quote(projectionLambda));
9396
}

net/DevExtreme.AspNet.Data/SelectExpressionCompiler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Expression Compile(Expression target, IEnumerable<string> clientExprList, bool u
3131

3232
var lambda = Expression.Lambda(
3333
useNew
34-
? AnonType.CreateNewExpression(memberExprList, _anonTypeNewTweaks)
34+
? new AnonTypeFacade(memberExprList).CreateNewExpression(_anonTypeNewTweaks)
3535
: memberExprList[0],
3636
itemExpr
3737
);

net/DevExtreme.AspNet.Data/Types/AnonType.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,31 +60,29 @@ public static Type Get(IList<Type> typeArguments) {
6060
return GetTemplate(size).MakeGenericType(typeArguments.ToArray());
6161
}
6262

63-
public static Expression CreateNewExpression(ICollection<Expression> expressions, AnonTypeNewTweaks tweaks) {
63+
public static Expression CreateNewExpression(IReadOnlyCollection<Expression> expressions, AnonTypeNewTweaks tweaks) {
6464
if(tweaks != null) {
6565
if(!tweaks.AllowEmpty && expressions.Count < 1)
6666
return Expression.Constant(1);
6767

68-
if(!tweaks.AllowUnusedMembers) {
69-
var unusedCount = SnapSize(expressions.Count) - expressions.Count;
70-
71-
if(unusedCount > 0)
72-
expressions = new List<Expression>(expressions);
73-
74-
while(unusedCount > 0) {
75-
expressions.Add(Expression.Constant(false));
76-
unusedCount--;
77-
}
78-
}
68+
if(!tweaks.AllowUnusedMembers)
69+
expressions = Pad(expressions, SnapSize(expressions.Count));
7970
}
8071

8172
var typeArguments = expressions.Select(i => i.Type).ToArray();
82-
var type = Get(typeArguments);
73+
return CreateNewExpression(Get(typeArguments), typeArguments, expressions, true);
74+
}
8375

76+
public static Expression CreateNewExpression(Type type, Type[] typeArguments, IEnumerable<Expression> expressions, bool useFields) {
8477
return Expression.New(
8578
type.GetConstructor(typeArguments),
8679
expressions,
87-
Enumerable.Range(0, typeArguments.Length).Select(i => type.GetField(IndexToField(i)))
80+
Enumerable.Range(0, typeArguments.Length).Select(i => {
81+
var name = IndexToField(i);
82+
return useFields
83+
? (MemberInfo)type.GetField(name)
84+
: (MemberInfo)type.GetProperty(name);
85+
})
8886
);
8987
}
9088

@@ -96,6 +94,15 @@ public static int FieldToIndex(string field) {
9694
return int.Parse(field.Substring(1));
9795
}
9896

97+
static IReadOnlyCollection<Expression> Pad(IReadOnlyCollection<Expression> collection, int totalCount) {
98+
var padLen = totalCount - collection.Count;
99+
100+
if(padLen < 1)
101+
return collection;
102+
103+
var padding = Enumerable.Repeat(Expression.Constant(false), padLen);
104+
return new List<Expression>(collection.Concat(padding));
105+
}
99106
}
100107

101108
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
6+
namespace DevExtreme.AspNet.Data.Types {
7+
8+
class AnonTypeFacade {
9+
readonly IReadOnlyCollection<Expression> MemberExpressions;
10+
11+
public AnonTypeFacade(IReadOnlyCollection<Expression> memberExpressions) {
12+
MemberExpressions = memberExpressions;
13+
}
14+
15+
public int MemberCount => MemberExpressions.Count;
16+
17+
bool UseBuiltInTypes => MemberCount <= AnonType.MAX_SIZE;
18+
19+
public Expression CreateNewExpression(AnonTypeNewTweaks tweaks) {
20+
if(UseBuiltInTypes)
21+
return AnonType.CreateNewExpression(MemberExpressions, tweaks);
22+
23+
var typeArguments = MemberExpressions.Select(i => i.Type).ToArray();
24+
var type = DynamicClassBridge.CreateType(typeArguments);
25+
return AnonType.CreateNewExpression(type, typeArguments, MemberExpressions, false);
26+
}
27+
28+
public MemberExpression CreateMemberAccessor(Expression expr, int index) {
29+
var name = AnonType.IndexToField(index);
30+
return UseBuiltInTypes
31+
? Expression.Field(expr, name)
32+
: Expression.Property(expr, name);
33+
}
34+
}
35+
36+
}

0 commit comments

Comments
 (0)