[CSHARP-1771] Support IIF method (i.e. ternary operator) in LINQ Created: 25/Sep/16  Updated: 08/Dec/21  Resolved: 25/Oct/21

Status: Closed
Project: C# Driver
Component/s: Linq, LINQ3
Affects Version/s: 2.3
Fix Version/s: 2.14.0

Type: New Feature Priority: Major - P3
Reporter: Omri Cohen Assignee: Robert Stam
Resolution: Done Votes: 11
Labels: linq, linq,query, rp-track
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

win 10, .net 4.6.1


Issue Links:
Depends
is depended on by CSHARP-3989 oDATA $select support with LINQ3 Closed
Duplicate
is duplicated by CSHARP-1423 System.ArgumentException in BsonMembe... Closed
is duplicated by CSHARP-2120 Unable to filter nested document with... Closed
is duplicated by CSHARP-3235 FormatException when using AutoMapper... Closed
is duplicated by CSHARP-3614 LINQ translation sometimes uses wrong... Closed
is duplicated by CSHARP-1467 Support of Inline Conditional-to-Filt... Closed
Case:

 Description   

I'm trying to use the new driver to support an OData webapi service.
I have "tenants" collection, a "tenant" document includes "applications" collection.
This query fails:
http://dev.api.axonize.com/odata/tenants?$expand=applications
With this stack trace:

{ "error":{ "code":"","message":"An error has occurred.","innererror":{ "message":"The expression tree is not supported: IIF(({document} == null), null, {document}{applications})","type":"System.NotSupportedException","stacktrace":" at MongoDB.Driver.Linq.Processors.EmbeddedPipeline.EmbeddedPipelineBinder.BindNonMethodCall(Expression node)\r\n at MongoDB.Driver.Linq.Processors.PipelineBinderBase`1.Bind(Expression node)\r\n at MongoDB.Driver.Linq.Processors.PipelineBinderBase`1.BindPipeline(Expression node)\r\n at MongoDB.Driver.Linq.Processors.PipelineBinderBase`1.BindMethodCall(MethodCallExpression node)\r\n at MongoDB.Driver.Linq.Processors.PipelineBinderBase`1.Bind(Expression node)\r\n at MongoDB.Driver.Linq.Processors.EmbeddedPipeline.EmbeddedPipelineBinder.Bind(Expression node, IBindingContext parent)\r\n at MongoDB.Driver.Linq.Processors.SerializationBinder.Visit(Expression node)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitConditional(ConditionalExpression node)\r\n at MongoDB.Driver.Linq.Processors.SerializationBinder.Visit(Expression node)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)\r\n at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)\r\n at MongoDB.Driver.Linq.Processors.SerializationBinder.Visit(Expression node)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)\r\n at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)\r\n at MongoDB.Driver.Linq.Processors.SerializationBinder.Visit(Expression node)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)\r\n at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)\r\n at MongoDB.Driver.Linq.Processors.SerializationBinder.Visit(Expression node)\r\n at MongoDB.Driver.Linq.Processors.Pipeline.MethodCallBinders.SelectBinder.Bind(PipelineExpression pipeline, PipelineBindingContext bindingContext, MethodCallExpression node, IEnumerable`1 arguments)\r\n at MongoDB.Driver.Linq.Processors.MethodInfoMethodCallBinder`1.Bind(PipelineExpression pipeline, TBindingContext bindingContext, MethodCallExpression node, IEnumerable`1 arguments)\r\n at MongoDB.Driver.Linq.Processors.PipelineBinderBase`1.BindMethodCall(MethodCallExpression node)\r\n at MongoDB.Driver.Linq.Processors.PipelineBinderBase`1.Bind(Expression node)\r\n at MongoDB.Driver.Linq.Processors.Pipeline.PipelineBinder.Bind(Expression node, IBsonSerializerRegistry serializerRegistry)\r\n at MongoDB.Driver.Linq.MongoQueryProviderImpl`1.Prepare(Expression expression)\r\n at MongoDB.Driver.Linq.MongoQueryProviderImpl`1.Translate(Expression expression)\r\n at MongoDB.Driver.Linq.MongoQueryProviderImpl`1.Execute(Expression expression)\r\n at MongoDB.Driver.Linq.MongoQueryableImpl`2.GetEnumerator()\r\n at System.Web.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, IEdmTypeReference feedType, ODataWriter writer, ODataSerializerContext writeContext)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Owin.HttpMessageHandlerAdapter.d__13.MoveNext()" } } }



 Comments   
