Skip to content

Commit 3b46a19

Browse files
author
rstam
committed
Partial implementation of CSHARP-433. Implemented OfType query operator. Still need to implement "is" operator and comparison of types using "==".
1 parent 11427b5 commit 3b46a19

File tree

8 files changed

+579
-12
lines changed

8 files changed

+579
-12
lines changed

Driver/Driver.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@
183183
<Compile Include="Internal\MongoUpdateMessage.cs" />
184184
<Compile Include="Internal\ReplicaSetConnector.cs" />
185185
<Compile Include="Linq\Expressions\ExpressionFormatter.cs" />
186+
<Compile Include="Linq\Expressions\ExpressionParameterFinder.cs" />
186187
<Compile Include="Linq\Expressions\ExpressionParameterReplacer.cs" />
187188
<Compile Include="Linq\LinqToMongo.cs" />
188189
<Compile Include="Linq\Translators\DeserializationProjector.cs" />
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/* Copyright 2010-2012 10gen 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 System.Linq.Expressions;
20+
using System.Text;
21+
22+
namespace MongoDB.Driver.Linq
23+
{
24+
/// <summary>
25+
/// A class that finds the first parameter in an expression.
26+
/// </summary>
27+
public class ExpressionParameterFinder : ExpressionVisitor
28+
{
29+
// private fields
30+
private ParameterExpression _parameter;
31+
32+
// constructors
33+
/// <summary>
34+
/// Initializes a new instance of the ExpressionParameterFinder class.
35+
/// </summary>
36+
public ExpressionParameterFinder()
37+
{
38+
}
39+
40+
// public static methods
41+
/// <summary>
42+
/// Finds the first parameter in an expression.
43+
/// </summary>
44+
/// <param name="node">The expression containing the parameter that should be found.</param>
45+
/// <returns>The first parameter found in the expression (or null if none was found).</returns>
46+
public static ParameterExpression FindParameter(Expression node)
47+
{
48+
var finder = new ExpressionParameterFinder();
49+
finder.Visit(node);
50+
return finder._parameter;
51+
}
52+
53+
// protected methods
54+
/// <summary>
55+
/// Visits an Expression.
56+
/// </summary>
57+
/// <param name="node">The Expression.</param>
58+
/// <returns>The Expression (posibly modified).</returns>
59+
protected override Expression Visit(Expression node)
60+
{
61+
if (_parameter != null)
62+
{
63+
return node; // exit faster if we've already found the parameter
64+
}
65+
return base.Visit(node);
66+
}
67+
68+
/// <summary>
69+
/// Remembers this parameter if it is the first parameter found.
70+
/// </summary>
71+
/// <param name="node">The ParameterExpression.</param>
72+
/// <returns>The ParameterExpression.</returns>
73+
protected override Expression VisitParameter(ParameterExpression node)
74+
{
75+
if (_parameter == null)
76+
{
77+
_parameter = node;
78+
}
79+
return node;
80+
}
81+
}
82+
}

Driver/Linq/Translators/SelectQuery.cs

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class SelectQuery : TranslatedQuery
3737
{
3838
// private fields
3939
private LambdaExpression _where;
40+
private Type _ofType;
4041
private List<OrderByClause> _orderBy;
4142
private LambdaExpression _projection;
4243
private Expression _skip;
@@ -56,6 +57,14 @@ public SelectQuery(MongoCollection collection, Type documentType)
5657
}
5758

5859
// public properties
60+
/// <summary>
61+
/// Gets the final result type if an OfType query operator was used (otherwise null).
62+
/// </summary>
63+
public Type OfType
64+
{
65+
get { return _ofType; }
66+
}
67+
5968
/// <summary>
6069
/// Gets a list of Expressions that defines the sort order (or null if not specified).
6170
/// </summary>
@@ -157,6 +166,21 @@ public override object Execute()
157166
cursor.SetLimit(ToInt32(_take));
158167
}
159168

169+
if (_ofType != null)
170+
{
171+
if (_projection == null)
172+
{
173+
var paramExpression = Expression.Parameter(DocumentType, "x");
174+
var convertExpression = Expression.Convert(paramExpression, _ofType);
175+
_projection = Expression.Lambda(convertExpression, paramExpression);
176+
}
177+
else
178+
{
179+
// TODO: handle projection after OfType
180+
throw new NotSupportedException();
181+
}
182+
}
183+
160184
IEnumerable enumerable;
161185
if (_projection == null)
162186
{
@@ -208,6 +232,7 @@ public void Translate(Expression expression)
208232
}
209233

210234
var message = string.Format("Don't know how to translate expression: {0}.", ExpressionFormatter.ToString(expression));
235+
throw new NotSupportedException(message);
211236
}
212237

213238
// private methods
@@ -1239,10 +1264,39 @@ private void CombinePredicateWithWhereClause(MethodCallExpression methodCallExpr
12391264
return;
12401265
}
12411266

1267+
if (_where.Parameters.Count != 1)
1268+
{
1269+
throw new MongoInternalException("Where lambda expression should have one parameter.");
1270+
}
12421271
var whereBody = _where.Body;
1243-
var predicateBody = ExpressionParameterReplacer.ReplaceParameter(predicate.Body, predicate.Parameters[0], _where.Parameters[0]);
1272+
var whereParameter = _where.Parameters[0];
1273+
1274+
if (predicate.Parameters.Count != 1)
1275+
{
1276+
throw new MongoInternalException("Predicate lambda expression should have one parameter.");
1277+
}
1278+
var predicateBody = predicate.Body;
1279+
var predicateParameter = predicate.Parameters[0];
1280+
1281+
// when using OfType the parameter types might not match (but they do have to be compatible)
1282+
ParameterExpression parameter;
1283+
if (predicateParameter.Type.IsAssignableFrom(whereParameter.Type))
1284+
{
1285+
predicateBody = ExpressionParameterReplacer.ReplaceParameter(predicateBody, predicateParameter, whereParameter);
1286+
parameter = whereParameter;
1287+
}
1288+
else if (whereParameter.Type.IsAssignableFrom(predicateParameter.Type))
1289+
{
1290+
whereBody = ExpressionParameterReplacer.ReplaceParameter(whereBody, whereParameter, predicateParameter);
1291+
parameter = predicateParameter;
1292+
}
1293+
else
1294+
{
1295+
throw new NotSupportedException("Can't combine existing where clause with new predicate because parameter types are incompatible.");
1296+
}
1297+
12441298
var combinedBody = Expression.AndAlso(whereBody, predicateBody);
1245-
_where = Expression.Lambda(combinedBody, _where.Parameters.ToArray());
1299+
_where = Expression.Lambda(combinedBody, parameter);
12461300
}
12471301
}
12481302

@@ -1278,7 +1332,9 @@ private object ExecuteDistinct(IMongoQuery query)
12781332

12791333
private BsonSerializationInfo GetSerializationInfo(Expression expression)
12801334
{
1281-
var documentSerializer = BsonSerializer.LookupSerializer(DocumentType);
1335+
// when using OfType the documentType used by the parameter might be a subclass of the DocumentType from the collection
1336+
var parameterExpression = ExpressionParameterFinder.FindParameter(expression);
1337+
var documentSerializer = BsonSerializer.LookupSerializer(parameterExpression.Type);
12821338
return GetSerializationInfo(documentSerializer, expression);
12831339
}
12841340

@@ -1755,6 +1811,9 @@ private void TranslateMethodCall(MethodCallExpression methodCallExpression)
17551811
case "Min":
17561812
TranslateMaxMin(methodCallExpression);
17571813
break;
1814+
case "OfType":
1815+
TranslateOfType(methodCallExpression);
1816+
break;
17581817
case "OrderBy":
17591818
case "OrderByDescending":
17601819
TranslateOrderBy(methodCallExpression);
@@ -1781,6 +1840,63 @@ private void TranslateMethodCall(MethodCallExpression methodCallExpression)
17811840
}
17821841
}
17831842

1843+
private void TranslateOfType(MethodCallExpression methodCallExpression)
1844+
{
1845+
var method = methodCallExpression.Method;
1846+
if (method.DeclaringType != typeof(Queryable))
1847+
{
1848+
var message = string.Format("OfType method of class {0} is not supported.", BsonUtils.GetFriendlyTypeName(method.DeclaringType));
1849+
throw new NotSupportedException(message);
1850+
}
1851+
if (!method.IsStatic)
1852+
{
1853+
throw new NotSupportedException("Expected OfType to be a static method.");
1854+
}
1855+
if (!method.IsGenericMethod)
1856+
{
1857+
throw new NotSupportedException("Expected OfType to be a generic method.");
1858+
}
1859+
var actualType = method.GetGenericArguments()[0];
1860+
1861+
var args = methodCallExpression.Arguments.ToArray();
1862+
if (args.Length != 1)
1863+
{
1864+
throw new NotSupportedException("Expected OfType method to have a single argument.");
1865+
}
1866+
var sourceExpression = args[0];
1867+
if (!sourceExpression.Type.IsGenericType)
1868+
{
1869+
throw new NotSupportedException("Expected source argument to OfType to be a generic type.");
1870+
}
1871+
var nominalType = sourceExpression.Type.GetGenericArguments()[0];
1872+
1873+
if (nominalType == actualType)
1874+
{
1875+
return; // nothing to do
1876+
}
1877+
1878+
if (_projection != null)
1879+
{
1880+
throw new NotSupportedException("OfType after a projection is not supported.");
1881+
}
1882+
1883+
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
1884+
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
1885+
if (discriminator.IsBsonArray)
1886+
{
1887+
discriminator = discriminator.AsBsonArray[discriminator.AsBsonArray.Count - 1];
1888+
}
1889+
1890+
var injectMethodInfo = typeof(LinqToMongo).GetMethod("Inject");
1891+
var query = Query.EQ("_t", discriminator);
1892+
var body = Expression.Call(injectMethodInfo, Expression.Constant(query));
1893+
var parameter = Expression.Parameter(nominalType, "x");
1894+
var predicate = Expression.Lambda(body, parameter);
1895+
CombinePredicateWithWhereClause(methodCallExpression, predicate);
1896+
1897+
_ofType = actualType;
1898+
}
1899+
17841900
private void TranslateOrderBy(MethodCallExpression methodCallExpression)
17851901
{
17861902
if (methodCallExpression.Arguments.Count != 2)

DriverUnitTests/DriverUnitTests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@
162162
<Compile Include="Jira\CSharp93Tests.cs" />
163163
<Compile Include="Jira\CSharp98Tests.cs" />
164164
<Compile Include="Jira\CSharp100Tests.cs" />
165+
<Compile Include="Linq\SelectOfTypeHierarchicalTests.cs" />
166+
<Compile Include="Linq\SelectOfTypeTests.cs" />
165167
<Compile Include="Linq\SelectQueryTests.cs" />
166168
<Compile Include="Linq\MongoQueryableTests.cs" />
167169
<Compile Include="Linq\MongoQueryProviderTests.cs" />

DriverUnitTests/GridFS/MongoGridFSStreamTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@ public void TestUpdateMD5()
282282
{
283283
var bytes = new byte[] { 1, 2, 3, 4 };
284284
stream.Write(bytes, 0, 4);
285+
#pragma warning disable 618 // about obsolete BsonBinarySubType.OldBinary
285286
stream.UpdateMD5 = false;
287+
#pragma warning restore
286288
}
287289

288290
fileInfo = _gridFS.FindOne("test");

0 commit comments

Comments
 (0)