Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit c6fe8aa

Browse files
committed
1. IsParameterAccess fixes
2. Unit tests for using_filter_with_nested_properties
1 parent c7d9188 commit c6fe8aa

File tree

2 files changed

+216
-32
lines changed

2 files changed

+216
-32
lines changed

src/ServiceStack.OrmLite/Expressions/SqlExpression.cs

Lines changed: 86 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public abstract partial class SqlExpression<T> : ISqlExpression, IHasUntypedSqlE
4141
public bool PrefixFieldWithTableName { get; set; }
4242
public bool WhereStatementWithoutWhereString { get; set; }
4343
public IOrmLiteDialectProvider DialectProvider { get; set; }
44-
public List<IDbDataParameter> Params { get; set; }
44+
public List<IDbDataParameter> Params { get; set; }
4545

4646
protected string Sep
4747
{
@@ -107,7 +107,7 @@ public virtual SqlExpression<T> Select(string selectExpression)
107107
{
108108
if (selectExpression != null)
109109
selectExpression.SqlVerifyFragment();
110-
110+
111111
return UnsafeSelect(selectExpression);
112112
}
113113

@@ -590,7 +590,7 @@ private SqlExpression<T> OrderByFields(string orderBySuffix, string[] fieldNames
590590
foreach (var fieldName in fieldNames)
591591
{
592592
var reverse = fieldName.StartsWith("-");
593-
var useSuffix = reverse
593+
var useSuffix = reverse
594594
? (orderBySuffix == OrderBySuffix.Asc ? OrderBySuffix.Desc : OrderBySuffix.Asc)
595595
: orderBySuffix;
596596
var useName = reverse ? fieldName.Substring(1) : fieldName;
@@ -980,7 +980,7 @@ public virtual void PrepareUpdateStatement(IDbCommand dbCmd, T item, bool exclud
980980
{
981981
if (fieldDef.ShouldSkipUpdate()) continue;
982982
if (fieldDef.IsRowVersion) continue;
983-
if (UpdateFields.Count > 0
983+
if (UpdateFields.Count > 0
984984
&& !UpdateFields.Contains(fieldDef.Name)) continue; // added
985985

986986
var value = fieldDef.GetValue(item);
@@ -1238,12 +1238,12 @@ protected virtual object VisitBinary(BinaryExpression b)
12381238
var operand = BindOperant(b.NodeType); //sep= " " ??
12391239
if (operand == "AND" || operand == "OR")
12401240
{
1241-
if (IsParameterAccess(b.Left))
1241+
if (IsNeedCompareToTrue(b.Left))
12421242
left = new PartialSqlString(string.Format("{0}={1}", VisitMemberAccess((MemberExpression) b.Left), GetQuotedTrueValue()));
12431243
else
12441244
left = Visit(b.Left);
12451245

1246-
if (IsParameterAccess(b.Right))
1246+
if (IsNeedCompareToTrue(b.Right))
12471247
right = new PartialSqlString(string.Format("{0}={1}", VisitMemberAccess((MemberExpression) b.Right), GetQuotedTrueValue()));
12481248
else
12491249
right = Visit(b.Right);
@@ -1351,6 +1351,25 @@ protected virtual object VisitBinary(BinaryExpression b)
13511351
}
13521352
}
13531353

1354+
/// <summary>
1355+
/// Determines whether the expression is the parameter inside MemberExpression which should be compared with TrueExpression.
1356+
/// </summary>
1357+
/// <param name="e">The specified expression.</param>
1358+
/// <returns>Returns true if the specified expression is the parameter inside MemberExpression which should be compared with TrueExpression;
1359+
/// otherwise, false.</returns>
1360+
protected virtual bool IsNeedCompareToTrue(Expression e)
1361+
{
1362+
if (!(e is MemberExpression)) return false;
1363+
1364+
var m = (MemberExpression)e;
1365+
1366+
if (m.Member.DeclaringType.IsNullableType() &&
1367+
m.Member.Name == "HasValue") //nameof(Nullable<bool>.HasValue))
1368+
return false;
1369+
1370+
return IsParameterAccess(m);
1371+
}
1372+
13541373
/// <summary>
13551374
/// Determines whether the expression is the parameter.
13561375
/// </summary>
@@ -1359,20 +1378,58 @@ protected virtual object VisitBinary(BinaryExpression b)
13591378
/// otherwise, false.</returns>
13601379
protected virtual bool IsParameterAccess(Expression e)
13611380
{
1362-
Expression m = e as MemberExpression;
1381+
return CheckExpressionForTypes(e, new[] { ExpressionType.Parameter });
1382+
}
13631383

1364-
while (m != null)
1384+
/// <summary>
1385+
/// Determines whether the expression is the parameter or convert.
1386+
/// </summary>
1387+
/// <param name="e">The specified expression.</param>
1388+
/// <returns>Returns true if the specified expression is parameter or convert;
1389+
/// otherwise, false.</returns>
1390+
protected virtual bool IsParameterOrConvertAccess(Expression e)
1391+
{
1392+
return CheckExpressionForTypes(e, new[] { ExpressionType.Parameter, ExpressionType.Convert });
1393+
}
1394+
1395+
protected bool CheckExpressionForTypes(Expression e, ExpressionType[] types)
1396+
{
1397+
while (e != null)
13651398
{
1366-
if (m.NodeType == ExpressionType.Parameter)
1399+
if (types.Contains(e.NodeType))
13671400
{
1368-
var isSubExprAccess = m is UnaryExpression &&
1369-
((UnaryExpression) m).Operand is IndexExpression;
1401+
var isSubExprAccess = e is UnaryExpression &&
1402+
((UnaryExpression)e).Operand is IndexExpression;
13701403

13711404
if (!isSubExprAccess)
13721405
return true;
13731406
}
13741407

1375-
m = m is MemberExpression ? ((MemberExpression) m).Expression : null;
1408+
if (e is BinaryExpression)
1409+
{
1410+
if (CheckExpressionForTypes(((BinaryExpression)e).Left, types))
1411+
return true;
1412+
1413+
if (CheckExpressionForTypes(((BinaryExpression)e).Right, types))
1414+
return true;
1415+
}
1416+
1417+
if (e is MethodCallExpression)
1418+
{
1419+
for (int i = 0; i < ((MethodCallExpression)e).Arguments.Count; i++)
1420+
{
1421+
if (CheckExpressionForTypes(((MethodCallExpression)e).Arguments[0], types))
1422+
return true;
1423+
}
1424+
}
1425+
1426+
if (e is UnaryExpression)
1427+
{
1428+
if (CheckExpressionForTypes(((UnaryExpression)e).Operand, types))
1429+
return true;
1430+
}
1431+
1432+
e = e is MemberExpression ? ((MemberExpression)e).Expression : null;
13761433
}
13771434

13781435
return false;
@@ -1426,19 +1483,14 @@ protected virtual object VisitMemberAccess(MemberExpression m)
14261483
throw new ArgumentException(string.Format("Expression '{0}' accesses unsupported property '{1}' of Nullable<T>", m, m.Member));
14271484
}
14281485

1429-
if (m.Expression.NodeType == ExpressionType.Parameter || m.Expression.NodeType == ExpressionType.Convert)
1430-
{
1431-
var isSubExprAccess = m.Expression is UnaryExpression &&
1432-
((UnaryExpression)m.Expression).Operand is IndexExpression;
1433-
if (!isSubExprAccess)
1434-
return GetMemberExpression(m);
1435-
}
1486+
if (IsParameterOrConvertAccess(m))
1487+
return GetMemberExpression(m);
14361488
}
14371489

14381490
return CachedExpressionCompiler.Evaluate(m);
14391491
}
14401492

1441-
private object GetMemberExpression(MemberExpression m)
1493+
protected virtual object GetMemberExpression(MemberExpression m)
14421494
{
14431495
var propertyInfo = m.Member as PropertyInfo;
14441496

@@ -1505,8 +1557,8 @@ private object SetAnonTypePropertyNamesForSelectExpression(object expr, Expressi
15051557
// to allow the caller to distinguish properties with the same names from different tables
15061558

15071559
var paramExpr = arg as ParameterExpression;
1508-
var selectList = paramExpr != null && paramExpr.Name != member.Name
1509-
? expr as SelectList
1560+
var selectList = paramExpr != null && paramExpr.Name != member.Name
1561+
? expr as SelectList
15101562
: null;
15111563
if (selectList != null)
15121564
{
@@ -1590,7 +1642,13 @@ protected virtual object VisitUnary(UnaryExpression u)
15901642
return GetNotValue(o);
15911643
case ExpressionType.Convert:
15921644
if (u.Method != null)
1645+
{
1646+
var e = u.Operand;
1647+
if (IsParameterAccess(e))
1648+
return Visit(e);
1649+
15931650
return CachedExpressionCompiler.Evaluate(u);
1651+
}
15941652
break;
15951653
}
15961654
return Visit(u.Operand);
@@ -1610,7 +1668,7 @@ protected virtual object VisitIndexExpression(IndexExpression e)
16101668
var list = oCollection as List<object>;
16111669
if (list != null)
16121670
return list[index];
1613-
1671+
16141672
throw new NotImplementedException("Unknown Expression: " + e);
16151673
}
16161674

@@ -1629,7 +1687,7 @@ private bool IsColumnAccess(MethodCallExpression m)
16291687
{
16301688
if (m.Object != null && m.Object as MethodCallExpression != null)
16311689
return IsColumnAccess(m.Object as MethodCallExpression);
1632-
1690+
16331691
var exp = m.Object as MemberExpression;
16341692
return IsParameterAccess(exp)
16351693
&& IsJoinedTable(exp.Expression.Type);
@@ -1753,11 +1811,11 @@ protected virtual string GetQuotedColumnName(ModelDefinition tableDef, string me
17531811
if (useFieldName)
17541812
{
17551813
var fd = tableDef.FieldDefinitions.FirstOrDefault(x => x.Name == memberName);
1756-
var fieldName = fd != null
1757-
? fd.FieldName
1814+
var fieldName = fd != null
1815+
? fd.FieldName
17581816
: memberName;
17591817

1760-
return PrefixFieldWithTableName
1818+
return PrefixFieldWithTableName
17611819
? DialectProvider.GetQuotedColumnName(tableDef, fieldName)
17621820
: DialectProvider.GetQuotedColumnName(fieldName);
17631821
}
@@ -1962,7 +2020,7 @@ protected string ConvertInExpressionToSql(MethodCallExpression m, object quotedC
19622020
string sqlIn = CreateInParamSql(inArgs);
19632021
return string.Format("{0} {1} ({2})", quotedColName, m.Method.Name, sqlIn);
19642022
}
1965-
2023+
19662024
var exprArg = argValue as ISqlExpression;
19672025
if (exprArg != null)
19682026
{

tests/ServiceStack.OrmLite.Tests/ExpressionVisitorTests.cs

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Data;
44
using System.Linq;
55
using NUnit.Framework;
6+
using ServiceStack.DataAnnotations;
67

78
namespace ServiceStack.OrmLite.Tests
89
{
@@ -17,10 +18,23 @@ public void Setup()
1718
using (var db = OpenDbConnection())
1819
{
1920
db.DropAndCreateTable<TestType>();
20-
db.Insert(new TestType { Id = 1, BoolCol = true, DateCol = new DateTime(2012, 1, 1), TextCol = "asdf", EnumCol = TestEnum.Val0, NullableIntCol = 10 });
21-
db.Insert(new TestType { Id = 2, BoolCol = true, DateCol = new DateTime(2012, 2, 1), TextCol = "asdf123", EnumCol = TestEnum.Val1, NullableIntCol = null });
22-
db.Insert(new TestType { Id = 3, BoolCol = false, DateCol = new DateTime(2012, 3, 1), TextCol = "qwer", EnumCol = TestEnum.Val2, NullableIntCol = 30 });
23-
db.Insert(new TestType { Id = 4, BoolCol = false, DateCol = new DateTime(2012, 4, 1), TextCol = "qwer123", EnumCol = TestEnum.Val3, NullableIntCol = 40 });
21+
db.Insert(new TestType { Id = 1, BoolCol = true, DateCol = new DateTime(2012, 1, 1), TextCol = "asdf", EnumCol = TestEnum.Val0, NullableIntCol = 10, TestType2ObjColId = 1 });
22+
db.Insert(new TestType { Id = 2, BoolCol = true, DateCol = new DateTime(2012, 2, 1), TextCol = "asdf123", EnumCol = TestEnum.Val1, NullableIntCol = null, TestType2ObjColId = 2 });
23+
db.Insert(new TestType { Id = 3, BoolCol = false, DateCol = new DateTime(2012, 3, 1), TextCol = "qwer", EnumCol = TestEnum.Val2, NullableIntCol = 30, TestType2ObjColId = 3 });
24+
db.Insert(new TestType { Id = 4, BoolCol = false, DateCol = new DateTime(2012, 4, 1), TextCol = "qwer123", EnumCol = TestEnum.Val3, NullableIntCol = 40, TestType2ObjColId = 4 });
25+
26+
db.DropAndCreateTable<TestType2>();
27+
db.Insert(new TestType2 { Id = 1, BoolCol = true, DateCol = new DateTime(2012, 4, 1), TextCol = "111", EnumCol = TestEnum.Val3, NullableIntCol = 10, TestType2Name = "2.1", TestType3ObjColId = 1});
28+
db.Insert(new TestType2 { Id = 2, BoolCol = false, DateCol = new DateTime(2012, 4, 1), TextCol = "222", EnumCol = TestEnum.Val3, NullableIntCol = 20, TestType2Name = "2.2", TestType3ObjColId = 2 });
29+
db.Insert(new TestType2 { Id = 3, BoolCol = true, DateCol = new DateTime(2012, 4, 1), TextCol = "333", EnumCol = TestEnum.Val3, NullableIntCol = 30, TestType2Name = "2.3", TestType3ObjColId = 3 });
30+
db.Insert(new TestType2 { Id = 4, BoolCol = false, DateCol = new DateTime(2012, 4, 1), TextCol = "444", EnumCol = TestEnum.Val3, NullableIntCol = 40, TestType2Name = "2.4", TestType3ObjColId = 4 });
31+
32+
33+
db.DropAndCreateTable<TestType3>();
34+
db.Insert(new TestType3 { Id = 1, BoolCol = true, DateCol = new DateTime(2012, 4, 1), TextCol = "111", EnumCol = TestEnum.Val3, NullableIntCol = 10, TestType3Name = "3.1", CustomInt = 100});
35+
db.Insert(new TestType3 { Id = 2, BoolCol = false, DateCol = new DateTime(2012, 4, 1), TextCol = "222", EnumCol = TestEnum.Val3, NullableIntCol = 20, TestType3Name = "3.2", CustomInt = 200 });
36+
db.Insert(new TestType3 { Id = 3, BoolCol = false, DateCol = new DateTime(2012, 4, 1), TextCol = "222", EnumCol = TestEnum.Val3, NullableIntCol = 30, TestType3Name = "3.3", CustomInt = 300});
37+
db.Insert(new TestType3 { Id = 4, BoolCol = false, DateCol = new DateTime(2012, 4, 1), TextCol = "222", EnumCol = TestEnum.Val3, NullableIntCol = 40, TestType3Name = "3.4", CustomInt = 400 });
2438
}
2539
Db = OpenDbConnection();
2640
}
@@ -338,6 +352,65 @@ public void Can_Select_using_expression_evaluated_to_constant()
338352
CollectionAssert.AreEquivalent(new[] { 1, 3, 4 }, target.Select(t => t.Id).ToArray());
339353
}
340354

355+
[Test]
356+
public void Can_Where_using_filter_with_nested_properties()
357+
{
358+
string filterText2 = "2.1";
359+
string filterText3 = "3.3";
360+
bool? nullableTrue = true;
361+
362+
var q = Db.From<TestType>().
363+
Join<TestType2>().
364+
Where(x => (!x.NullableBoolCol.HasValue || x.NullableBoolCol.Value) && x.NullableIntCol.HasValue && x.TestType2ObjCol.BoolCol);
365+
var target = Db.Select(q);
366+
Assert.AreEqual(2, target.Count);
367+
368+
q = Db.From<TestType>().
369+
Join<TestType2>().
370+
Where(x => x.TestType2ObjCol.BoolCol && x.DateCol != DateTime.MinValue);
371+
target = Db.Select(q);
372+
Assert.AreEqual(2, target.Count);
373+
374+
q = Db.From<TestType>().
375+
Join<TestType2>().
376+
Where(x => x.TestType2ObjCol.BoolCol && x.TestType2ObjCol.BoolCol == nullableTrue &&
377+
x.DateCol != DateTime.MinValue && x.TestType2ObjCol.TestType2Name == filterText2);
378+
target = Db.Select(q);
379+
Assert.AreEqual(1, target.Count);
380+
381+
var intValue = 300;
382+
q = Db.From<TestType>().
383+
Join<TestType2>().
384+
Join<TestType2, TestType3>().
385+
Where(x => !x.NullableBoolCol.HasValue && x.TestType2ObjCol.BoolCol &&
386+
x.TestType2ObjCol.TestType3ObjCol.TestType3Name == filterText3 &&
387+
x.TestType2ObjCol.TestType3ObjCol.CustomInt == new CustomInt(intValue));
388+
target = Db.Select(q);
389+
Assert.AreEqual(1, target.Count);
390+
391+
q = Db.From<TestType>().
392+
Join<TestType2>().
393+
Join<TestType2, TestType3>().
394+
Where(x => !x.NullableBoolCol.HasValue && x.TestType2ObjCol.BoolCol &&
395+
x.NullableIntCol == new CustomInt(10)).
396+
GroupBy(x => x.TestType2ObjCol.TestType3ObjCol.CustomInt).
397+
Having(x => (Sql.Max(x.TestType2ObjCol.TestType3ObjCol.CustomInt) ?? 0) == new CustomInt(100)).
398+
Select(x => x.TestType2ObjCol.TestType3ObjCol.CustomInt);
399+
target = Db.Select(q);
400+
Assert.AreEqual(1, target.Count);
401+
402+
q = Db.From<TestType>().
403+
Join<TestType2>().
404+
Join<TestType2, TestType3>().
405+
Where(x => !x.NullableBoolCol.HasValue && x.TestType2ObjCol.BoolCol &&
406+
x.NullableIntCol == new CustomInt(10)).
407+
GroupBy(x => x.TestType2ObjCol.TestType3ObjCol.CustomInt).
408+
Having(x => (Sql.Max(x.TestType2ObjCol.TestType3ObjCol.CustomInt) ?? 0) != 10).
409+
Select(x => x.TestType2ObjCol.TestType3ObjCol.CustomInt);
410+
target = Db.Select(q);
411+
Assert.AreEqual(1, target.Count);
412+
}
413+
341414
private int MethodReturningInt(int val)
342415
{
343416
return val;
@@ -372,6 +445,10 @@ public class TestType
372445
public TestEnum EnumCol { get; set; }
373446
public TestType ComplexObjCol { get; set; }
374447
public int? NullableIntCol { get; set; }
448+
449+
[ForeignKey(typeof(TestType2))]
450+
public int TestType2ObjColId { get; set; }
451+
public TestType2 TestType2ObjCol { get; set; }
375452
}
376453

377454
public class TestType2
@@ -384,5 +461,54 @@ public class TestType2
384461
public TestEnum EnumCol { get; set; }
385462
public TestType ComplexObjCol { get; set; }
386463
public int? NullableIntCol { get; set; }
464+
public string TestType2Name { get; set; }
465+
466+
[ForeignKey(typeof(TestType3))]
467+
public int TestType3ObjColId { get; set; }
468+
public TestType3 TestType3ObjCol { get; set; }
469+
}
470+
471+
public class TestType3
472+
{
473+
public int Id { get; set; }
474+
public string TextCol { get; set; }
475+
public bool BoolCol { get; set; }
476+
public bool? NullableBoolCol { get; set; }
477+
public DateTime DateCol { get; set; }
478+
public TestEnum EnumCol { get; set; }
479+
public TestType3 ComplexObjCol { get; set; }
480+
public int? NullableIntCol { get; set; }
481+
public string TestType3Name { get; set; }
482+
public CustomInt CustomInt { get; set; }
483+
}
484+
485+
/// <summary>
486+
/// For testing VisitUnary with expression "u" that: u.Method != null (implicit conversion)
487+
/// </summary>
488+
public class CustomInt
489+
{
490+
private readonly int _value;
491+
492+
public CustomInt(int value)
493+
{
494+
_value = value;
495+
}
496+
497+
public int Value
498+
{
499+
get { return _value; }
500+
}
501+
502+
503+
public static implicit operator int(CustomInt s)
504+
{
505+
return s.Value;
506+
}
507+
508+
public static implicit operator CustomInt(int s)
509+
{
510+
return new CustomInt(s);
511+
}
512+
387513
}
388514
}

0 commit comments

Comments
 (0)