Comment by Robert Stam [ 25/Oct/21 ]

This issue has been fixed in the new LINQ provider (known as LINQ3) which will be included in the upcoming 2.14 release.

Configure your MongoClientSettings to use LinqProvider.V3 if you want to use this functionality.

To configure a client to use the LINQ3 provider use code like the following

var connectionString = "mongodb://localhost";
var clientSettings = MongoClientSettings.FromConnectionString(connectionString);
clientSettings.LinqProvider = LinqProvider.V3;
var client = new MongoClient(clientSettings);

Comment by Githook User [ 25/Oct/21 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-1771: Verify that the ternary operator works as expected in LINQ3.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/18fd1c7b12cc83217831d5c5c5c52463ae191ef4

Comment by Jean-Claude ADIBA [ 30/Sep/21 ]

I have been waiting for this now for over a month 

Comment by Adam Jodlowski [ 30/Sep/21 ]

Hello 

Can i ask you if you have any rough estimate as to when this new LINQ implementation will be finished and released?

I'm experiencing the problem with IIF and i need to create some workaround for that. It would be nice to know for how much long i will need to support this workaround.

Thanks.

Comment by Dimitri Kroo [ 08/Jun/21 ]

Hi,

mega news! Thank you!

"Upcoming beta" means it is not in beta1 yet?

Comment by James Kovacs [ 07/Jun/21 ]

Hi, jiri.vavra@datapac.sk,

We are currently completing a code review of the new LINQ implementation before merging it into the main repository. Our goal is to ship it as an opt-in replacement for our current LINQ provider in an upcoming beta release of 2.13.0.

Sincerely,
James

Comment by Jiri Vavra [ 07/Jun/21 ]

Hello 

Can i ask you if you have any rough estimate as to when this new LINQ implementation will be finished and released?

I'm experiencing the problem with IIF and i need to create some workaround for that. It would be nice to know for how much long i will need to support this workaround.

Thanks.

 

Comment by Dmitry Lukyanov (Inactive) [ 15/Apr/21 ]

Hello dimitri.kroo@baramundi.com , we're actively working on a new LINQ implementation, and consider adding this case there. We will let here know when it will be ready.

Comment by Dimitri Kroo [ 15/Apr/21 ]

Hi!

Any Update on this?

Comment by Robert Stam [ 10/Dec/20 ]

I don't think it's possible to translate IIF to a normal MongoDB query/filter. There simply is no available query operator that could behave as required.

It would be possible however to translate it to a query/filter using the `$expr` escape mechanism to use the full expression language in a normal query.

Comment by James Kovacs [ 10/Dec/20 ]

Note that ExpressionType.Conditional (aka the C# ternary operator aka IIF) works in the select clause but not the where clause:

Works:

            var query = from doc in coll.AsQueryable()
                        select new { result = doc["a"] == 1 ? 42 : 144};

Fails with NotImpementedException:

            var query = from doc in coll.AsQueryable()
                        where doc["a"] == null ? true : doc["a"] == 1
                        select new { result = doc["a"] == 1 ? 42 : 144};

Comment by Robert Stam [ 05/Oct/20 ]

Thank you for reporting this. We are working on a new implementation of our LINQ translator and will make sure IIF works properly in the new implementation.

Comment by Nicholas Burnett [ 07/Jan/20 ]

I was running into a similar problem when specifying an OData filter like this:

$filter=isof('DerivedType')

Here is the exception this caused:

'Unsupported filter: IIF(({document} Is DerivedType), True, False).'

Setting HandleNullPropagation to False does not solve this problem.

So adding my vote for IFF being properly supported.

However, I was able to work around the issue like this:

Create a filter binder that unwraps the 'is' expression from the 'IFF' expression.

public class ApODataFilterBinder : Microsoft.AspNet.OData.Query.Expressions.FilterBinder
{
     public ApODataFilterBinder(IServiceProvider requestContainer) : base(requestContainer) { }       
 
     public override Expression BindSingleValueFunctionCallNode(SingleValueFunctionCallNode node)
     {
        var expression = base.BindSingleValueFunctionCallNode(node);            
        return node.Name == "isof" ? ((ConditionalExpression) expression).Test : expression;
     }
}

And then configuring this FilterBinder in IoC like this:

            app.UseMvc(c =>
            {
                c.EnableDependencyInjection(config =>
                {
                    config.AddService(Microsoft.OData.ServiceLifetime.Transient,   typeof(FilterBinder), typeof(ApODataFilterBinder));
                });
            });

 

Comment by Luke Cooper [ 06/Apr/19 ]

My case is that I have an optional property that is a class. I want it to deserialize to null if it doesn't exist. So, the query ends up looking like 
Property = p.Property == null ? null : new Property { ...members }
It works if the Property in the actual document is null, but does not work if the Property doesn't exist in the document at all (though I expect it would).
Example:
Works:

{ Name:"something", SomeInt: 2, Property: null }

Doesn't work:

{ Name: "something", SomeInt: 2 }
Comment by Ross Buggins [ 30/Oct/18 ]

Spot on - _settings.HandleNullPropagation = HandleNullPropagationOption.False has done the trick for me too - thanks for letting us know.

Ross

Comment by Terence Hancock [ 03/Jul/18 ]

By the way, I have found a workaround to this in WebApi OData.
I was calling this  EntityName.Where(i => i.Description.Contains("substring"))
And was getting error in WebApi OData

IIF((({document}{Description} == null) OrElse False), null, Convert({document}{Description}.Contains("SUBSTRING"))) is not supported.

I simply turned off NullPropagation in the OdataQuerySettings like so:

_settings.HandleNullPropagation = HandleNullPropagationOption.False;

And now the .Contains works great!

IIF is apparently a null handling part of the expression and I would think it SHOULD be handled by the IMongoQueryable implementation. Although I would admit my knowledge of expressions and linq is limited, so I could be wrong there.

I have also found that setting the _settings.EnsureStableOrdering = false; is helpful with using OrderBy

Comment by Teun Kooijman [ 10/Jun/18 ]

Hi Omri,

 

Do you perhaps have the custom .dll out on GitHub somewhere, or perhaps on a NuGet repository I could access? Would love to see what your version is capable of!

 

Kind Regards,

Teun

Comment by Omri Cohen [ 10/Jun/18 ]

My fixes have updated to much more than described on my last comment.
At the moment I have my own version of MongoDB c# driver which I maintain side by side with the official MongoDB c# driver.
I wish MongoDB will add the IFF support so I would go back to the official nuget packages.

If any of MongoDB's developers would like me to write my changes and discuss how to merge it in, I would love to help.

Cheers,
Omri.

Comment by Teun Kooijman [ 08/Jun/18 ]

@Omri Cohen's fix seems easy enough, but I'd hate to use a custom .dll for something so trivial. Is there any chance this is going to be fixed by the MongoDb C# Driver team themselves anytime soon?

Comment by Omri Cohen [ 28/Dec/17 ]

I forked your github repo and added few lines to the start of the Translate method and now everything works
I'm sure it can be written better but it's a start and can help to all of the OData with MongoDB users.
private FilterDefinition<BsonDocument> Translate(Expression node)
{
if (node is BinaryExpression binary)
{
if (binary.Left is ConditionalExpression conditional && conditional.IfFalse is UnaryExpression unary)

{ node = unary.Operand; }

if (binary.Left is UnaryExpression leftUnary)

{ node = leftUnary; }

}
if (node is ConditionalExpression ce && ce.IfFalse is UnaryExpression ue)

{ node = ue.Operand; }

FilterDefinition<BsonDocument> filter = null;

switch (node.NodeType)
.
.
.
}

@Robert Stam, any chance you'll fix this issue for the near future releases?

Thanks and Merry Xmas,
Omri.

Comment by Omri Cohen [ 21/Dec/17 ]

Any news on this ticket?

Comment by Alan Clark [ 24/Dec/16 ]

Another vote for this - OData filters contains, isof, startswith (and presumably others, haven't tried) can't be run against a mongo IQueryable because they require IIF support.

Comment by Robert Stam [ 04/Oct/16 ]

The driver already supports LINQ.

This ticket is specifically about the IIF method not being supported by the .NET driver's LINQ implementation.

Comment by Omri Cohen [ 04/Oct/16 ]

You don't have to implement OData, just LINQ

Comment by Ivan Artemov [ 03/Oct/16 ]

I'm against! I think there is no need to clutter up the C# driver with OData implementation.
May be in the future as additional library

Comment by Omri Cohen [ 30/Sep/16 ]

Thanks Craig,

OData support can benefit mongo with new customers that wants the power of OData with the power of document db as mongo.
At the moment I'm using the old driver in order to execute queries with LINQ.
I wish to abandon the old driver and work only with the new one.

Comment by Craig Wilson [ 28/Sep/16 ]

Thanks Omri,

It appears as though we don't support this particular expression (IIF). I'll mark this as a new Feature and we'll see about implementing it.

Craig

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