[CSHARP-785] Custom/unsupported collections with their own impl of .Contains(...) causes a LINQ error Created: 20/Jul/13  Updated: 12/Aug/13  Resolved: 12/Aug/13

Status: Closed
Project: C# Driver
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: David Pfeffer Assignee: Unassigned
Resolution: Cannot Reproduce Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
duplicates CSHARP-732 ICollection<T>.Contains is not suppor... Closed

 Description   

When a .NET collection type, such as ISet, defines its own Contains method, the C# compiler will (rightly) choose this implementation over the Enumerable.Contains extension method. When the collection type is unsupported by MongoDB natively, however, Mongo's client driver for C# will throw an exception because it doesn't know how to translate the Contains method invocation into a query.

The workaround at the moment is not too bad – calling Enumerable.Contains(collection, item to check) rather than collection.Contains(item to check) translates properly by the driver. However, this is a bit hackish and is definitely not easy to read.

I think this is a situation where "if it looks like a duck and quacks like a duck, its a duck" applies. If a type implements IEnumerable<T>, defines a method called Contains, and that method takes one argument of type T, you should translate it just like you would do with the extension method or one of the supported collection types' Contains method.



 Comments   
Comment by David Pfeffer [ 20/Jul/13 ]

That's strange, because it does work for me too now that I've tried your sample code. Unfortunately it was a member of my team that generated the error originally, not me. I should be able to get him to reproduce it sometime this weekend and post back.

Comment by Craig Wilson [ 20/Jul/13 ]

I initially used ISet<T> and the query was generated appropriately (I couldn't deserialize for lack of a custom serializer). See below. Could you provide the exception you are receiving? Also, could you post your custom serializer? Your custom serializer needs to implement IBsonArraySerializer in order to provide serialization information for the items contained. If you have not implemented IBsonArraySerializer, you will receive an exception with a message like "

{0}

requires that the serializer specified for

{1}

support items by implementing

{2}

and returning a non-null result.

{3}

is the current serializer."

class Program
{
    private class Foo
    {
        public ObjectId Id { get; set; }
 
        public ISet<int> Ages { get; set; }
    }
 
 
    static void Main(string[] args)
    {
        var client = new MongoClient();
        var testDb = client.GetServer().GetDatabase("test");
        var fooCol = testDb.GetCollection<Foo>("foo");
        fooCol.Drop();
        fooCol.Save(new Foo { Ages = new HashSet<int> { 1, 2, 3 } });
 
        var fooColBson = testDb.GetCollection("foo");
        var query = from f in fooCol.AsQueryable()
                        where f.Ages.Contains(3)
                        select f;
 
        var mongoQuery = ((MongoQueryable<Foo>)query).GetMongoQuery();
        Debug.Assert(mongoQuery.ToString() == "{ \"Ages\" : 3 }");
    }
}

Comment by David Pfeffer [ 20/Jul/13 ]

Change Ages in your example to ISet<int> and use a custom serializer to load it into a custom set type, and it fails.

Comment by Craig Wilson [ 20/Jul/13 ]

#1 is covered by this issue. #2 is not. However, #2 already works correctly. Below is a sample program used to demonstrate this... Perhaps you could provide a sample of what isn't working?

class Program
{
    private class Foo
    {
        public ObjectId Id { get; set; }
 
        public HashSet<int> Ages { get; set; }
    }
 
 
    static void Main(string[] args)
    {
        var client = new MongoClient();
        var testDb = client.GetServer().GetDatabase("test");
        var fooCol = testDb.GetCollection<Foo>("foo");
        fooCol.Drop();
        fooCol.Save(new Foo { Ages = new HashSet<int> { 1, 2, 3 } });
 
        var fooColBson = testDb.GetCollection("foo");
        var fooDoc = (from f in fooCol.AsQueryable()
                        where f.Ages.Contains(3)
                        select f).Single();
    }

Comment by David Pfeffer [ 20/Jul/13 ]

I'm actually talking about #2 above, whereas the other issue (CSHARP-732) appears to focus on #1. However, if they're both included in that issue, then great!

Comment by Craig Wilson [ 20/Jul/13 ]

Hi David. In tracking this down, we already have this issue reported and it has already been corrected in master and will either be a part of a soon-to-be-released 1.8.2 or 1.9.

Comment by Craig Wilson [ 20/Jul/13 ]

Could you identify which of the below you are referring to.

// 1)
var local = new [] { 1, 2 };
var query = from c in collection.AsQueryable<Person>()
            where local.Contains(c.Ages)
            select c;
 
// { Ages : { "$in" : [1, 2 ] } }

// 2)
var query = from c in collection.AsQueryable<Person>()
            where c.Ages.Contains(1)
            select c;
 
// { Ages : 1 }

Generated at Wed Feb 07 21:37:49 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.