Skip to content

Commit 5412f33

Browse files
committed
CSHARP-4743: Add support for DateTime.Date and DateTime.TimeOfDay.
1 parent 208438f commit 5412f33

File tree

2 files changed

+169
-22
lines changed

2 files changed

+169
-22
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Reflection;
2121
using MongoDB.Bson;
2222
using MongoDB.Bson.Serialization;
23+
using MongoDB.Bson.Serialization.Options;
2324
using MongoDB.Bson.Serialization.Serializers;
2425
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
2526
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
@@ -128,29 +129,42 @@ private static bool TryTranslateDateTimeProperty(MemberExpression expression, Ag
128129
AstExpression ast;
129130
IBsonSerializer serializer;
130131

131-
if (propertyInfo.Name == "DayOfWeek")
132+
switch (propertyInfo.Name)
132133
{
133-
ast = AstExpression.Subtract(AstExpression.DatePart(AstDatePart.DayOfWeek, container.Ast), 1);
134-
serializer = new EnumSerializer<DayOfWeek>(BsonType.Int32);
135-
}
136-
else
137-
{
138-
AstDatePart datePart;
139-
switch (propertyInfo.Name)
140-
{
141-
case "Day": datePart = AstDatePart.DayOfMonth; break;
142-
case "DayOfWeek": datePart = AstDatePart.DayOfWeek; break;
143-
case "DayOfYear": datePart = AstDatePart.DayOfYear; break;
144-
case "Hour": datePart = AstDatePart.Hour; break;
145-
case "Millisecond": datePart = AstDatePart.Millisecond; break;
146-
case "Minute": datePart = AstDatePart.Minute; break;
147-
case "Month": datePart = AstDatePart.Month; break;
148-
case "Second": datePart = AstDatePart.Second; break;
149-
case "Year": datePart = AstDatePart.Year; break;
150-
default: return false;
151-
}
152-
ast = AstExpression.DatePart(datePart, container.Ast);
153-
serializer = new Int32Serializer();
134+
case "Date":
135+
ast = AstExpression.DateTrunc(container.Ast, "day");
136+
serializer = container.Serializer;
137+
break;
138+
139+
case "DayOfWeek":
140+
ast = AstExpression.Subtract(AstExpression.DatePart(AstDatePart.DayOfWeek, container.Ast), 1);
141+
serializer = new EnumSerializer<DayOfWeek>(BsonType.Int32);
142+
break;
143+
144+
case "TimeOfDay":
145+
var endDate = container.Ast;
146+
var startDate = AstExpression.DateTrunc(container.Ast, "day");
147+
ast = AstExpression.DateDiff(startDate, endDate, "millisecond");
148+
serializer = new TimeSpanSerializer(BsonType.Int64, TimeSpanUnits.Milliseconds);
149+
break;
150+
151+
default:
152+
var datePart = propertyInfo.Name switch
153+
{
154+
"Day" => AstDatePart.DayOfMonth,
155+
"DayOfWeek" => AstDatePart.DayOfWeek,
156+
"DayOfYear" => AstDatePart.DayOfYear,
157+
"Hour" => AstDatePart.Hour,
158+
"Millisecond" => AstDatePart.Millisecond,
159+
"Minute" => AstDatePart.Minute,
160+
"Month" => AstDatePart.Month,
161+
"Second" => AstDatePart.Second,
162+
"Year" => AstDatePart.Year,
163+
_ => throw new ExpressionNotSupportedException(expression)
164+
};
165+
ast = AstExpression.DatePart(datePart, container.Ast);
166+
serializer = new Int32Serializer();
167+
break;
154168
}
155169

156170
result = new AggregationExpression(expression, ast, serializer);
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using FluentAssertions;
18+
using MongoDB.Driver.Core.Misc;
19+
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
20+
using MongoDB.Driver.Linq;
21+
using MongoDB.TestHelpers.XunitExtensions;
22+
using Xunit;
23+
24+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
25+
{
26+
public class CSharp4743Tests : Linq3IntegrationTest
27+
{
28+
[Theory]
29+
[ParameterAttributeData]
30+
public void Where_using_DateTime_Date_should_work(
31+
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
32+
{
33+
RequireServer.Check().Supports(Feature.DateOperatorsNewIn50);
34+
var collection = GetCollection(linqProvider);
35+
var memberId = 1;
36+
var startDateTime = new DateTime(2023, 08, 07, 1, 2, 3, DateTimeKind.Utc);
37+
38+
var queryable = collection.AsQueryable()
39+
.Where(
40+
b => b.MemberId == memberId &&
41+
b.InteractionDate.HasValue && b.InteractionDate.Value.Date >= startDateTime.Date);
42+
43+
if (linqProvider == LinqProvider.V2)
44+
{
45+
var exception = Record.Exception(() => Translate(collection, queryable));
46+
exception.Should().BeOfType<InvalidOperationException>();
47+
}
48+
else
49+
{
50+
var stages = Translate(collection, queryable);
51+
AssertStages(stages, "{ $match : { $and : [{ MemberId : 1 }, { InteractionDate : { $ne : null } }, { $expr : { $gte : [{ $dateTrunc : { date : '$InteractionDate', unit : 'day' } }, ISODate('2023-08-07')] } }] } }");
52+
53+
var result = queryable.Single();
54+
result.Id.Should().Be(1);
55+
}
56+
}
57+
58+
[Theory]
59+
[ParameterAttributeData]
60+
public void Where_using_DateTime_TimeOfDay_should_work(
61+
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
62+
{
63+
RequireServer.Check().Supports(Feature.DateOperatorsNewIn50);
64+
var collection = GetCollection(linqProvider);
65+
var memberId = 1;
66+
var startTimeOfDay = TimeSpan.FromHours(1);
67+
68+
var queryable = collection.AsQueryable()
69+
.Where(
70+
b => b.MemberId == memberId &&
71+
b.InteractionDate.HasValue && b.InteractionDate.Value.TimeOfDay >= startTimeOfDay);
72+
73+
if (linqProvider == LinqProvider.V2)
74+
{
75+
var exception = Record.Exception(() => Translate(collection, queryable));
76+
exception.Should().BeOfType<InvalidOperationException>();
77+
}
78+
else
79+
{
80+
var stages = Translate(collection, queryable);
81+
AssertStages(stages, "{ $match : { $and : [{ MemberId : 1 }, { InteractionDate : { $ne : null } }, { $expr : { $gte : [{ $dateDiff : { startDate : { $dateTrunc : { date : '$InteractionDate', unit : 'day' } }, endDate : '$InteractionDate', unit : 'millisecond' } }, { $numberLong : 3600000 }] } }] } }");
82+
83+
var result = queryable.Single();
84+
result.Id.Should().Be(1);
85+
}
86+
}
87+
88+
[Theory]
89+
[ParameterAttributeData]
90+
public void Where_using_DateTime_Year_should_work(
91+
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
92+
{
93+
var collection = GetCollection(linqProvider);
94+
var memberId = 1;
95+
var startDateTime = new DateTime(2023, 08, 07, 0, 0, 0, DateTimeKind.Utc);
96+
97+
var queryable = collection.AsQueryable()
98+
.Where(
99+
b => b.MemberId == memberId &&
100+
b.InteractionDate.HasValue && b.InteractionDate.Value.Year >= startDateTime.Year);
101+
102+
if (linqProvider == LinqProvider.V2)
103+
{
104+
var exception = Record.Exception(() => Translate(collection, queryable));
105+
exception.Should().BeOfType<InvalidOperationException>();
106+
}
107+
else
108+
{
109+
var stages = Translate(collection, queryable);
110+
AssertStages(stages, "{ $match : { $and : [{ MemberId : 1 }, { InteractionDate : { $ne : null } }, { $expr : { $gte : [{ $year : '$InteractionDate' }, 2023] } }] } }");
111+
112+
var result = queryable.Single();
113+
result.Id.Should().Be(1);
114+
}
115+
}
116+
117+
private IMongoCollection<C> GetCollection(LinqProvider linqProvider)
118+
{
119+
var collection = GetCollection<C>("test", linqProvider);
120+
CreateCollection(
121+
collection,
122+
new C { Id = 1, MemberId = 1, InteractionDate = new DateTime(2023, 08, 07, 1, 2, 3, DateTimeKind.Utc) });
123+
return collection;
124+
}
125+
126+
private class C
127+
{
128+
public int Id { get; set; }
129+
public int MemberId { get; set; }
130+
public DateTime? InteractionDate { get; set; }
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)