Skip to content

Commit 3dafb8b

Browse files
authored
CSHARP-5540: Fix exception when using AsQueryable().Last() (#1649)
1 parent 18365f8 commit 3dafb8b

File tree

2 files changed

+201
-1
lines changed

2 files changed

+201
-1
lines changed

Diff for: src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToExecutableQueryTranslators/LastMethodToExecutableQueryTranslator.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ public static ExecutableQuery<TDocument, TOutput> Translate<TDocument>(MongoQuer
7575
pipeline.OutputSerializer);
7676
}
7777

78-
pipeline = pipeline.AddStage(
78+
pipeline = pipeline.AddStages(
7979
AstStage.Group(
8080
id: BsonNull.Value,
8181
fields: AstExpression.AccumulatorField("_last", AstUnaryAccumulatorOperator.Last, AstExpression.RootVar)),
82+
AstStage.ReplaceRoot(AstExpression.GetField(AstExpression.RootVar, "_last")),
8283
pipeline.OutputSerializer);
8384

8485
var finalizer = method.Name == "LastOrDefault" ? __singleOrDefaultFinalizer : __singleFinalizer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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 System.Collections.Generic;
18+
using System.Linq;
19+
using FluentAssertions;
20+
using MongoDB.Driver.Linq;
21+
using MongoDB.Driver.TestHelpers;
22+
using Xunit;
23+
24+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToExecutableQueryTranslators
25+
{
26+
public class LastMethodToExecutableQueryTranslator : LinqIntegrationTest<LastMethodToExecutableQueryTranslator.ClassFixture>
27+
{
28+
public LastMethodToExecutableQueryTranslator(ClassFixture fixture)
29+
: base(fixture)
30+
{
31+
}
32+
33+
[Fact]
34+
public void Last_should_work()
35+
{
36+
var collection = Fixture.Collection;
37+
var queryable = collection.AsQueryable().OrderBy(t => t.Id);
38+
39+
var result = queryable.Last();
40+
var stages = queryable.GetMongoQueryProvider().LoggedStages;
41+
42+
AssertStages(
43+
stages,
44+
"{ $sort : { _id : 1 } }",
45+
"{ $group : { _id : null, _last : { $last : '$$ROOT' } } }",
46+
"{ $replaceRoot : { newRoot : '$_last' } }");
47+
48+
result.Id.Should().Be(3);
49+
}
50+
51+
[Fact]
52+
public void Last_with_no_matching_documents_should_throw()
53+
{
54+
var collection = Fixture.Collection;
55+
var queryable = collection.AsQueryable().Where(t => t.Id > 5);
56+
57+
var exception = Record.Exception(() => queryable.Last());
58+
var stages = queryable.GetMongoQueryProvider().LoggedStages;
59+
60+
AssertStages(
61+
stages,
62+
"{ $match : { _id : { $gt : 5 } }}",
63+
"{ $group : { _id : null, _last : { $last : '$$ROOT' } } }",
64+
"{ $replaceRoot : { newRoot : '$_last' } }");
65+
66+
exception.Should().BeOfType<InvalidOperationException>();
67+
exception.Message.Should().Contain("Sequence contains no elements");
68+
}
69+
70+
[Fact]
71+
public void LastOrDefault_should_work()
72+
{
73+
var collection = Fixture.Collection;
74+
var queryable = collection.AsQueryable().OrderBy(t => t.Id);
75+
76+
var result = queryable.LastOrDefault();
77+
var stages = queryable.GetMongoQueryProvider().LoggedStages;
78+
79+
AssertStages(
80+
stages,
81+
"{ $sort : { _id : 1 } }",
82+
"{ $group : { _id : null, _last : { $last : '$$ROOT' } } }",
83+
"{ $replaceRoot : { newRoot : '$_last' } }");
84+
85+
result.Should().NotBeNull();
86+
result.Id.Should().Be(3);
87+
}
88+
89+
[Fact]
90+
public void LastOrDefault_with_no_matching_documents_should_work()
91+
{
92+
var collection = Fixture.Collection;
93+
var queryable = collection.AsQueryable().Where(t => t.Id > 5);
94+
95+
var result = queryable.LastOrDefault();
96+
var stages = queryable.GetMongoQueryProvider().LoggedStages;
97+
98+
AssertStages(
99+
stages,
100+
"{ $match : { _id : { $gt : 5 } }}",
101+
"{ $group : { _id : null, _last : { $last : '$$ROOT' } } }",
102+
"{ $replaceRoot : { newRoot : '$_last' } }");
103+
104+
result.Should().BeNull();
105+
}
106+
107+
[Fact]
108+
public void LastOrDefaultWithPredicate_should_work()
109+
{
110+
var collection = Fixture.Collection;
111+
var queryable = collection.AsQueryable().OrderBy(t => t.Id);
112+
113+
var result = queryable.LastOrDefault(t => t.Id > 1);
114+
var stages = queryable.GetMongoQueryProvider().LoggedStages;
115+
116+
AssertStages(
117+
stages,
118+
"{ $sort : { _id : 1 } }",
119+
"{ $match : { _id : { $gt : 1 } }}",
120+
"{ $group : { _id : null, _last : { $last : '$$ROOT' } } }",
121+
"{ $replaceRoot : { newRoot : '$_last' } }");
122+
123+
result.Should().NotBeNull();
124+
result.Id.Should().Be(3);
125+
}
126+
127+
[Fact]
128+
public void LastOrDefaultWithPredicate_with_no_matching_documents_should_work()
129+
{
130+
var collection = Fixture.Collection;
131+
var queryable = collection.AsQueryable();
132+
133+
var result = queryable.LastOrDefault(t => t.Id > 4);
134+
var stages = queryable.GetMongoQueryProvider().LoggedStages;
135+
136+
AssertStages(
137+
stages,
138+
"{ $match : { _id : { $gt : 4 } }}",
139+
"{ $group : { _id : null, _last : { $last : '$$ROOT' } } }",
140+
"{ $replaceRoot : { newRoot : '$_last' } }");
141+
142+
result.Should().BeNull();
143+
}
144+
145+
[Fact]
146+
public void LastWithPredicate_should_work()
147+
{
148+
var collection = Fixture.Collection;
149+
var queryable = collection.AsQueryable().OrderBy(t => t.Id);
150+
151+
var result = queryable.Last(t => t.Id > 1);
152+
var stages = queryable.GetMongoQueryProvider().LoggedStages;
153+
154+
AssertStages(
155+
stages,
156+
"{ $sort : { _id : 1 } }",
157+
"{ $match : { _id : { $gt : 1 } }}",
158+
"{ $group : { _id : null, _last : { $last : '$$ROOT' } } }",
159+
"{ $replaceRoot : { newRoot : '$_last' } }");
160+
161+
result.Id.Should().Be(3);
162+
}
163+
164+
[Fact]
165+
public void LastWithPredicate_with_no_matching_documents_should_throw()
166+
{
167+
var collection = Fixture.Collection;
168+
var queryable = collection.AsQueryable();
169+
170+
var exception = Record.Exception(() => queryable.Last(t => t.Id > 4));
171+
var stages = queryable.GetMongoQueryProvider().LoggedStages;
172+
173+
AssertStages(
174+
stages,
175+
"{ $match : { _id : { $gt : 4 } }}",
176+
"{ $group : { _id : null, _last : { $last : '$$ROOT' } } }",
177+
"{ $replaceRoot : { newRoot : '$_last' } }");
178+
179+
exception.Should().BeOfType<InvalidOperationException>();
180+
exception.Message.Should().Contain("Sequence contains no elements");
181+
}
182+
183+
public class TestClass
184+
{
185+
public int Id { get; set; }
186+
public string StringProperty { get; set; }
187+
}
188+
189+
public sealed class ClassFixture : MongoCollectionFixture<TestClass>
190+
{
191+
protected override IEnumerable<TestClass> InitialData { get; } =
192+
[
193+
new TestClass { Id = 1, StringProperty = "A" },
194+
new TestClass { Id = 2, StringProperty = "B" },
195+
new TestClass { Id = 3, StringProperty = "C" }
196+
];
197+
}
198+
}
199+
}

0 commit comments

Comments
 (0)