Skip to content

Commit b0ea8a7

Browse files
author
rstam
committed
Implemented the Min and Max LINQ query operators.
1 parent 7c8a043 commit b0ea8a7

File tree

2 files changed

+184
-29
lines changed

2 files changed

+184
-29
lines changed

Driver/Linq/Translators/SelectQuery.cs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,13 @@ public override object Execute()
136136
var sortBy = new SortByDocument();
137137
foreach (var clause in _orderBy)
138138
{
139-
var memberExpression = (MemberExpression)clause.Key.Body;
140-
var serializationInfo = GetSerializationInfo(memberExpression);
139+
var keyExpression = clause.Key.Body;
140+
var serializationInfo = GetSerializationInfo(keyExpression);
141+
if (serializationInfo == null)
142+
{
143+
var message = string.Format("Invalid OrderBy expression: {0}.", ExpressionFormatter.ToString(keyExpression));
144+
throw new NotSupportedException(message);
145+
}
141146
var direction = (clause.Direction == OrderByDirection.Descending) ? -1 : 1;
142147
sortBy.Add(serializationInfo.ElementName, direction);
143148
}
@@ -231,6 +236,10 @@ public void Translate(Expression expression)
231236
case "LastOrDefault":
232237
TranslateLast(methodCallExpression);
233238
break;
239+
case "Max":
240+
case "Min":
241+
TranslateMaxMin(methodCallExpression);
242+
break;
234243
case "OrderBy":
235244
case "OrderByDescending":
236245
TranslateOrderBy(methodCallExpression);
@@ -1206,6 +1215,67 @@ private void TranslateLast(MethodCallExpression methodCallExpression)
12061215
}
12071216
}
12081217

