Skip to content

Commit d995041

Browse files
committed
#13 started on the OData Compatibility
1 parent 98951c1 commit d995041

File tree

8 files changed

+315
-7
lines changed

8 files changed

+315
-7
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
using FluentAssertions;
2+
using Microsoft.AspNet.OData.Query;
3+
using Microsoft.Extensions.Logging;
4+
using NSubstitute;
5+
using NUnit.Framework;
6+
using System.Linq;
7+
using Models = SubSonic.Extensions.Test.Models;
8+
9+
namespace SubSonic.Tests.DAL.OData
10+
{
11+
using Infrastructure;
12+
using Infrastructure.Logging;
13+
using Linq.Expressions;
14+
using System;
15+
16+
[TestFixture]
17+
public class ODataCompatibilityTests
18+
: SUT.BaseTestFixture
19+
{
20+
IODataQueryOptions<Models.Person> personQueryOptions = null;
21+
22+
public override void SetupTestFixture()
23+
{
24+
base.SetupTestFixture();
25+
26+
personQueryOptions = Substitute.For<IODataQueryOptions<Models.Person>>();
27+
}
28+
29+
/// <summary>
30+
/// simulate an $filter on more than two fields
31+
/// </summary>
32+
/// <param name="direction"></param>
33+
[Test]
34+
[TestCase(nameof(string.StartsWith))]
35+
[TestCase(nameof(string.EndsWith))]
36+
[TestCase(nameof(string.Contains))]
37+
[TestCase(nameof(string.Equals))]
38+
public void ShouldBeAbleToApplyWhereUsingOData(string @operator)
39+
{
40+
personQueryOptions.ApplyTo(Arg.Any<IQueryable>())
41+
.Returns(call =>
42+
{
43+
if (call.Arg<IQueryable>() is IQueryable<Models.Person> people)
44+
{
45+
switch (@operator)
46+
{
47+
case nameof(string.StartsWith):
48+
return people
49+
.Where(x =>
50+
x.FamilyName.StartsWith("Car"));
51+
case nameof(string.EndsWith):
52+
return people
53+
.Where(x =>
54+
x.FamilyName.EndsWith("Car"));
55+
case nameof(string.Contains):
56+
return people
57+
.Where(x =>
58+
x.FamilyName.Contains("Car"));
59+
case nameof(string.Equals):
60+
return people
61+
.Where(x =>
62+
x.FamilyName == "Carter");
63+
}
64+
}
65+
66+
throw new NotImplementedException();
67+
});
68+
69+
IQueryable select = personQueryOptions.ApplyTo(Context.People);
70+
71+
IDbQuery query = null;
72+
73+
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
74+
75+
using (var perf = logging.Start("SQL Query Writer"))
76+
{
77+
FluentActions.Invoking(() =>
78+
{
79+
ISubSonicQueryProvider<Models.Person> builder = Context.Instance.GetService<ISubSonicQueryProvider<Models.Person>>();
80+
81+
query = builder.ToQuery(select.Expression);
82+
}).Should().NotThrow();
83+
}
84+
85+
logging.LogInformation("\n" + query.Sql + "\n");
86+
87+
query.Sql.Should().NotBeNullOrEmpty();
88+
query.Sql.Should().Contain("WHERE");
89+
switch (@operator)
90+
{
91+
case nameof(string.StartsWith):
92+
case nameof(string.EndsWith):
93+
case nameof(string.Contains):
94+
query.Sql.Should().Contain("LIKE");
95+
break;
96+
case nameof(string.Equals):
97+
query.Sql.Should().Contain(" = @");
98+
break;
99+
}
100+
}
101+
102+
/// <summary>
103+
/// simulate an $orderby descending and ascending
104+
/// </summary>
105+
/// <param name="direction"></param>
106+
[Test]
107+
[TestCase("ascending")]
108+
[TestCase("descending")]
109+
public void ShouldBeAbleToApplyOrderByUsingOData(string direction)
110+
{
111+
personQueryOptions.ApplyTo(Arg.Any<IQueryable>())
112+
.Returns(call =>
113+
{
114+
if (call.Arg<IQueryable>() is IQueryable<Models.Person> people)
115+
{
116+
if (direction.Equals("ascending"))
117+
{
118+
return people.OrderBy(x => x.FamilyName);
119+
}
120+
else
121+
{
122+
return people.OrderByDescending(x => x.FamilyName);
123+
}
124+
}
125+
126+
throw new NotImplementedException();
127+
});
128+
129+
IQueryable select = personQueryOptions.ApplyTo(Context.People);
130+
131+
IDbQuery query = null;
132+
133+
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
134+
135+
using (var perf = logging.Start("SQL Query Writer"))
136+
{
137+
FluentActions.Invoking(() =>
138+
{
139+
ISubSonicQueryProvider<Models.Person> builder = Context.Instance.GetService<ISubSonicQueryProvider<Models.Person>>();
140+
141+
query = builder.ToQuery(select.Expression);
142+
}).Should().NotThrow();
143+
}
144+
145+
logging.LogInformation("\n" + query.Sql + "\n");
146+
147+
query.Sql.Should().NotBeNullOrEmpty();
148+
query.Sql.Should().Contain("ORDER BY");
149+
150+
if (direction.Equals("descending"))
151+
{
152+
query.Sql.Should().Contain("DESC");
153+
}
154+
}
155+
156+
/// <summary>
157+
/// simulate an $orderby on more than two fields
158+
/// </summary>
159+
/// <param name="direction"></param>
160+
[Test]
161+
[TestCase("ascending")]
162+
[TestCase("descending")]
163+
public void ShouldBeAbleToApplyOrderByThenByUsingOData(string direction)
164+
{
165+
personQueryOptions.ApplyTo(Arg.Any<IQueryable>())
166+
.Returns(call =>
167+
{
168+
if (call.Arg<IQueryable>() is IQueryable<Models.Person> people)
169+
{
170+
if (direction.Equals("ascending"))
171+
{
172+
return people
173+
.OrderBy(x => x.FamilyName)
174+
.ThenByDescending(x => x.FirstName);
175+
}
176+
else
177+
{
178+
return people
179+
.OrderByDescending(x => x.FamilyName)
180+
.ThenBy(x => x.FirstName);
181+
}
182+
}
183+
184+
throw new NotImplementedException();
185+
});
186+
187+
IQueryable select = personQueryOptions.ApplyTo(Context.People);
188+
189+
IDbQuery query = null;
190+
191+
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
192+
193+
using (var perf = logging.Start("SQL Query Writer"))
194+
{
195+
FluentActions.Invoking(() =>
196+
{
197+
ISubSonicQueryProvider<Models.Person> builder = Context.Instance.GetService<ISubSonicQueryProvider<Models.Person>>();
198+
199+
query = builder.ToQuery(select.Expression);
200+
}).Should().NotThrow();
201+
}
202+
203+
logging.LogInformation("\n" + query.Sql + "\n");
204+
205+
query.Sql.Should().NotBeNullOrEmpty();
206+
query.Sql.Should().Contain("ORDER BY");
207+
208+
if (direction.Equals("descending"))
209+
{
210+
query.Sql.Should().Contain("DESC");
211+
}
212+
}
213+
}
214+
}

