[CSHARP-526] LINQ queries against AsQueryable<T> where T is an interface aren't supported Created: 13/Jul/12  Updated: 20/Mar/14  Resolved: 16/Jul/12

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

Type: Bug Priority: Major - P3
Reporter: Gabriel Figueroa Assignee: Robert Stam
Resolution: Done Votes: 0
Labels: bson
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Attachments: Text File Program.cs    

 Description   

When you want to deserialize an object using an interface using this setup. It crashes with BsonSerializationException "No serializer found for type X". The way i have mongo setup is like this.

MongoRepository<TObject>
{
public MongoRepository(string serverUrl, string databaseName, MongoCredentials credentials)

{ var mongourl = new MongoUrl(serverUrl); _mongoServer = MongoServer.Create(mongourl); _mongoCollection = _mongoDatabase.GetCollection<TObject>(typeof(TObject).Name); }

public TObject Find(Expression<Func<TObject,bool>> predicate)

{ return _mongoCollection.AsQueryable().FirstOrDefault(predicate); }

}

When TObject is an Interface type it crashes with BsonSerializationException. A fix that works pretty well on git commit 6a30fd35ea2d45b3aa8f2c7b176a24e90980f150 in the BsonSerializer.cs file line 589 in the LookupSerializer method near where the exception is thrown i put this snippet in

if(serializer == null)
{
foreach (var bsonSerializer in __serializers)
{
if(type.IsAssignableFrom(bsonSerializer.Key))

{ serializer = bsonSerializer.Value; break; }

}
}

it will allow AsQueryable to use an interface type and deserialize it properly.



 Comments   
Comment by Robert Stam [ 16/Jul/12 ]

Closing this issue, at least for now, as works as designed.

Comment by Gabriel Figueroa [ 14/Jul/12 ]

Yeah you are right i'll just have to see about writing my code so that i can get AsQueryable<> to have the concrete type before i pass it on to MongoDB. Thats probably easier to do than to come up with a general solution to work inside of the driver. In the simple case my snippet works fine but the second you need any of the advance features of the class mapper it falls apart. Thank You for your time, keep up the great work.

Comment by Robert Stam [ 14/Jul/12 ]

I don't think we can implement LINQ queries in terms of interfaces, because some of the information we need to translate a LINQ query to a MongoDB query is attached to the classes. Consider the following interface and classes:

public interface IX
{
    public int X { get; set; }
}
 
public class C : IX
{
    public ObjectId Id { get; set; }
    [BsonElement("cx")]
    public int X { get; set; }
}
 
public class D : IX
{
    public ObjectId Id { get; set; }
    [BsonElement("dx")]
    public int X { get; set; }
}

and the following LINQ query:

var query = collection.AsQueryable<IX>.Where(doc => doc.X == 1);

We can't map this LINQ query to a MongoDB query because we don't know the element name that X is mapped to unless we know the class. Depending on the actual class in use, the MongoDB query might be either one of these:

{ cx : 1 }
{ dx : 1 }

Comment by Gabriel Figueroa [ 14/Jul/12 ]

Your sample application is the exact error that i am seeing. You are correct that my little snippet could have problems if more than one class that implements that interface is registered with the driver. Your workaround would work in all other cases but for me

 var ic = (IC)collection.AsQueryable<C>().FirstOrDefault(x=>x.X == 1) 

won't work because i don't know what C is when it gets down to the repository class, since i am abstracting away the data layer. For now the snippet works for what i need it to do. I know that later on i have other classes that implement the same interface will need to be persisted so I'll see if i can come up with a way to be more type safe and pick the the serializer that fits the type that is fits the best or just write a custom serializer for my interface types. Thank You for looking into it.

Comment by Robert Stam [ 13/Jul/12 ]

Here's the line from the test program that throws an exception:

var ic = collection.AsQueryable<IC>().FirstOrDefault(x => x.X == 1);

Where IC is an interface.

The following slightly different line of code would work:

var ic = (IC)collection.AsQueryable<C>().FirstOrDefault(x => x.X == 1);

because C is not an interface.

Comment by Robert Stam [ 13/Jul/12 ]

I've edited the summary description to more accurately describe what I believe is the root issue.

Comment by Robert Stam [ 13/Jul/12 ]

Also wanted to comment that your proposed fix is probably dangerous. There may be multiple potential hits and you are arbitrarily grabbing the first one. That probably worked in your test environment, but might not be safe in general.

Comment by Robert Stam [ 13/Jul/12 ]

This is the stack trace from running the attached test program. Note that the exception is coming during translation of the LINQ query and not during actual deserialization of a real document. The LINQ translation layer needs serialization information because it needs to know the corresponding element name in the document and how the corresponding values are serialized.

