Skip to content

Commit 6c76b8a

Browse files
author
rstam
committed
Implemented CSHARP-481. Added support for ContainsKey in LINQ queries.
1 parent 64176ac commit 6c76b8a

File tree

3 files changed

+449
-0
lines changed

3 files changed

+449
-0
lines changed

Driver/Linq/Translators/PredicateTranslator.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515

1616
using System;
1717
using System.Collections;
18+
using System.Collections.Generic;
1819
using System.Linq;
1920
using System.Linq.Expressions;
2021
using System.Text.RegularExpressions;
2122

2223
using MongoDB.Bson;
2324
using MongoDB.Bson.IO;
2425
using MongoDB.Bson.Serialization;
26+
using MongoDB.Bson.Serialization.Options;
2527
using MongoDB.Driver.Builders;
2628
using MongoDB.Driver.Linq.Utils;
2729

@@ -375,8 +377,94 @@ private IMongoQuery BuildContainsAnyQuery(MethodCallExpression methodCallExpress
375377
return null;
376378
}
377379

380+
private IMongoQuery BuildContainsKeyQuery(MethodCallExpression methodCallExpression)
381+
{
382+
var dictionaryType = methodCallExpression.Object.Type;
383+
var implementedInterfaces = new List<Type>(dictionaryType.GetInterfaces());
384+
if (dictionaryType.IsInterface)
385+
{
386+
implementedInterfaces.Add(dictionaryType);
387+
}
388+
389+
Type dictionaryGenericInterface = null;
390+
Type dictionaryInterface = null;
391+
foreach (var implementedInterface in implementedInterfaces)
392+
{
393+
if (implementedInterface.IsGenericType)
394+
{
395+
if (implementedInterface.GetGenericTypeDefinition() == typeof(IDictionary<,>))
396+
{
397+
dictionaryGenericInterface = implementedInterface;
398+
}
399+
}
400+
else if (implementedInterface == typeof(IDictionary))
401+
{
402+
dictionaryInterface = implementedInterface;
403+
}
404+
}
405+
406+
Type keyNominalType;
407+
if (dictionaryGenericInterface != null)
408+
{
409+
keyNominalType = dictionaryGenericInterface.GetGenericArguments()[0]; // TKey
410+
}
411+
else if (dictionaryInterface != null)
412+
{
413+
keyNominalType = typeof(object);
414+
}
415+
else
416+
{
417+
return null;
418+
}
419+
420+
var arguments = methodCallExpression.Arguments.ToArray();
421+
if (arguments.Length != 1)
422+
{
423+
return null;
424+
}
425+
426+
var constantExpression = arguments[0] as ConstantExpression;
427+
if (constantExpression == null)
428+
{
429+
return null;
430+
}
431+
var key = constantExpression.Value;
432+
433+
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(methodCallExpression.Object);
434+
var dictionarySerializationOptions = (DictionarySerializationOptions)serializationInfo.SerializationOptions ?? DictionarySerializationOptions.Defaults;
435+
436+
var keyActualType = (key != null) ? key.GetType() : keyNominalType;
437+
var keySerializer = BsonSerializer.LookupSerializer(keyActualType);
438+
var keySerializationInfo = new BsonSerializationInfo(
439+
null, // elementName
440+
keySerializer,
441+
keyNominalType,
442+
dictionarySerializationOptions.KeyValuePairSerializationOptions.KeySerializationOptions);
443+
var serializedKey = _serializationInfoHelper.SerializeValue(keySerializationInfo, key);
444+
445+
switch (dictionarySerializationOptions.Representation)
446+
{
447+
case DictionaryRepresentation.ArrayOfDocuments:
448+
return Query.EQ(serializationInfo.ElementName + ".k", serializedKey);
449+
case DictionaryRepresentation.Document:
450+
return Query.Exists(serializationInfo.ElementName + "." + serializedKey.AsString, true);
451+
default:
452+
var message = string.Format(
453+
"{0} in a LINQ query is only supported for DictionaryRepresentation ArrayOfDocuments or Document, not {1}.",
454+
methodCallExpression.Method.Name, // could be Contains (for IDictionary) or ContainsKey (for IDictionary<TKey, TValue>)
455+
dictionarySerializationOptions.Representation);
456+
throw new NotSupportedException(message);
457+
}
458+
}
459+
378460
private IMongoQuery BuildContainsQuery(MethodCallExpression methodCallExpression)
379461
{
462+
// handle IDictionary Contains the same way as IDictionary<TKey, TValue> ContainsKey
463+
if (typeof(IDictionary).IsAssignableFrom(methodCallExpression.Object.Type))
464+
{
465+
return BuildContainsKeyQuery(methodCallExpression);
466+
}
467+
380468
if (methodCallExpression.Method.DeclaringType == typeof(string))
381469
{
382470
return BuildStringQuery(methodCallExpression);
@@ -611,6 +699,7 @@ private IMongoQuery BuildMethodCallQuery(MethodCallExpression methodCallExpressi
611699
case "Contains": return BuildContainsQuery(methodCallExpression);
612700
case "ContainsAll": return BuildContainsAllQuery(methodCallExpression);
613701
case "ContainsAny": return BuildContainsAnyQuery(methodCallExpression);
702+
case "ContainsKey": return BuildContainsKeyQuery(methodCallExpression);
614703
case "EndsWith": return BuildStringQuery(methodCallExpression);
615704
case "Equals": return BuildEqualsQuery(methodCallExpression);
616705
case "In": return BuildInQuery(methodCallExpression);

DriverUnitTests/DriverUnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
<Compile Include="Jira\CSharp93Tests.cs" />
173173
<Compile Include="Jira\CSharp98Tests.cs" />
174174
<Compile Include="Jira\CSharp100Tests.cs" />
175+
<Compile Include="Linq\SelectDictionaryTests.cs" />
175176
<Compile Include="Linq\SelectNullableTests.cs" />
176177
<Compile Include="Linq\SelectOfTypeHierarchicalTests.cs" />
177178
<Compile Include="Linq\SelectOfTypeTests.cs" />

0 commit comments

Comments
 (0)