Skip to content

Commit bf299e1

Browse files
committed
additional tests to ensure that the query engine can render data points that should not make it into the SQL
1 parent 751e399 commit bf299e1

File tree

3 files changed

+158
-10
lines changed

3 files changed

+158
-10
lines changed

SubSonic.Core.DataAccessLayer/src/Builders/DbWherePredicateBuilder/DbWherePredicateBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ public Expression ParseLambda(Expression predicate)
240240
return body;
241241
}
242242

243-
private IEnumerable<DbTableExpression> DbTables => __instances.Select(builder => builder.table);
243+
private IEnumerable<DbTableExpression> DbTables => __instances.SelectMany(builder => builder.table.ToTableList());
244244

245245
private DbTableExpression GetDbTable(Type type) => DbTables.Single(table =>
246246
table.Type.GenericTypeArguments[0] == type || table.Type.GenericTypeArguments[0].IsSubclassOf(type));

SubSonic.Core.DataAccessLayer/src/Builders/DbWherePredicateBuilder/DbWherePredicateBuilderVisit.cs

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace SubSonic.Builders
99
using Collections;
1010
using Linq;
1111
using Linq.Expressions;
12+
using System.Diagnostics;
1213

1314
public partial class DbWherePredicateBuilder
1415
{
@@ -197,16 +198,32 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
197198
{
198199
Expression root = null;
199200

201+
bool shouldBuildSqlMapping = false;
202+
200203
if (call.Object is MemberExpression member)
201204
{
202-
if (member.Expression.IsNotNull())
205+
shouldBuildSqlMapping = member.Expression is ParameterExpression;
206+
207+
if (member.Member is FieldInfo field &&
208+
member.Expression is ConstantExpression fieldValue)
203209
{
204-
root = Visit(member);
210+
root = CheckValueAgainstType(member.Type, field.GetValue(fieldValue.Value));
211+
}
212+
else if (member.Member is PropertyInfo staticProperty &&
213+
member.Expression is null)
214+
{ // this use case the property is most likely static
215+
Debug.Assert(staticProperty.GetMethod.IsStatic);
216+
217+
root = CheckValueAgainstType(member.Type, staticProperty.GetValue(null));
205218
}
206219
else if (member.Member is PropertyInfo property &&
207-
property.GetMethod.IsStatic)
220+
member.Expression is ConstantExpression propertyValue)
208221
{
209-
root = Expression.Constant(property.GetValue(null));
222+
root = CheckValueAgainstType(member.Type, property.GetValue(propertyValue.Value));
223+
}
224+
else if (member.Expression is ParameterExpression parameter)
225+
{
226+
root = Visit(member);
210227
}
211228
}
212229

@@ -224,13 +241,37 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
224241
}
225242
}
226243

227-
if (root is ConstantExpression constant)
244+
if (!shouldBuildSqlMapping)
228245
{
229-
return GetNamedExpression(call.Method.Invoke(constant.Value, GetArrayOfValues(arguments)));
246+
if(root is ConstantExpression constant)
247+
{
248+
return Expression.Constant(call.Method.Invoke(constant.Value, GetArrayOfValues(arguments)));
249+
}
250+
else if (root is NewExpression @new)
251+
{
252+
object value = null;
253+
254+
if (@new.Type.IsNullableType())
255+
{
256+
Debug.Assert(@new.Arguments.Count == arguments.Count);
257+
258+
value = @new.Constructor.Invoke(GetArrayOfValues(arguments));
259+
}
260+
else
261+
{
262+
value = @new.Constructor.Invoke(GetArrayOfValues(@new.Arguments));
263+
}
264+
265+
return GetNamedExpression(call.Method.Invoke(value, GetArrayOfValues(arguments)));
266+
}
267+
}
268+
else if (root is ConstantExpression constant)
269+
{
270+
return GetNamedExpression(call.Method.Invoke(constant.Value, GetArrayOfValues(arguments)));
230271
}
231272
else
232273
{
233-
return Expression.Call(root, call.Method, arguments);
274+
return Expression.Call(root, call.Method, GetArrayOfNamedValues(arguments));
234275
}
235276
}
236277

@@ -242,16 +283,57 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
242283
return base.VisitMethodCall(node);
243284
}
244285

