Skip to content

Commit a380012

Browse files
authored
Merge pull request #49 from joadan/support-timeonly-filter
Support timeonly filter
2 parents c701955 + d462fe8 commit a380012

3 files changed

Lines changed: 271 additions & 8 deletions

File tree

src/Linq2OData.Core/Expressions/FilterExpressionVisitor.cs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,22 @@ protected override Expression VisitConstant(ConstantExpression c)
385385
return c;
386386
}
387387

388+
protected override Expression VisitNew(NewExpression n)
389+
{
390+
try
391+
{
392+
var lambda = Expression.Lambda(n);
393+
var compiled = lambda.Compile();
394+
var value = compiled.DynamicInvoke();
395+
AppendByValueType(value, sb);
396+
return n;
397+
}
398+
catch
399+
{
400+
throw new NotSupportedException($"The constructor for '{n.Type.Name}' is not supported");
401+
}
402+
}
403+
388404
protected override Expression VisitMember(MemberExpression m)
389405
{
390406
// Handle string.Length property on entity properties
@@ -578,6 +594,14 @@ private void AppendByValueType(object? value, StringBuilder sb)
578594
{
579595
sb.Append(FilterHelper.ToODataFilter(dateTimeOffset, odataVersion));
580596
}
597+
else if (value is TimeOnly timeOnly)
598+
{
599+
sb.Append(FilterHelper.ToODataFilter(timeOnly, odataVersion));
600+
}
601+
else if (value is DateOnly dateOnly)
602+
{
603+
sb.Append(FilterHelper.ToODataFilter(dateOnly, odataVersion));
604+
}
581605
else
582606
{
583607
throw new NotSupportedException($"The constant for '{value}' is not supported");
@@ -599,18 +623,24 @@ private static string GetODataMemberName(MemberInfo member)
599623
{
600624
foreach (var accessName in memberAccessNames)
601625
{
602-
var property = value.GetType().GetProperty(accessName);
603-
if (property == null)
626+
var type = value.GetType();
627+
var property = type.GetProperty(accessName);
628+
if (property != null)
604629
{
605-
throw new NotSupportedException($"Property '{accessName}' not found on type '{value.GetType().Name}'");
630+
var nextValue = property.GetValue(value);
631+
if (nextValue == null) return null;
632+
value = nextValue;
606633
}
607-
608-
var nextValue = property.GetValue(value);
609-
if (nextValue == null)
634+
else
610635
{
611-
return null;
636+
var field = type.GetField(accessName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
637+
if (field == null)
638+
throw new NotSupportedException($"Property or field '{accessName}' not found on type '{type.Name}'");
639+
640+
var nextValue = field.GetValue(value);
641+
if (nextValue == null) return null;
642+
value = nextValue;
612643
}
613-
value = nextValue;
614644
}
615645
return value;
616646
}

src/Linq2OData.Core/Expressions/FilterHelper.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ public static string ToODataFilter(DateTimeOffset date, ODataVersion version)
6060
}
6161
}
6262

63+
public static string ToODataFilter(TimeOnly time, ODataVersion version)
64+
{
65+
return $"time'{time:HH:mm:ss}'";
66+
}
67+
68+
public static string ToODataFilter(DateOnly date, ODataVersion version)
69+
{
70+
return $"date'{date:yyyy-MM-dd}'";
71+
}
72+
6373

6474

6575
}

test/Linq2OData.Tests/FilterExpressionTests.cs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ public class TestProduct : IODataEntitySet
2222
public DateTime? DiscontinuedDate { get; set; }
2323
public DateTimeOffset LastModified { get; set; }
2424
public DateTimeOffset? LastChecked { get; set; }
25+
public TimeOnly OpenTime { get; set; }
26+
public TimeOnly? CloseTime { get; set; }
27+
public DateOnly OpenDate { get; set; }
28+
public DateOnly? CloseDate { get; set; }
2529
public TestCategory? Category { get; set; }
2630
public string __Key => $"ID={ID}";
2731
}
@@ -256,6 +260,24 @@ public void ODataFilterVisitor_GreaterThanOrEqual_GeneratesCorrectFilter()
256260
Assert.Equal("(Stock ge 10)", result);
257261
}
258262

