[CSHARP-2130] Deserialization of polymorphic types throwing exception Created: 19/Dec/17  Updated: 27/Oct/23  Resolved: 25/Feb/19

Status: Closed
Project: C# Driver
Component/s: Serialization
Affects Version/s: 2.4.4
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: kiranj email Assignee: Wan Bachtiar
Resolution: Works as Designed Votes: 0
Labels: core
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Windows 10


Issue Links:
Duplicate

 Description   

Am trying to deserialize polymorphic classes. The default deserialization works when document is created in the same function, but throws an exception when trying to deserialize else where.

Here are SIX steps to reproduce:
1) Create a blank asp.net core 2 mvc webapi project.
2 Add necessary nuget's for Mongo Driver

3) In start up, add this line: BsonSerializer.RegisterDiscriminatorConvention(typeof(Base), new ScalarDiscriminatorConvention("_t"));

Default template contains values controller:

4) Replace the file with below code:

##################################################################################
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Driver;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.IdGenerators;
using MongoDB.Bson;

namespace apitest.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IActionResult Get()
{
List<Base> objs = new List<Base>();
Base b = new Base2()

{ baseName = "BaseName", IBaseName = "IBaseName", base2Name = "base2Name" }

;
Base d = new Derived()

{ baseName = "BaseName", IBaseName = "IBaseName", derivedName = "derivedName" }

;
objs.Add(b);
objs.Add(d);

MongoDbContext m = new MongoDbContext();
var testCollection = m.GetCollection<Base>("test");

testCollection.DeleteMany(_ => true);

foreach (var obj in objs)

{ testCollection.InsertOne(obj); Console.WriteLine($"Completed write"); }

var filterDefinition = Builders<Base>.Filter.Where(s => true);
IEnumerable<Base> list = testCollection.Find(filterDefinition).ToList();

foreach (var o in list)
{
Console.WriteLine($"

{o.GetType().Name}

");
}
Console.ReadLine();
return new OkResult();
}
}

public abstract class Base
{
[BsonIgnoreIfDefault]
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id;
public string baseName;
public string IBaseName

{ get; set; }

}

public class Base2 : Base

{ public string base2Name; }

public class Derived : Base

{ public string derivedName; }

public class MongoDbContext
{
private IMongoDatabase _db;
public MongoDbContext()

{ var client = new MongoClient("mongodb://localhost:27017"); _db = client.GetDatabase("test"); }

public IMongoCollection<T> GetCollection<T>(string collectionName)

{ return _db.GetCollection<T>(collectionName); }

}

}

##################################################################################

Note that console properly shows the derived classes deserialized.

5) Now, comment the following lines in the Get Method:
##################################################################################
//testCollection.DeleteMany(_ => true);

//foreach (var obj in objs)
//

{ // testCollection.InsertOne(obj); // Console.WriteLine($"Completed write"); //}

##################################################################################

6) Run again and see that it throws following exception.
##################################################################################
Application started. Press Ctrl+C to shut down.
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[0]
An unhandled exception has occurred while executing the request
System.InvalidOperationException: Can't compile a NewExpression with a constructor declared on an abstract class
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitNewExpression(Expression expr)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitExpression(Expression node, CompilationFlags flags)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitLambdaBody(CompilerScope parent, Boolean inlined, CompilationFlags flags)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitLambdaBody()
at System.Linq.Expressions.Compiler.LambdaCompiler.Compile(LambdaExpression lambda)
at System.Linq.Expressions.Expression`1.Compile(Boolean preferInterpretation)
at MongoDB.Bson.Serialization.BsonClassMap.GetCreator()
at MongoDB.Bson.Serialization.BsonClassMap.CreateInstance()
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeClass(BsonDeserializationContext context)
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
at MongoDB.Driver.Core.Operations.CursorBatchDeserializationHelper.DeserializeBatch[TDocument](RawBsonArray batch, IBsonSerializer`1 documentSerializer, MessageEncoderSettings messageEncoderSettings)
at MongoDB.Driver.Core.Operations.FindCommandOperation`1.CreateCursorBatch(BsonDocument commandResult)
at MongoDB.Driver.Core.Operations.FindCommandOperation`1.CreateCursor(IChannelSourceHandle channelSource, BsonDocument commandResult, Boolean slaveOk)
at MongoDB.Driver.Core.Operations.FindCommandOperation`1.Execute(IReadBinding binding, CancellationToken cancellationToken)
at MongoDB.Driver.Core.Operations.FindOperation`1.Execute(IReadBinding binding, CancellationToken cancellationToken)
at MongoDB.Driver.OperationExecutor.ExecuteReadOperation[TResult](IReadBinding binding, IReadOperation`1 operation, CancellationToken cancellationToken)
at MongoDB.Driver.MongoCollectionImpl`1.ExecuteReadOperation[TResult](IReadOperation`1 operation, ReadPreference readPreference, CancellationToken cancellationToken)
at MongoDB.Driver.MongoCollectionImpl`1.ExecuteReadOperation[TResult](IReadOperation`1 operation, CancellationToken cancellationToken)
at MongoDB.Driver.MongoCollectionImpl`1.FindSync[TProjection](FilterDefinition`1 filter, FindOptions`2 options, CancellationToken cancellationToken)
at MongoDB.Driver.FindFluent`2.ToCursor(CancellationToken cancellationToken)
at MongoDB.Driver.IAsyncCursorSourceExtensions.ToList[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken)
at apitest.Controllers.ValuesController.Get() in C:\ws\gh\temp\apitest\apitest\Controllers\ValuesController.cs:line 40
at lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__14.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextResourceFilter>d__22.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeFilterPipelineAsync>d__17.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeAsync>d__15.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.<Invoke>d__7.MoveNext()
##################################################################################



 Comments   
Comment by Wan Bachtiar [ 22/Feb/19 ]

System.InvalidOperationException: Can't compile a NewExpression with a constructor declared on an abstract class

Hi Kiranj,

The exception is thrown by LambdaCompiler.EmitNewExpression because it's trying to construct from an abstract class. When deserializing polymorphic classes, it is important that the serializer know about all the classes in the hierarchy before deserialization begins. You could either declare:

    [BsonKnownTypes(typeof(Base2), typeof(Derived))]
    public abstract class Base 
    {
        [BsonIgnoreIfDefault]
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id;
        public string baseName;
        public string IBaseName { get; set; }
    }

or, you could register individually via code:

    BsonClassMap.RegisterClassMap<Base>();
    BsonClassMap.RegisterClassMap<Base2>();
    BsonClassMap.RegisterClassMap<Derived>();

See also MongoDB .NET/C# driver: Polymorphism

Please note that the CSHARP project is for reporting bugs or feature suggestions for the MongoDB .NET/C# driver. If you have any follow-up questions on the use of the driver, please post a question on mongodb-user group with relevant the information.

Regards,
Wan.

Comment by ??????? ???????? [ 19/Jul/18 ]

try to use [KnownBsonTypes] attribute on base class

Comment by kiranj email [ 19/Dec/17 ]

Before step 5, build and run to see it working.

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