[CSHARP-3235] FormatException when using AutoMapper.ProjectTo Created: 23/Oct/20  Updated: 06/May/21  Resolved: 28/Oct/20

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

Type: Bug Priority: Major - P3
Reporter: Mohamad Javad Ebrahimi Assignee: James Kovacs
Resolution: Duplicate Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
duplicates CSHARP-1771 Support IIF method (i.e. ternary oper... Closed
is duplicated by CSHARP-3614 LINQ translation sometimes uses wrong... Closed

 Description   

 

var mapperConfiguration = new MapperConfiguration(config =>
{
    config.CreateMap<Post, PostDto>();
    config.CreateMap<Post.EmbeddedCategory, PostDto.CategoryDto>();
    config.CreateMap<Post.EmbeddedComment, PostDto.CommentDto>();
});
var mapper = mapperConfiguration.CreateMapper();
var result = postCollection
     .AsQueryable()
     .ProjectTo<PostDto>(mapper.ConfigurationProvider)
     .ToList();

 Message: System.FormatException : An error occurred while deserializing the Category property of class MongoDb.Bugs.PostDto: Element 'Id' does not match any field or property of class MongoDb.Bugs.PostDto+CategoryDto. ---- System.FormatException : Element 'Id' does not match any field or property of class MongoDb.Bugs.PostDto+CategoryDto. Stack Trace: MongoQueryProviderImpl`1.Execute(Expression expression) MongoQueryableImpl`2.GetEnumerator() List`1.ctor(IEnumerable`1 collection) Enumerable.ToList[TSource](IEnumerable`1 source) Tests.AutoMapper_ProjectTo() line 41 ----- Inner Stack Trace ----- BsonClassMapSerializer`1.DeserializeClass(BsonDeserializationContext context) BsonClassMapSerializer`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) IBsonSerializerExtensions.Deserialize(IBsonSerializer serializer, BsonDeserializationContext context) BsonClassMapSerializer`1.DeserializeMemberValue(BsonDeserializationContext context, BsonMemberMap memberMap)

 



 Comments   
Comment by James Kovacs [ 28/Oct/20 ]

Thank you for the additional information. Digging in further, the problem is specifically with CategoryDto. If you ignore the Category to CategoryDto mapping (via .ForMember(x => x.Category, opt => opt.Ignore())), your code works correctly.

Turning our attention to the equivalent Select that you provided offers more insight into the problem. The Category portion of the provided LINQ query translates into:

Category = IIF((p.Category == null), null, new CategoryDto() { Id = p.Category.Id, Name = p.Category.Name })

If we remove the ternary operator from the LINQ query, the following also runs successfully:

Category = new CategoryDto {
    Id = p.Category.Id,
    Name = p.Category.Name
}

Our current LINQ provider does not correctly or fully support LINQ's IIF conditional as noted in CSHARP-1771. We are currently working on a new LINQ translator, which will include support for IIF.

Thank you again for reporting this bug. Please monitor CSHARP-1771 for the availability of a fix to be included with the new LINQ provider.

Comment by Mohamad Javad Ebrahimi [ 28/Oct/20 ]

Thanks for replying.

No, it is not with AutoMapper.

The equivalent query that AutoMapper makes is:

var result = postCollection
	.AsQueryable()
	.Select(p => new PostDto
	{
		Id = p.Id,
		Title = p.Title,
		Category = p.Category == null ? null : new PostDto.CategoryDto
		{
			Id = p.Id,
			Name = p.Category.Name,
		},
		Comments = p.Comments.Select(c => new PostDto.CommentDto
		{
			Id = c.Id,
			Text = c.Text
		})
	})
	.ToList()

And equivalent to the previous query using json string is:

ProjectionDefinition<Post, PostDto> project = @"
{
	'Category': {
		'$cond': [{
				'$eq': ['$Category', null]
			}, null, {
				'Id': '$Category._id',
				'Name': '$Category.Name'
			}
		]
	},
	'Comments': {
		'$map': {
			'input': '$Comments',
			'as': 'dtoEmbeddedComment',
			'in': {
				'Id': '$$dtoEmbeddedComment._id',
				'Text': '$$dtoEmbeddedComment.Text'
			}
		}
	},
	'Id': '$_id',
	'Title': '$Title',
	'_id': 0
}";
 
var result = postCollection
	.Aggregate()
	.Project(project)
	.ToList();

But there is the same error with this.

 

Maybe this will help that a workaround for this is annotating CategoryDto with [BsonNoId] attribute.

 

Comment by James Kovacs [ 28/Oct/20 ]

Thank you for reporting the problem and providing the linked repro. We understand that you are attempting to use AutoMapper to convert POCOs (retrieved from MongoDB) to and from DTOs.

Let's focus on the query itself:

var result = postCollection
     .AsQueryable()
     .ProjectTo<PostDto>(mapper.ConfigurationProvider)
     .ToList();

If we remove AutoMapper's ProjectTo<PostDto>, result contains two correctly deserialized Post objects as expected as your DatabaseFixture pre-populates the collection with those post documents. With AutoMapper's ProjectTo<PostDto>, we observe the exception noted in your description. This appears to be an issue with AutoMapper and not with the MongoDB .NET Driver's LINQ provider. We recommend contacting the maintainers of AutoMapper to investigate further.

Comment by Mohamad Javad Ebrahimi [ 23/Oct/20 ]

reproduce code

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