263+
[Fact]
264+
public void ODataFilterVisitor_ConstantAsObject_GeneratesCorrectFilter()
265+
{
266+
267+
var product = new TestProduct { ID = 10, Name = "Test", Price = 9.99m, Stock = 100, IsAvailable = true, CreatedDate = DateTime.Now, LastModified = DateTimeOffset.Now, OpenTime = TimeOnly.FromTimeSpan(TimeSpan.FromHours(9)), OpenDate = DateOnly.FromDateTime(DateTime.Today) };
268+
269+
// Arrange
270+
var visitor = new ODataFilterVisitor();
271+
Expression<Func<TestProduct, bool>> expression = p => p.Stock >= product.ID;
272+
273+
// Act
274+
var result = visitor.ToFilter(expression, ODataVersion.V4);
275+
276+
// Assert
277+
Assert.Equal("(Stock ge 10)", result);
278+
}
279+
280+
259281
#endregion
260282

261283
#region Boolean Tests
@@ -1419,4 +1441,205 @@ public void ODataFilterVisitor_EnumInComplexExpression_GeneratesCorrectFilter()
14191441
}
14201442

14211443
#endregion
1444+
1445+
#region DateOnly Tests
1446+
1447+
[Fact]
1448+
public void ODataFilterVisitor_DateOnlyConstant_V4_GeneratesCorrectFilter()
1449+
{
1450+
// Arrange
1451+
var visitor = new ODataFilterVisitor();
1452+
var openDate = new DateOnly(2024, 6, 15);
1453+
Expression<Func<TestProduct, bool>> expression = p => p.OpenDate == openDate;
1454+
1455+
// Act
1456+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1457+
1458+
// Assert
1459+
Assert.Equal("(OpenDate eq date'2024-06-15')", result);
1460+
}
1461+
1462+
[Fact]
1463+
public void ODataFilterVisitor_DateOnlyConstant_V2_GeneratesCorrectFilter()
1464+
{
1465+
// Arrange
1466+
var visitor = new ODataFilterVisitor();
1467+
var openDate = new DateOnly(2024, 6, 15);
1468+
Expression<Func<TestProduct, bool>> expression = p => p.OpenDate == openDate;
1469+
1470+
// Act
1471+
var result = visitor.ToFilter(expression, ODataVersion.V2);
1472+
1473+
// Assert
1474+
Assert.Equal("(OpenDate eq date'2024-06-15')", result);
1475+
}
1476+
1477+
[Fact]
1478+
public void ODataFilterVisitor_DateOnlyGreaterThan_V4_GeneratesCorrectFilter()
1479+
{
1480+
// Arrange
1481+
var visitor = new ODataFilterVisitor();
1482+
var cutoff = new DateOnly(2024, 1, 1);
1483+
Expression<Func<TestProduct, bool>> expression = p => p.OpenDate > cutoff;
1484+
1485+
// Act
1486+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1487+
1488+
// Assert
1489+
Assert.Equal("(OpenDate gt date'2024-01-01')", result);
1490+
}
1491+
1492+
[Fact]
1493+
public void ODataFilterVisitor_DateOnlyLessThan_V4_GeneratesCorrectFilter()
1494+
{
1495+
// Arrange
1496+
var visitor = new ODataFilterVisitor();
1497+
var cutoff = new DateOnly(2024, 12, 31);
1498+
Expression<Func<TestProduct, bool>> expression = p => p.OpenDate < cutoff;
1499+
1500+
// Act
1501+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1502+
1503+
// Assert
1504+
Assert.Equal("(OpenDate lt date'2024-12-31')", result);
1505+
}
1506+
1507+
[Fact]
1508+
public void ODataFilterVisitor_NullableDateOnly_V4_GeneratesCorrectFilter()
1509+
{
1510+
// Arrange
1511+
var visitor = new ODataFilterVisitor();
1512+
var closeDate = new DateOnly(2024, 9, 30);
1513+
Expression<Func<TestProduct, bool>> expression = p => p.CloseDate == closeDate;
1514+
1515+
// Act
1516+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1517+
1518+
// Assert
1519+
Assert.Equal("(CloseDate eq date'2024-09-30')", result);
1520+
}
1521+
1522+
[Fact]
1523+
public void ODataFilterVisitor_DateOnlyInlineConstant_V4_GeneratesCorrectFilter()
1524+
{
1525+
// Arrange
1526+
var visitor = new ODataFilterVisitor();
1527+
Expression<Func<TestProduct, bool>> expression = p => p.OpenDate >= new DateOnly(2024, 3, 1);
1528+
1529+
// Act
1530+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1531+
1532+
// Assert
1533+
Assert.Equal("(OpenDate ge date'2024-03-01')", result);
1534+
}
1535+
1536+
#endregion
1537+
1538+
#region TimeOnly Tests
1539+
1540+
[Fact]
1541+
public void ODataFilterVisitor_TimeOnlyConstant_V4_GeneratesCorrectFilter()
1542+
{
1543+
// Arrange
1544+
var visitor = new ODataFilterVisitor();
1545+
var openTime = new TimeOnly(9, 0, 0);
1546+
Expression<Func<TestProduct, bool>> expression = p => p.OpenTime == openTime;
1547+
1548+
// Act
1549+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1550+
1551+
// Assert
1552+
Assert.Equal("(OpenTime eq time'09:00:00')", result);
1553+
}
1554+
1555+
[Fact]
1556+
public void ODataFilterVisitor_TimeOnlyConstant_V2_GeneratesCorrectFilter()
1557+
{
1558+
// Arrange
1559+
var visitor = new ODataFilterVisitor();
1560+
var openTime = new TimeOnly(9, 0, 0);
1561+
Expression<Func<TestProduct, bool>> expression = p => p.OpenTime == openTime;
1562+
1563+
// Act
1564+
var result = visitor.ToFilter(expression, ODataVersion.V2);
1565+
1566+
// Assert
1567+
Assert.Equal("(OpenTime eq time'09:00:00')", result);
1568+
}
1569+
1570+
[Fact]
1571+
public void ODataFilterVisitor_TimeOnlyGreaterThan_V4_GeneratesCorrectFilter()
1572+
{
1573+
// Arrange
1574+
var visitor = new ODataFilterVisitor();
1575+
var cutoff = new TimeOnly(17, 30, 0);
1576+
Expression<Func<TestProduct, bool>> expression = p => p.OpenTime > cutoff;
1577+
1578+
// Act
1579+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1580+
1581+
// Assert
1582+
Assert.Equal("(OpenTime gt time'17:30:00')", result);
1583+
}
1584+
1585+
[Fact]
1586+
public void ODataFilterVisitor_TimeOnlyLessThan_V4_GeneratesCorrectFilter()
1587+
{
1588+
// Arrange
1589+
var visitor = new ODataFilterVisitor();
1590+
var cutoff = new TimeOnly(12, 0, 0);
1591+
Expression<Func<TestProduct, bool>> expression = p => p.OpenTime < cutoff;
1592+
1593+
// Act
1594+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1595+
1596+
// Assert
1597+
Assert.Equal("(OpenTime lt time'12:00:00')", result);
1598+
}
1599+
1600+
[Fact]
1601+
public void ODataFilterVisitor_TimeOnlyWithSeconds_V4_GeneratesCorrectFilter()
1602+
{
1603+
// Arrange
1604+
var visitor = new ODataFilterVisitor();
1605+
var closeTime = new TimeOnly(23, 59, 59);
1606+
Expression<Func<TestProduct, bool>> expression = p => p.OpenTime <= closeTime;
1607+
1608+
// Act
1609+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1610+
1611+
// Assert
1612+
Assert.Equal("(OpenTime le time'23:59:59')", result);
1613+
}
1614+
1615+
[Fact]
1616+
public void ODataFilterVisitor_NullableTimeOnly_V4_GeneratesCorrectFilter()
1617+
{
1618+
// Arrange
1619+
var visitor = new ODataFilterVisitor();
1620+
var closeTime = new TimeOnly(18, 0, 0);
1621+
Expression<Func<TestProduct, bool>> expression = p => p.CloseTime == closeTime;
1622+
1623+
// Act
1624+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1625+
1626+
// Assert
1627+
Assert.Equal("(CloseTime eq time'18:00:00')", result);
1628+
}
1629+
1630+
[Fact]
1631+
public void ODataFilterVisitor_TimeOnlyInlineConstant_V4_GeneratesCorrectFilter()
1632+
{
1633+
// Arrange
1634+
var visitor = new ODataFilterVisitor();
1635+
Expression<Func<TestProduct, bool>> expression = p => p.OpenTime >= new TimeOnly(8, 30, 0);
1636+
1637+
// Act
1638+
var result = visitor.ToFilter(expression, ODataVersion.V4);
1639+
1640+
// Assert
1641+
Assert.Equal("(OpenTime ge time'08:30:00')", result);
1642+
}
1643+
1644+
#endregion
14221645
}

0 commit comments

Comments
 (0)