SubSonic.Tests/SubSonic.Tests.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,10 @@
2424
<ProjectReference Include="..\SubSonic.Extensions.Test\SubSonic.Extensions.Test.csproj" />
2525
<ProjectReference Include="..\SubSonic\SubSonic.Core.DataAccessLayer.csproj" />
2626
</ItemGroup>
27+
28+
<ItemGroup>
29+
<Reference Include="Microsoft.AspNetCore.OData">
30+
<HintPath>..\..\WebApi\bin\Release\netstandard2.0\Microsoft.AspNetCore.OData.dll</HintPath>
31+
</Reference>
32+
</ItemGroup>
2733
</Project>

SubSonic/Error.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace SubSonic
6+
{
7+
internal static class Error
8+
{
9+
public static Exception ArgumentNull(string parameter)
10+
{
11+
return new ArgumentNullException(parameter);
12+
}
13+
14+
public static Exception Argument(string message, string parameter)
15+
{
16+
return new ArgumentException(message, parameter);
17+
}
18+
19+
public static Exception NotImplemented()
20+
{
21+
throw new NotImplementedException();
22+
}
23+
}
24+
}

SubSonic/Extensions/Internal/MethodInfo.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,10 @@ public static bool IsOrderBy(this MethodInfo info, out OrderByType orderByType)
2323

2424
return info.Name.In(nameof(Legacy.OrderBy), nameof(Legacy.ThenBy), nameof(Legacy.OrderByDescending), nameof(Legacy.ThenByDescending));
2525
}
26+
27+
public static bool IsWhere(this MethodInfo info)
28+
{
29+
return info.Name.Equals(nameof(Legacy.Where), StringComparison.CurrentCulture);
30+
}
2631
}
2732
}

SubSonic/Infrastructure/Builders/DbSqlQueryBuilder/DbSqlQueryBuilderBuildMethods.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,14 @@ protected virtual Expression BuildQuery(Expression expression)
491491
{
492492
return BuildSelectWithOrderByDeclaration(call, orderByType);
493493
}
494+
else if (call.Method.IsWhere())
495+
{
496+
return BuildSelectWithWhere(call);
497+
}
498+
else
499+
{
500+
throw new NotSupportedException(SubSonicErrorMessages.UnSupportedMethodException.Format(call.Method.Name));
501+
}
494502
}
495503
}
496504