1218+
private void TranslateMaxMin(MethodCallExpression methodCallExpression)
1219+
{
1220+
var methodName = methodCallExpression.Method.Name;
1221+
1222+
if (_orderBy != null)
1223+
{
1224+
var message = string.Format("{0} cannot be used with OrderBy.", methodName);
1225+
throw new NotSupportedException(message);
1226+
}
1227+
if (_skip != null || _take != null)
1228+
{
1229+
var message = string.Format("{0} cannot be used with Skip or Take.", methodName);
1230+
throw new NotSupportedException(message);
1231+
}
1232+
1233+
switch (methodCallExpression.Arguments.Count)
1234+
{
1235+
case 1:
1236+
break;
1237+
case 2:
1238+
if (_projection != null)
1239+
{
1240+
var message = string.Format("{0} must be used with either Select or a selector argument, but not both.", methodName);
1241+
throw new NotSupportedException(message);
1242+
}
1243+
_projection = (LambdaExpression)StripQuote(methodCallExpression.Arguments[1]);
1244+
break;
1245+
default:
1246+
throw new ArgumentOutOfRangeException("methodCallExpression");
1247+
}
1248+
if (_projection == null)
1249+
{
1250+
var message = string.Format("{0} must be used with either Select or a selector argument.", methodName);
1251+
throw new NotSupportedException(message);
1252+
}
1253+
1254+
// implement Max/Min by sorting on the relevant field(s) and taking the first result
1255+
_orderBy = new List<OrderByClause>();
1256+
if (_projection.Body.NodeType == ExpressionType.New)
1257+
{
1258+
// take the individual constructor arguments and make new lambdas out of them for the OrderByClauses
1259+
var newExpression = (NewExpression)_projection.Body;
1260+
foreach (var keyExpression in newExpression.Arguments)
1261+
{
1262+
var delegateTypeGenericDefinition = typeof(Func<,>);
1263+
var delegateType = delegateTypeGenericDefinition.MakeGenericType(_projection.Parameters[0].Type, keyExpression.Type);
1264+
var keyLambda = Expression.Lambda(delegateType, keyExpression, _projection.Parameters);
1265+
var clause = new OrderByClause(keyLambda, (methodName == "Min") ? OrderByDirection.Ascending : OrderByDirection.Descending);
1266+
_orderBy.Add(clause);
1267+
}
1268+
}
1269+
else
1270+
{
1271+
var clause = new OrderByClause(_projection, (methodName == "Min") ? OrderByDirection.Ascending : OrderByDirection.Descending);
1272+
_orderBy.Add(clause);
1273+
}
1274+
1275+
_take = Expression.Constant(1);
1276+
SetElementSelector(methodCallExpression, source => source.Cast<object>().First());
1277+
}
1278+
12091279
private void TranslateOrderBy(MethodCallExpression methodCallExpression)
12101280
{
12111281
if (methodCallExpression.Arguments.Count != 2)

DriverOnlineTests/Linq/SelectQueryTests.cs

Lines changed: 112 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,12 @@ public int GetHashCode(int obj)
126126
private MongoDatabase _database;
127127
private MongoCollection<C> _collection;
128128
private MongoCollection<SystemProfileInfo> _systemProfileCollection;
129-
private ObjectId id1 = ObjectId.GenerateNewId();
130-
private ObjectId id2 = ObjectId.GenerateNewId();
131-
private ObjectId id3 = ObjectId.GenerateNewId();
132-
private ObjectId id4 = ObjectId.GenerateNewId();
133-
private ObjectId id5 = ObjectId.GenerateNewId();
129+
130+
private ObjectId _id1 = ObjectId.GenerateNewId();
131+
private ObjectId _id2 = ObjectId.GenerateNewId();
132+
private ObjectId _id3 = ObjectId.GenerateNewId();
133+
private ObjectId _id4 = ObjectId.GenerateNewId();
134+
private ObjectId _id5 = ObjectId.GenerateNewId();
134135

135136
[TestFixtureSetUp]
136137
public void Setup()
@@ -143,11 +144,11 @@ public void Setup()
143144

144145
// documents inserted deliberately out of order to test sorting
145146
_collection.Drop();
146-
_collection.Insert(new C { Id = id2, X = 2, Y = 11, D = new D { Z = 22 }, A = new [] { 2, 3, 4 }, L = new List<int> { 2, 3, 4 } });
147-
_collection.Insert(new C { Id = id1, X = 1, Y = 11, D = new D { Z = 11 }, S = "x is 1", SA = new string[] { "Tom", "Dick", "Harry" } });
148-
_collection.Insert(new C { Id = id3, X = 3, Y = 33, D = new D { Z = 33 }, B = true, BA = new bool[] { true }, E = E.A, EA = new E[] { E.A, E.B } });
149-
_collection.Insert(new C { Id = id5, X = 5, Y = 44, D = new D { Z = 55 }, DBRef = new MongoDBRef("db", "c", 1) });
150-
_collection.Insert(new C { Id = id4, X = 4, Y = 44, D = new D { Z = 44 } });
147+
_collection.Insert(new C { Id = _id2, X = 2, Y = 11, D = new D { Z = 22 }, A = new [] { 2, 3, 4 }, L = new List<int> { 2, 3, 4 } });
148+
_collection.Insert(new C { Id = _id1, X = 1, Y = 11, D = new D { Z = 11 }, S = "x is 1", SA = new string[] { "Tom", "Dick", "Harry" } });
149+
_collection.Insert(new C { Id = _id3, X = 3, Y = 33, D = new D { Z = 33 }, B = true, BA = new bool[] { true }, E = E.A, EA = new E[] { E.A, E.B } });
150+
_collection.Insert(new C { Id = _id5, X = 5, Y = 44, D = new D { Z = 55 }, DBRef = new MongoDBRef("db", "c", 1) });
151+
_collection.Insert(new C { Id = _id4, X = 4, Y = 44, D = new D { Z = 44 } });
151152
}
152153

153154
[Test]
@@ -480,11 +481,11 @@ public void TestDistinctId()
480481
select c.Id).Distinct();
481482
var results = query.ToList();
482483
Assert.AreEqual(5, results.Count);
483-
Assert.IsTrue(results.Contains(id1));
484-
Assert.IsTrue(results.Contains(id2));
485-
Assert.IsTrue(results.Contains(id3));
486-
Assert.IsTrue(results.Contains(id4));
487-
Assert.IsTrue(results.Contains(id5));
484+
Assert.IsTrue(results.Contains(_id1));
485+
Assert.IsTrue(results.Contains(_id2));
486+
Assert.IsTrue(results.Contains(_id3));
487+
Assert.IsTrue(results.Contains(_id4));
488+
Assert.IsTrue(results.Contains(_id5));
488489
}
489490

490491
[Test]
@@ -1178,35 +1179,119 @@ public void TestLongCountWithSkipAndTake()
11781179
}
11791180

