4

So, I am performing a SelectMany query to iterate through the Lists inside of objects in a collection. Here is my query:

var collection = _database.GetCollection<VehicleDataUpload>(VehiclesCollection);
var aggregation = collection.AsQueryable()
    .SelectMany(v => v.VehicleEntries)
    .Where(i => Convert.ToInt32(i.PostFlashDTCs) > 0)
    .ToList();

However, each time I run this I get the following error:

A first chance exception of type 'System.InvalidOperationException' occurred in MongoDB.Driver.dll

I thought the problem had to do with the convert function, so I changed it to:

.Where(i => Convert.ToInt32("1") > 0)

And it still worked fine. My coworker said it might choke on converting a string 0, but when I hardcode in "0", it still works. For some reason, it just can't convert the class field. I have set the field to string and even set a default value for it:

public class VehicleEntry
{
    [BsonElement("PostFlashDTCs")]
    [BsonDefaultValue("0")]
    public String PostFlashDTCs { get; set; }
}

What is the reason why it shows an InvalidFormatException when I read from the object itself?

EDIT

I wrote a quick for loop to iterate through (after dropping the where step) and print if there was a string that couldn't be converted to an integer. Nothing was printed to the console:

foreach (VehicleEntry vehicle in aggregation1)
{
    int result;
    if (!Int32.TryParse(vehicle.PostFlashDTCs, out result))
    {
        Console.WriteLine("Bad value!");
    }
}

EDIT 2

I restricted my query a bit to pull from a case that only had 12 VehicleEntry elements. I put a debugger afterward (I removed the where case temporarily) and went through all twelve elements and they all had valid numeric strings for the PostFlashDTCs field, so the problem resides in the convert function, not with an invalid string.

EDIT 3

If I'm unable to use Convert.ToInt32 in a LINQ query, why would it work fine when I hardcode the string value? Is the compiler doing something weird to optimize that code? I'm not aware of such optimization, but I suppose it's possible.

2
  • Have you/can you debug to make sure the value is "0"? Commented Jan 15, 2016 at 17:26
  • @MichaelB I iterated through with a TryParse to print to the console with a bad value but it never printed. I will edit to show what I did. Commented Jan 15, 2016 at 17:30

2 Answers 2

2

The mongo driver takes the linq expression built as an expression tree and converts it in Mongo's own native querying language. The reason for the error is that it doesn't know how to convert Convert.ToInt32.

The .ToList() part forces the query to run on Mongo, after which the linq statements will run within the application and you will be able to use Convert.ToInt32 after .ToList.

You might be able to use a cast (https://jira.mongodb.org/browse/CSHARP-900) so...

.Where(i => (int)i.PostFlashDTCs > 0)

if not raise a ticket with the Mongo team.

Sign up to request clarification or add additional context in comments.

5 Comments

But why would it work when I hardcoded in Convert.ToInt32("1")?
It will work when it is hardcoded as the Linq with convert Convert.ToInt32("1") to 1 and then pass that value to Mongo which Mongo can handle. The problem is if you are asking Mongo to run Convert.ToInt32, it doesn't not know what that function is
Nice suggestion on the cast, but C# won't let you cast int like that, you need convert.
Have a look at removing .AsQueryable() and using .Find on the collection directly docs.mongodb.org/getting-started/csharp/query
I'll take a look. I was able to do this using the C# driver but I was really hoping to switch it to LINQ somehow.
2

So... I kind of got a little deep in the rabbit hole.

If you go to MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToPipelineTranslators.ExpressionToPipelineTranslator you will get to see which methods the Mongo.Driver is able to translate to a Mongo query. Even though Parse is one of them, it only parses to DateTime (MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators.ParseMethodToAggregationExpressionTranslator):

    public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
    {
        if (expression.Method.Is(DateTimeMethod.Parse))
        {
            return TranslateDateTimeParse(context, expression);
        }
        throw new ExpressionNotSupportedException(expression);
    }

My solution was to use CompareTo without parsing the string to int, but instead converting the int constant to string:

var aggregation = collection.AsQueryable()
    .SelectMany(v => v.VehicleEntries)
    .Where(i => i.PostFlashDTCs.CompareTo("0") > 0)
    .ToList();

EDIT

After way to much time debuging the Mongo.Driver, I've found that if you cast the field to BsonValue and then to int it will be translated to $toInt in the query expression:

collection.Aggregate().Match(a => ((int)(BsonValue)a.YourField == 10);
aggregate([{ "$match" : { "$expr" : { "$eq" : [{ "$toInt" : $YourField }, 10] } } }])

It will also work for:

"MongoDB.Bson.ObjectId" => "objectId",
"System.Boolean" => "bool",
"System.DateTime" => "date",
"System.Decimal" => "decimal",
"System.Double" => "double",
"System.Int32" => "int",
"System.Int64" => "long",
"System.String" => "string"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.