Mapster icon indicating copy to clipboard operation
Mapster copied to clipboard

ProjectToType with Mongodb Queryable

Open ziaulhasanhamim opened this issue 4 years ago • 20 comments

Is there any way to use the ProjectToType extension with IMongoQueryable of MongoDB c# driver? IMongoQueryable extends IQueryable.

ziaulhasanhamim avatar Aug 19 '21 12:08 ziaulhasanhamim

I was solving a similar issue with AutoMaper. https://stackoverflow.com/a/66220779/4438653 You can cast the IQueryable back to IMongoQueryable.

skalahonza avatar Feb 05 '22 08:02 skalahonza

var posts = db.GetCollection<Post>("posts");

var postList = await ((IMongoQueryable<PostDto>)posts.AsQueryable()
    .ProjectToType<PostDto>())
    .ToListAsync();

class Post
{
    [BsonId]
    public ObjectId Id { get; set; }

    public string Title { get; set; }

    public string Description { get; set; }
}

public record PostDto(string Title, string Description);

I tried that but got this exception

Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'itemName')
   at MongoDB.Driver.Core.Misc.Ensure.IsNotNull[T](T value, String paramName)
   at MongoDB.Driver.Linq.Linq2Implementation.Expressions.SelectExpression..ctor(Expression source, String itemName, Expression selector)
   at MongoDB.Driver.Linq.Linq2Implementation.Processors.Pipeline.MethodCallBinders.SelectBinder.Bind(PipelineExpression pipeline, PipelineBindingContext bindingContext, MethodCallExpression node, IEnumerable`1 arguments)
   at MongoDB.Driver.Linq.Linq2Implementation.Processors.MethodInfoMethodCallBinder`1.Bind(PipelineExpression pipeline, TBindingContext bindingContext, MethodCallExpression node, IEnumerable`1 arguments)
   at MongoDB.Driver.Linq.Linq2Implementation.Processors.PipelineBinderBase`1.BindMethodCall(MethodCallExpression node)
   at MongoDB.Driver.Linq.Linq2Implementation.Processors.PipelineBinderBase`1.Bind(Expression node)
   at MongoDB.Driver.Linq.Linq2Implementation.Processors.Pipeline.PipelineBinder.Bind(Expression node, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.Linq.Linq2Implementation.MongoQueryProviderImpl`1.Prepare(Expression expression)
   at MongoDB.Driver.Linq.Linq2Implementation.MongoQueryProviderImpl`1.Translate(Expression expression)
   at MongoDB.Driver.Linq.Linq2Implementation.MongoQueryProviderImpl`1.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at MongoDB.Driver.Linq.Linq2Implementation.MongoQueryableImpl`2.ToCursorAsync(CancellationToken cancellationToken)
   at MongoDB.Driver.IAsyncCursorSourceExtensions.ToListAsync[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in C:\Users\ziaul\\MapsterMongo\Program.cs:line 13       
   at Program.<Main>(String[] args)

ziaulhasanhamim avatar Feb 05 '22 12:02 ziaulhasanhamim

This happened to me with automapper as well. What helped was writing the record in the old syntax with default constructor with no parameters.

public record PostDto 
{
   public string Title { get; init; }
   public string Description { get; init; }
}

skalahonza avatar Feb 06 '22 15:02 skalahonza

If that doesn't work for you, the last resort workaround could be that you can fetch your MongoDb entities into list. Then call Adpat and perform the mapping in memory and not in database,

skalahonza avatar Feb 06 '22 15:02 skalahonza

This happened to me with automapper as well. What helped was writing the record in the old syntax with default constructor with no parameters.

doesn't help

ziaulhasanhamim avatar Feb 06 '22 15:02 ziaulhasanhamim

Well, according to this: https://github.com/MapsterMapper/Mapster/issues/403. This project might not be maintained anymore. So in my opinion you have two options:

  1. Do mapping in memory
  2. Switch to another mapping tool e.g. AutoMapper

skalahonza avatar Feb 07 '22 12:02 skalahonza

yeah you are correct. But I really like the way mapster does things. No need for di containers needed. Very simple mapping with extensions

ziaulhasanhamim avatar Feb 07 '22 12:02 ziaulhasanhamim

Yes, it works very well with Entity Framework, however acts weird with MongoDb.

skalahonza avatar Feb 07 '22 12:02 skalahonza

it would be very sad if it's not maintained anymore

ziaulhasanhamim avatar Feb 07 '22 13:02 ziaulhasanhamim

@andrerav can you give something on it?

ziaulhasanhamim avatar Feb 18 '22 12:02 ziaulhasanhamim

Hi @ziaulhasanhamim, I'm currently working on streamlining the build and packaging and get a new version of Mapster.Tool as soon as possible. After that I can take a closer look at this issue. I've read through this thread but not sure if I am understanding the problem correctly. Can you give me a clear explanation of what kind of functionality is missing in Mapster to help you with this problem?

andrerav avatar Feb 18 '22 13:02 andrerav

Mapster supports mapping for queryable. I get a weird exception when I try to use mapster mapping with MongoDB queryable. MongoDB Queryable is very similar to ef core. Also I think automapper supports it without any extra dependency.

I think the issue is about parameter names in the expression that mapster creates to map one type to other. Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'itemName') here the item name is the parameter name. MongoDB doesn't allow the parameter name to be null. It can be anything other than null.

I think this is how parameters are created in mapster expressions

Expression.Parameter(parameterType)

but there is also a second parameter which is parameterName.

Expression.Parameter(parameterType, parameterName)

This is my observation of the problem. It's very much possible that I'm wrong because I'm not that familiar with expression and queryable APIs.

But mapster can support MongoDB. I won't think there will be a lot of work.

I know there are many works pending but I just hope this project keeps up. This is literally the best mapper I've used in .net. Thanks to you for taking responsibility for this project

ziaulhasanhamim avatar Feb 18 '22 14:02 ziaulhasanhamim

@andrerav Anything on this?

ziaulhasanhamim avatar Aug 22 '22 07:08 ziaulhasanhamim

I'm using extension method:

public static IMongoQueryable<TResult> ProjectTo<TResult>(this IMongoQueryable query, TypeAdapterConfig config) { return (IMongoQueryable<TResult>)query.ProjectToType<TResult>(config); }

Ryba1986 avatar Oct 03 '22 18:10 Ryba1986

@Ryba1986 does't that thow exception? For me it throws an exception

ziaulhasanhamim avatar Oct 04 '22 01:10 ziaulhasanhamim

I am using LinqProvider v3

Ryba1986 avatar Oct 04 '22 05:10 Ryba1986

Thank you sir appreciate your efforts. I was searching a solution for this for a lot of time.

ziaulhasanhamim avatar Oct 04 '22 05:10 ziaulhasanhamim

@ziaulhasanhamim Using ProjectToType() with IQueryable can cause problems in cases where there are asynchronous operations. I suggest that you materialize your query (with ToList(), for example) before doing any mapping. This should fix your problem.

andrerav avatar Mar 04 '23 20:03 andrerav

Hi

var posts = db.GetCollection<Post>("posts");

var postList = await ((IMongoQueryable<PostDto>)posts.AsQueryable()
    .ProjectToType<PostDto>())
    .ToListAsync();

class Post
{
    [BsonId]
    public ObjectId Id { get; set; }

    public string Title { get; set; }

    public string Description { get; set; }
}

public record PostDto(string Title, string Description);

I tried this code and it works correct if use MongoDb.Driver v2.19.0 and it gets exception in v2.18.0. I think it was bug of MongoDb.Driver

alex-tselikovsky avatar Mar 08 '23 13:03 alex-tselikovsky

@Ryba1986 does't that thow exception? For me it throws an exception

I am using LinqProvider v3

I tried this code and it works correct if use MongoDb.Driver v2.19.0 and it gets exception in v2.18.0. I think it was bug of MongoDb.Driver

MongoDb.Driver before v2.19.0 using LinqProvider v2 for default.

Ryba1986 avatar Jun 04 '23 17:06 Ryba1986