@@ -518,6 +526,40 @@ private static Type[] GetTypeArguments(LambdaExpression expression)
518526
return Array.Empty<Type>();
519527
}
520528

529+
private Expression BuildSelectWithWhere(MethodCallExpression expression)
530+
{
531+
if (!(expression is null))
532+
{
533+
DbSelectExpression select = null;
534+
LambdaExpression where = null;
535+
536+
foreach (var argument in expression.Arguments)
537+
{
538+
if (argument is DbSelectExpression _select)
539+
{
540+
select = _select;
541+
}
542+
else if (argument is UnaryExpression unary)
543+
{
544+
if (unary.Operand is LambdaExpression _unary)
545+
{
546+
where = _unary;
547+
}
548+
}
549+
else if (argument is BinaryExpression binary)
550+
{
551+
throw new NotImplementedException();
552+
}
553+
}
554+
555+
return DbExpression.DbSelect(
556+
select,
557+
DbExpression.DbWhere(select.From, select.Type, where));
558+
}
559+
560+
return expression;
561+
}
562+
521563
private Expression BuildSelectWithOrderByDeclaration(MethodCallExpression expression, OrderByType orderByType)
522564
{
523565
DbSelectExpression select = null;

SubSonic/Infrastructure/Builders/DbWherePredicateBuilder/DbWherePredicateBuilderVisit.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ protected override Expression VisitBinary(BinaryExpression node)
3131
Arguments.Push(Visit(node.Right));
3232

3333
BuildLogicalExpression();
34-
35-
3634
}
3735
return node;
3836
case ExpressionType.Or:
@@ -72,6 +70,15 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
7270
}
7371
}
7472
}
73+
else if (comparison.In(DbComparisonOperator.Contains, DbComparisonOperator.NotContains, DbComparisonOperator.StartsWith, DbComparisonOperator.NotStartsWith, DbComparisonOperator.EndsWith, DbComparisonOperator.NotEndsWith))
74+
{
75+
Arguments.Push(Visit(call.Object));
76+
77+
foreach (Expression argument in call.Arguments)
78+
{
79+
Arguments.Push(Visit(argument));
80+
}
81+
}
7582
else if (comparison.In(DbComparisonOperator.Between, DbComparisonOperator.NotBetween))
7683
{
7784
#if NETSTANDARD2_0
@@ -80,7 +87,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
8087
if (call.Method.Name.Contains("Between", StringComparison.CurrentCulture))
8188
#endif
8289
{
83-
using (var args = Arguments.FocusOn(call.Method.Name))
90+
using (Arguments.FocusOn(call.Method.Name))
8491
{
8592
foreach (Expression argument in call.Arguments)
8693
{

SubSonic/Linq/Expressions/DbSelectExpression.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,16 @@ public static DbExpression DbSelect(object collection, Type type, DbTableExpress
133133
};
134134
}
135135

136+
public static DbExpression DbSelect(DbSelectExpression select, DbExpression where)
137+
{
138+
if (select is null)
139+
{
140+
throw Error.ArgumentNull(nameof(select));
141+
}
142+
143+
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, where ?? select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take);
144+
}
145+
136146
public static DbExpression DbSelect(DbSelectExpression select, DbJoinExpression join)
137147
{
138148
if (select is null)

SubSonic/Linq/Expressions/DbWhereExpression.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,22 @@ public static DbExpression DbWhere(DbTableExpression table, Type type, LambdaExp
4646
{
4747
if (table is null)
4848
{
49-
throw new ArgumentNullException(nameof(table));
49+
throw Error.ArgumentNull(nameof(table));
5050
}
5151

5252
if (type is null)
5353
{
54-
throw new ArgumentNullException(nameof(type));
54+
throw Error.ArgumentNull(nameof(type));
5555
}
5656

5757
if (predicate is null)
5858
{
59-
throw new ArgumentNullException(nameof(predicate));
59+
throw Error.ArgumentNull(nameof(predicate));
6060
}
6161

6262
if (whereType.NotIn(DbExpressionType.Where, DbExpressionType.Exists, DbExpressionType.NotExists))
6363
{
64-
throw new ArgumentException("", nameof(whereType));
64+
throw Error.Argument("", nameof(whereType));
6565
}
6666

6767
return DbWherePredicateBuilder.GetWherePredicate(type, predicate, whereType, table);

0 commit comments

Comments
 (0)