[CSHARP-2302] c# driver throws on aggregate function when the source sequence is empty Created: 19/Jun/18  Updated: 27/Oct/23  Resolved: 26/Nov/18

Status: Closed
Project: C# Driver
Component/s: Linq, Read Operations
Affects Version/s: 2.6.1
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Itzhak Kagan Assignee: Robert Stam
Resolution: Works as Designed Votes: 2
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

c# driver 2.6.1,
server version 3.4.10
windows 7 sp1 64 bit



 Description   

I have a class:

class DynamicForm
{
     public long? FolderId { get; set; } 
     public long PatientSystemId { get; set; }
 
     public DateTime ModifiedDate { get; set; }
}

 

I have a function that gets the PatientSystemId as parameter GetDynamicFormsAsync(long patientSystemId)

I run the following query:

var query = db.GetCollection<DynamicForm>("dforms").AsQueryable()
   .Where(a => a.PatientSystemId == patientSystemId && a.FolderId == null)
   .MaxAsync(a => a.ModifiedDate);
var qResult = await query;

When there are documents in the database that meet the filter criteria I do get the Max date as desired.

However, if there is no document meeting the query criteria then I get a run-time exception:

System.InvalidOperationException
 HResult=0x80131509
 Message=The source sequence is empty.
 Source=MongoDB.Driver
 StackTrace:
 at MongoDB.Driver.AsyncCursorHelper.<SingleAsync>d__3`1.MoveNext()
 at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
 at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
 at ..... my code

When patientSystemId parameter equals 51, the following query is generated:

{ 
    "aggregate" : "dforms", 
    "pipeline" : [
        { "$match" : { "patientSystemId" : NumberLong(51), "folderId" : null } }, 
        { "$group" : { "id" : 1, "_result" : { "$max" : "$modifiedDate" } } }
    ],
    "cursor" : { }
}

When running this query in mongo shell, no exception occurs.

Can you please address this bug / issue.
Just say that you have read it please.

Thanks,
Itzhak



 Comments   
Comment by Robert Stam [ 27/Nov/18 ]

The Max function in LINQ is defined (by Microsoft) to throw an InvalidOperationException when the sequence is empty. To prove this to yourself try the following code:

var list = new List<int>();
var max = list.Max(); // throws InvalidOperationException

The same is true of other methods like Average:

var list = new List<int>();
var max = list.Average(); // throws InvalidOperationException

It is correct that you should only use the Max function when you know the sequence is not empty. That is not a limitation of the MongoDB C# driver, it's just the way Max and Average are defined.

Comment by Ilya Sh [ 27/Nov/18 ]

FOA Generally while a query works in the shell it is expected to work similarly in any driver, eventually.
Secondly, this case applies not only to Max and Min functions where you can just use OrderBy, as generously suggested, but also (for example) to average function. what would you suggest then?
IMHO with answers like this you should just drop the development of the C# driver Linq support, and instruct your clients' devs to use native aggregation only.
Now the fact that this behavior is by definition, seems very awkward to say the least, while it works differently in the shell. Maybe you should consider changing definitions like the one in discussion?!
Thanks.

 

Comment by Itzhak Kagan [ 27/Nov/18 ]

So actually what you are saying is to never use the linq Max function since there is always a chance to have no match.
If that is the case then I suggest to remove the support for the Max function (and all other alike functions) and have documented.

Comment by Robert Stam [ 26/Nov/18 ]

The LINQ Max method by definition is supposed to throw an InvalidOperationException if there are no values.

So the following query MUST throw an InvalidOperationException if no documents match:

DateTime maxModifiedDate = collection.AsQueryable()
    .Where(a => a.PatientSystemId == patientSystemId && a.FolderId == null)
    .Max(x => x.ModifiedDate);

I can offer two alternative queries that will return a nullable DateTime if there are no matching documents.

The first alternative is to sort by descending ModifiedDate and take the first one, if any (it will be the max):

var result = collection.AsQueryable()
    .Where(a => a.PatientSystemId == patientSystemId && a.FolderId == null)
    .Select(x => new { ModifiedDate = x.ModifiedDate })
    .OrderByDescending(x => x.ModifiedDate)
    .FirstOrDefault();
DateTime? maxModifiedDate = result == null ? (DateTime?)null : result.ModifiedDate;

This will perform well as long as the number of matching documents is small enough that sorting them is not time consuming.

The second alternative is to use the aggregation pipeline directly instead of LINQ:

var pipeline = new EmptyPipelineDefinition<DynamicForm>()
    .Match(a => a.PatientSystemId == patientSystemId && a.FolderId == null)
    .Group("{ _id : 1, maxModifiedDate : { $max : '$ModifiedDate' } }");
 
var result = collection.Aggregate(pipeline).FirstOrDefault();
DateTime? maxModifiedDate = result == null ? (DateTime?)null : result["maxModifiedDate"].ToUniversalTime();

 

Comment by Axente Adrian [ 21/Nov/18 ]

Is there any possibility to implement DefaultIfEmpty extension method in the Mongo Linq provider?

This will solve the problem.

 

Best Regards

Axy

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