[CSHARP-4781] DefaultIfEmpty using wrong default value Created: 06/Sep/23  Updated: 28/Oct/23  Resolved: 25/Sep/23

Status: Closed
Project: C# Driver
Component/s: LINQ3
Affects Version/s: 2.19.0, 2.19.1, 2.19.2, 2.20.0, 2.21.0
Fix Version/s: 2.22.0

Type: Bug Priority: Major - P3
Reporter: Aleš Hübl Assignee: Robert Stam
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Backwards Compatibility: Fully Compatible
Documentation Changes: Not Needed
Documentation Changes Summary:

1. What would you like to communicate to the user about this feature?
2. Would you like the user to see examples of the syntax and/or executable code and its output?
3. Which versions of the driver/connector does this apply to?


 Description   

Summary

When a class without parameterless constructor is used in Unwind you get a runtime error: 

System.MissingMethodException: Cannot dynamically create an instance of type 'PD.Waste.Dustbins.Planning.API.Services.DatesCollectionPlanProviderService+UnwoundDatesCollectionPlan'. Reason: No parameterless constructor defined.
at System.RuntimeType.ActivatorCache..ctor(RuntimeType rt)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators.DefaultIfEmptyMethodToAggregationExpressionTranslator.Translate(TranslationContext context, MethodCallExpression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodCallExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, MethodCallExpression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.ExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, Expression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.ExpressionToAggregationExpressionTranslator.TranslateEnumerable(TranslationContext context, Expression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators.FirstOrLastMethodToAggregationExpressionTranslator.Translate(TranslationContext context, MethodCallExpression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodCallExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, MethodCallExpression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.ExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, Expression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MemberExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, MemberExpression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.ExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, Expression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MemberInitExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, Expression expression, NewExpression newExpression, IReadOnlyList`1 bindings)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MemberInitExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, MemberInitExpression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.ExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, Expression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(TranslationContext context, LambdaExpression lambdaExpression, IBsonSerializer parameterSerializer, Boolean asRoot)
at MongoDB.Driver.Linq.Linq3Implementation.GroupingWithOutputExpressionStageDefinition`3.RenderProjectStage(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry, IBsonSerializer`1& outputSerializer)
at MongoDB.Driver.Linq.Linq3Implementation.GroupingWithOutputExpressionStageDefinition`3.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
at MongoDB.Driver.Linq.Linq3Implementation.GroupWithOutputExpressionStageDefinition`3.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
at MongoDB.Driver.AppendedStagePipelineDefinition`3.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
at MongoDB.Driver.MongoCollectionImpl`1.AggregateAsync[TResult](IClientSessionHandle session, PipelineDefinition`2 pipeline, AggregateOptions options, CancellationToken cancellationToken)
at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)
at MongoDB.Driver.IAsyncCursorSourceExtensions.ToListAsync[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken)

Please provide the version of the driver. If applicable, please provide the MongoDB server version and topology (standalone, replica set, or sharded cluster).

 

This started happening after upgrading the MongoDb csharp driver from 2.18.0 to 2.21.0.

How to Reproduce

Run a query using the Unwind method with class that only has constructor with parameters.

Additional Background

The problem can be avoided by manually setting the default LinqProvider to Linq2.



 Comments   
Comment by Aleš Hübl [ 26/Sep/23 ]

Thank you Robert, we will try the fixed version soon!

Comment by Githook User [ 25/Sep/23 ]

Author:

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

Message: CSHARP-4781: DefaultIfEmpty using wrong default value.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/88a63ead0e39660b95f7edeb1ae1b731a1047a56

Comment by Robert Stam [ 09/Sep/23 ]

I've been reading up on DefaultIfEmpty and I now believe this is a bug.

DefaultIfEmpty should create the default value by using `default(TSource)` instead of by `new TSource()`.

If TSource is a reference type the default value will be null.

 

Comment by Aleš Hübl [ 08/Sep/23 ]

Hello, 

 

 I believe that this piece of code was causing the problem:

// code placeholder
var results = await _collection
    .Aggregate()
    .Match(Builders<DatesCollectionPlan>.Filter.And(filters))
    .Unwind<DatesCollectionPlan, UnwoundDatesCollectionPlan>(d => d.Dates)
    .Match(d => d.Dates >= fromInclusive && d.Dates <= toInclusive)
    .Group(
        d => d.Id,
        g =>
            new DatesCollectionPlanInIntervalResult
            {
                DistrictVehicleCrewId = g.DefaultIfEmpty()
                    .First()!
                    .DistrictVehicleCrewId,
                CollectionPlaceId = g.DefaultIfEmpty().First()!.CollectionPlaceId,
                DustbinCount = g.DefaultIfEmpty().First()!.DustbinCount,
                DustbinCollectionPlanStateId = g.DefaultIfEmpty()
                    .First()!
                    .DustbinCollectionPlanStateId,
                TimeZoneInfoId = g.DefaultIfEmpty().First()!.TimeZoneInfoId,
                DustbinCollectionPlanIndex = g.DefaultIfEmpty()
                    .First()!
                    .DustbinCollectionPlanIndex,
                Dates = g.Select(x => x.Dates).ToList()
            }
    )
    .ToListAsync(cancellationToken); 

 

As for the workarounds that you suggested. Adding an empty constructor would cause warnings because there are non nullable fields in the class and we have nullability checks turned on. This could be mitigated by some pragma of course. 

As for the DefaultIfEmpty, I don't think that it is that what causes the problem as I call it on the grouping, not on the UnwoundDatesCollectionPlan itself, but maybe I'm mistaken.

 

Anyway, thanks for your help, I appreciate it a lot.

Comment by Robert Stam [ 07/Sep/23 ]

This might not be a bug, but until I see your sample code to reproduce it I won't know for sure.

The reason I say that it might not be a bug is that I see `DefaultIfEmpty` on your call stack, and `DefaultIfEmpty` has to instantiate an instance of the class to use as the default value if the sequence is empty, and it does this by calling the no-argument constructor.

You can work around this by either:

  1. adding a no-argument constructor to your class
  2. use the other overload of `DefaultIfEmpty` that allows you to provide an explicit default value
Comment by Robert Stam [ 07/Sep/23 ]

Thank you for reporting this issue.

I can try to reverse engineer how this error occurs, but if you could provide some sample code reproducing the issue that would speed things up.

Comment by PM Bot [ 06/Sep/23 ]

Hi ales.hubl@gmail.com, thank you for reporting this issue! The team will look into it and get back to you soon.

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