11801181
[Test]
1181-
[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "The Max query operator is not supported.")]
1182-
public void TestMax()
1182+
public void TestMaxDZWithProjection()
1183+
{
1184+
var result = (from c in _collection.AsQueryable<C>()
1185+
select c.D.Z).Max();
1186+
Assert.AreEqual(55, result);
1187+
}
1188+
1189+
[Test]
1190+
public void TestMaxDZWithSelector()
1191+
{
1192+
var result = (from c in _collection.AsQueryable<C>()
1193+
select c).Max(c => c.D.Z);
1194+
Assert.AreEqual(55, result);
1195+
}
1196+
1197+
[Test]
1198+
[ExpectedException(typeof(NotSupportedException), ExpectedMessage = "Max must be used with either Select or a selector argument, but not both.")]
1199+
public void TestMaxWithProjectionAndSelector()
1200+
{
1201+
var result = (from c in _collection.AsQueryable<C>()
1202+
select c.D).Max(d => d.Z);
1203+
}
1204+
1205+
[Test]
1206+
public void TestMaxXWithProjection()
11831207
{
11841208
var result = (from c in _collection.AsQueryable<C>()
1185-
select c).Max();
1209+
select c.X).Max();
1210+
Assert.AreEqual(5, result);
1211+
}
1212+
1213+
[Test]
1214+
public void TestMaxXWithSelector()
1215+
{
1216+
var result = (from c in _collection.AsQueryable<C>()
1217+
select c).Max(c => c.X);
1218+
Assert.AreEqual(5, result);
1219+
}
1220+
1221+
[Test]
1222+
public void TestMaxXYWithProjection()
1223+
{
1224+
var result = (from c in _collection.AsQueryable<C>()
1225+
select new { c.X, c.Y }).Max();
1226+
Assert.AreEqual(5, result.X);
1227+
Assert.AreEqual(44, result.Y);
11861228
}
11871229

11881230
[Test]
1189-
[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "The Max query operator is not supported.")]
1190-
public void TestMaxWithSelector()
1231+
public void TestMaxXYWithSelector()
11911232
{
11921233
var result = (from c in _collection.AsQueryable<C>()
1193-
select c).Max(c => 1.0);
1234+
select c).Max(c => new { c.X, c.Y });
1235+
Assert.AreEqual(5, result.X);
1236+
Assert.AreEqual(44, result.Y);
11941237
}
11951238

11961239
[Test]
1197-
[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "The Min query operator is not supported.")]
1198-
public void TestMin()
1240+
public void TestMinDZWithProjection()
11991241
{
12001242
var result = (from c in _collection.AsQueryable<C>()
1201-
select c).Min();
1243+
select c.D.Z).Min();
1244+
Assert.AreEqual(11, result);
12021245
}
12031246

12041247
[Test]
1205-
[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "The Min query operator is not supported.")]
1206-
public void TestMinWithSelector()
1248+
public void TestMinDZWithSelector()
12071249
{
12081250
var result = (from c in _collection.AsQueryable<C>()
1209-
select c).Min(c => 1.0);
1251+
select c).Min(c => c.D.Z);
1252+
Assert.AreEqual(11, result);
1253+
}
1254+
1255+
[Test]
1256+
[ExpectedException(typeof(NotSupportedException), ExpectedMessage = "Min must be used with either Select or a selector argument, but not both.")]
1257+
public void TestMinWithProjectionAndSelector()
1258+
{
1259+
var result = (from c in _collection.AsQueryable<C>()
1260+
select c.D).Min(d => d.Z);
1261+
}
1262+
1263+
[Test]
1264+
public void TestMinXWithProjection()
1265+
{
1266+
var result = (from c in _collection.AsQueryable<C>()
1267+
select c.X).Min();
1268+
Assert.AreEqual(1, result);
1269+
}
1270+
1271+
[Test]
1272+
public void TestMinXWithSelector()
1273+
{
1274+
var result = (from c in _collection.AsQueryable<C>()
1275+
select c).Min(c => c.X);
1276+
Assert.AreEqual(1, result);
1277+
}
1278+
1279+
[Test]
1280+
public void TestMinXYWithProjection()
1281+
{
1282+
var result = (from c in _collection.AsQueryable<C>()
1283+
select new { c.X, c.Y }).Min();
1284+
Assert.AreEqual(1, result.X);
1285+
Assert.AreEqual(11, result.Y);
1286+
}
1287+
1288+
[Test]
1289+
public void TestMinXYWithSelector()
1290+
{
1291+
var result = (from c in _collection.AsQueryable<C>()
1292+
select c).Min(c => new { c.X, c.Y });
1293+
Assert.AreEqual(1, result.X);
1294+
Assert.AreEqual(11, result.Y);
12101295
}
12111296

12121297
[Test]

0 commit comments

Comments
 (0)