286+
private Expression CheckValueAgainstType(Type type, object value)
287+
{
288+
if (type.IsNullableType())
289+
{
290+
if (value is null)
291+
{
292+
return Expression.New(type.GetConstructor(type.GenericTypeArguments), type.GenericTypeArguments.Select(x => Expression.Constant(Activator.CreateInstance(x), x)));
293+
}
294+
else
295+
{
296+
return Expression.New(type.GetConstructor(type.GenericTypeArguments), Expression.Constant(value));
297+
}
298+
}
299+
else if (value != null &&
300+
value.GetType() == type)
301+
{
302+
return Expression.Constant(value);
303+
}
304+
305+
throw Error.NotSupported();
306+
}
307+
308+
private Expression[] GetArrayOfNamedValues(IEnumerable<Expression> arguments) => arguments.Select(x =>
309+
{
310+
if (x is DbNamedValueExpression existing)
311+
{
312+
return existing;
313+
}
314+
else if (x is ConstantExpression constant)
315+
{
316+
return GetNamedExpression(constant.Value);
317+
}
318+
319+
throw Error.NotSupported();
320+
}).ToArray();
321+
245322
private object[] GetArrayOfValues(IEnumerable<Expression> expressions) => expressions.Select(x =>
246323
{
247324
if (x is ConstantExpression constant)
248325
{
249326
return constant.Value;
250327
}
251-
else
328+
else if (x is DbNamedValueExpression named)
252329
{
253-
throw Error.NotImplemented();
330+
if (named.Value is ConstantExpression namedConstant)
331+
{
332+
return namedConstant.Value;
333+
}
254334
}
335+
336+
throw Error.NotSupported();
255337
}).ToArray();
256338

257339
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "this data table is cleaned up after the result is back from the db.")]

SubSonic.Tests/DAL/SqlQueryProvider/SqlGeneratorTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,72 @@ FROM [dbo].[Unit] AS [{1}]
473473
query.Parameters.Get("@bedrooms_1").Value.Should().Be(1);
474474
}
475475

476+
[Test]
477+
public void WhereCanFigureOutValuesFromNullableNonEntityObjects()
478+
{
479+
DateTime
480+
Start = new DateTime(1985, 01, 01);
481+
DateTime?
482+
End = null;
483+
484+
Expression select = Context
485+
.Renters
486+
.Where(Renter =>
487+
Renter.StartDate.Between(Start, End.GetValueOrDefault(DateTime.Today.AddDays(1))))
488+
.Expression;
489+
490+
IDbQuery query = null;
491+
492+
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
493+
494+
using (var perf = logging.Start("SQL Query Writer"))
495+
{
496+
FluentActions.Invoking(() =>
497+
{
498+
ISubSonicQueryProvider<Status> builder = Context.Instance.GetService<ISubSonicQueryProvider<Status>>();
499+
500+
query = builder.ToQuery(select);
501+
}).Should().NotThrow();
502+
}
503+
504+
query.Parameters.Get("@dt_value_2").GetValue<DateTime>().Should().Be(DateTime.Today.AddDays(1));
505+
}
506+
507+
[Test]
508+
public void WhereCanFigureOutValuesFromAnonymousNonEntityObjects()
509+
{
510+
var anon = new
511+
{
512+
Start = new DateTime(1985, 01, 01),
513+
End = DateTime.Today.AddDays(1)
514+
};
515+
516+
Expression select = Context
517+
.Renters
518+
.Where(Renter =>
519+
Renter.StartDate.Between(anon.Start, anon.End))
520+
.Expression;
521+
522+
IDbQuery query = null;
523+
524+
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
525+
526+
using (var perf = logging.Start("SQL Query Writer"))
527+
{
528+
FluentActions.Invoking(() =>
529+
{
530+
ISubSonicQueryProvider<Status> builder = Context.Instance.GetService<ISubSonicQueryProvider<Status>>();
531+
532+
query = builder.ToQuery(select);
533+
}).Should().NotThrow();
534+
}
535+
536+
logging.LogInformation(query.Sql);
537+
538+
query.Parameters.Get("@dt_startdate_1").GetValue<DateTime>().Should().Be(anon.Start);
539+
query.Parameters.Get("@dt_startdate_2").GetValue<DateTime>().Should().Be(anon.End);
540+
}
541+
476542
[Test]
477543
public void CanMergeMultipleWhereStatements()
478544
{

0 commit comments

Comments
 (0)