EF Core cannot convert from query select of anonymous type to DTO object class

Viewed 1114

I am having trouble casting the results of a custom query to a list of DTO objects. I am using EF Core 5.0.0.

Here is my DTO class:

public class UserPoolDto
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Key { get; set; }
    public string TypeName { get; set; }
    public string CategoryName { get; set; }
    public string StyleName { get; set; }
    public decimal RegCost { get; set; }
    public DateTime RegStartTimeUTC { get; set; }
    public DateTime RegEndTimeUTC { get; set; }
    public string Icon { get; set; }
    public string Description { get; set; }
    public string Instructions { get; set; }
    public string CreatedBy { get; set; }
}

This UserPoolDto is used in a view model as a simple list like so:

public class UserPoolVm
{
    public IList<UserPoolDto> Pools { get; set; }
}

Here is the code that uses EF core to query the DB and try to cast the results to a list of UserPoolDto objects to be assigned to my view model:

    public async Task<UserPoolVm> Handle(GetUserPoolsQuery request, CancellationToken cancellationToken)
    {
        var pools = await _context.Pools
                                  .Include(p => p.Type)
                                  .Include(p => p.Type.Category)
                                  .Include(p => p.Style)
                                  .Select(p => new
                                               {
                                                    p.Id,
                                                    p.Name,
                                                    p.Key,
                                                    TypeName = p.Type.Name,
                                                    CategoryName = p.Type.Category.Name,
                                                    StyleName = p.Style.Name,
                                                    p.RegCost,
                                                    p.RegStartTimeUTC,
                                                    p.RegEndTimeUTC,
                                                    p.Icon,
                                                    p.Description,
                                                    p.Instructions,
                                                    p.CreatedBy,
                                                })
                                  .Where(x => x.CreatedBy == request.UserId)
                                  .ToListAsync(cancellationToken);

        return new UserPoolVm
                   {
                       Pools = pools // ERROR IS THROWN ON THIS LINE
                   };
}

However, the project won't build and I get the following error (I broke the property types into separate lines so they are easier to compare with the class properties above):

Cannot implicitly convert type 'System.Collections.Generic.List<<anonymous type: long Id, string Name, string Key, string TypeName, string CategoryName, string StyleName, decimal RegCost, System.DateTime RegStartTimeUTC, System.DateTime RegEndTimeUTC, string Icon, string Description, string Instructions, string CreatedBy
to 'System.Collections.Generic.IList<Application.Pools.Queries.GetUserPools.UserPoolDto>'.
An explicit conversion exists (are you missing a cast?)

I've tried adding an explicit cast like so:

        return new UserPoolVm
        {
            Pools = (IList<UserPoolDto>)pools
        };

This lets the project build and run but I get the exact same error above as a runtime error instead of an error that prevents building.

I have checked multiple times to make sure the number, type, and name of all fields returns matches the UserPoolDto. It all matches. I can even debug the code and confirm that the objects returned by the query match the DTO object. However it still won't cast the results.

Question: can someone help me figure out why the anonymous type returned by the query cannot be cast to UserPoolDto even though the properties all match?

Thanks

2 Answers

When a compiler encounters a anonymous type it internally creates a class with a name which is auto generated. As a result even though your anonymous type and UserPoolDto dto have same properties they are two different types or classes altogether. So when we try to assign one with another and try to build it eventually results in a cast exception.

There are certain ways to solve the problem in hand by doing a cast as discussed here. However the best approach would be to directly create a dto instance inside of your select.

var pools = await _context.Pools
       .Include(p => p.Type)
       .Include(p => p.Type.Category)
       .Include(p => p.Style)
       .Select(p => new UserPoolDto
       {
           Id = p.Id,
           Name = p.Name,
           Key = p.Key,
           TypeName = p.Type.Name,
           CategoryName = p.Type.Category.Name,
           StyleName = p.Style.Name,
           RegCost = p.RegCost,
           RegStartTimeUTC = p.RegStartTimeUTC,
           RegEndTimeUTC = p.RegEndTimeUTC,
           Icon = p.Icon,
           Description = p.Description,
           Instructions = p.Instructions,
           CreatedBy = p.CreatedBy,
       })
       .Where(x => x.CreatedBy == request.UserId)
       .ToListAsync(cancellationToken);

The reason is because pool is now an anonymous type not the same type as the pool in your database anymore. Change the pool in your view model to

public class UserPoolVm
{
    public dynamic Pools {get; set;} 
} 

Then, simply pass the pools to the view model in the method

return new UserPoolVm { Pools = pools}; }

Pools would resolve the values in the anonymous type at runtime.

Related