Unhandled exception:
MongoDB.Bson.BsonSerializationException: No serializer found for type TestCSharp
526.IC.
   at MongoDB.Bson.Serialization.BsonSerializer.LookupSerializer(Type type) in C
:\work\rstam\mongo-csharp-driver\Bson\Serialization\BsonSerializer.cs:line 594
   at MongoDB.Driver.Linq.Utils.BsonSerializationInfoFinder.VisitParameter(Param
eterExpression node) in C:\work\rstam\mongo-csharp-driver\Driver\Linq\Utils\Bson
SerializationInfoFinder.cs:line 184
   at MongoDB.Driver.Linq.ExpressionVisitor`1.Visit(Expression node) in C:\work\
rstam\mongo-csharp-driver\Driver\Linq\Expressions\ExpressionVisitorGeneric.cs:li
ne 92
   at MongoDB.Driver.Linq.Utils.BsonSerializationInfoFinder.Visit(Expression nod
e) in C:\work\rstam\mongo-csharp-driver\Driver\Linq\Utils\BsonSerializationInfoF
inder.cs:line 81
   at MongoDB.Driver.Linq.Utils.BsonSerializationInfoFinder.VisitMember(MemberEx
pression node) in C:\work\rstam\mongo-csharp-driver\Driver\Linq\Utils\BsonSerial
izationInfoFinder.cs:line 143
   at MongoDB.Driver.Linq.ExpressionVisitor`1.Visit(Expression node) in C:\work\
rstam\mongo-csharp-driver\Driver\Linq\Expressions\ExpressionVisitorGeneric.cs:li
ne 94
   at MongoDB.Driver.Linq.Utils.BsonSerializationInfoFinder.Visit(Expression nod
e) in C:\work\rstam\mongo-csharp-driver\Driver\Linq\Utils\BsonSerializationInfoF
inder.cs:line 81
   at MongoDB.Driver.Linq.Utils.BsonSerializationInfoFinder.GetSerializationInfo
(Expression node, Dictionary`2 serializationInfoCache) in C:\work\rstam\mongo-cs
harp-driver\Driver\Linq\Utils\BsonSerializationInfoFinder.cs:line 56
   at MongoDB.Driver.Linq.Utils.BsonSerializationInfoHelper.GetSerializationInfo
(Expression node) in C:\work\rstam\mongo-csharp-driver\Driver\Linq\Utils\BsonSer
ializationInfoHelper.cs:line 48
   at MongoDB.Driver.Linq.PredicateTranslator.BuildComparisonQuery(Expression va
riableExpression, ExpressionType operatorType, ConstantExpression constantExpres
sion) in C:\work\rstam\mongo-csharp-driver\Driver\Linq\Translators\PredicateTran
slator.cs:line 346
   at MongoDB.Driver.Linq.PredicateTranslator.BuildComparisonQuery(BinaryExpress
ion binaryExpression) in C:\work\rstam\mongo-csharp-driver\Driver\Linq\Translato
rs\PredicateTranslator.cs:line 306
   at MongoDB.Driver.Linq.PredicateTranslator.BuildQuery(Expression expression)
in C:\work\rstam\mongo-csharp-driver\Driver\Linq\Translators\PredicateTranslator
.cs:line 84
   at MongoDB.Driver.Linq.SelectQuery.BuildQuery() in C:\work\rstam\mongo-csharp
-driver\Driver\Linq\Translators\SelectQuery.cs:line 124
   at MongoDB.Driver.Linq.SelectQuery.Execute() in C:\work\rstam\mongo-csharp-dr
iver\Driver\Linq\Translators\SelectQuery.cs:line 133
   at MongoDB.Driver.Linq.MongoQueryProvider.Execute(Expression expression) in C
:\work\rstam\mongo-csharp-driver\Driver\Linq\MongoQueryProvider.cs:line 155
   at MongoDB.Driver.Linq.MongoQueryProvider.Execute[TResult](Expression express
ion) in C:\work\rstam\mongo-csharp-driver\Driver\Linq\MongoQueryProvider.cs:line
 131
   at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source, Express
ion`1 predicate)
   at TestCSharp526.Program.Main(String[] _args) in c:\users\robert stam\documen
ts\visual studio 2010\Projects\TestCSharp526\Program.cs:line 38
Press Enter to continue

Comment by Robert Stam [ 13/Jul/12 ]

I've attached a small console program that I believe reproduces the issue you are describing. Take a look, and if you feel it adequately reproduces your issue then you don't need to provide any additional information. Thanks.

Comment by Robert Stam [ 13/Jul/12 ]

Can you provide the following additional information:

1. sample interface definition
2. sample class definition
3. stack trace

It would be really nice if you could provide a standalone console application that reproduces the exception.

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