Access child property from nested DTO of type IQueryable<T>

Viewed 382

Using .NET Core 3.1 with EF Core 3.1

Considering those 3 nested DTO :

public class AdminImmoOrderDto
{
    public IQueryable<AdminImmoOrderProductDto> OrderProducts { get; set; }
    public bool IsCompleted { get; set; }
}

public class AdminImmoOrderProductDto
{
    public IQueryable<AdminImmoOrderProductFileDto> OrderFiles { get; set; }
    public bool IsCompleted { get; set; }
}

public class AdminImmoOrderProductFileDto
{
    public string FileName { get; set; }
    public string FileUrl { get; set; }
}

An Order contains some OrderProducts and those OrderProducts contain some OrderFiles. The logic is as following : to be IsCompleted an OrderProduct must have at least one OrderFile.

I have written some extension method to do the mapping and projection logic, like this :

public static IQueryable<AdminImmoOrderProductDto> MapOrderProductToDto(this ICollection<OrderProducts> products)
        {
            return products
                .AsQueryable()
                .Select(p => new AdminImmoOrderProductDto
                {
                    IsCompleted = p.OrderFiles.Any(of => of != null),  
                    OrderFiles = p.OrderFiles.MapOrderProductFileToDto()
                });
        }
    }

Up until there, everything is working. But as you can see I'm using some method to map the children collection inside the parent mapping method.

With that in mind, when trying to set IsCompleted for the top level DTO OrderDto, I need to filter on the child property OrderProductDto.IsCompleted. This time, all the OrderProducts have to be completed for the parent Order to be considered completed aswell. I need to access IsCompleted in the children collection but I can't so I'm trying this :

public static IQueryable<AdminImmoOrderDto> MapOrderToDto(this IQueryable<Orders> orders)
        {
            return orders
                .Include(order => order.OrderProducts)
                    .ThenInclude(orderProduct => orderProduct.OrderFiles)
                .Select(o => new AdminImmoOrderDto
                {
                    OrderProducts = o.OrderProducts.MapOrderProductToDto(),
                    IsCompleted = o.OrderProducts.MapOrderProductToDto().All(od => od.IsCompleted == true)
                });
        }
    }

But this line

IsCompleted = o.OrderProducts.MapOrderProductToDto().All(od => od.IsCompleted == true)

Throws the following EF Core exception

.All(od => od.IsCompleted == True)' by 'NavigationExpandingExpressionVisitor' failed.
This may indicate either a bug or a limitation in EF Core.
See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.

And, has you can see the link refers to Client vs. Server Evaluation

This property IsCompleted is there to later filter my Orders. I've tried moving this piece of code in my filter without success. I have also tried to do this logic before mapping my DTO, using the linq code bloc instead of the property in the child DTO (since it does not exist before the mapping is done).

I also tried explicitly forcing the client evaluation by calling .ToList() or .AsEnumerable() before calling .All()

My question is : how can I set the IsCompleted property for OrderDto depending on the IsCompleted property in the child Dto OrderProductDto without flattening my top level IQueryable ?

Thanks you.

Update 10/03

I've tried moving this to my filter Query Object, i don't need the property if i can filter on the children of on order. Sadly the error is still the same. See code below.

    public enum OrdersFilterBy
    {
        NoFilter = 0,
        IsCompleted = 1,
    }
    public static class AdminImmoOrderListDtoFilter
    {
        public static IQueryable<AdminImmoOrderDto> FilterOrdersBy(this IQueryable<AdminImmoOrderDto> orders, OrdersFilterBy filterBy)
        {
            switch(filterBy)
            {
                case OrdersFilterBy.NoFilter:
                    return orders;
                case OrdersFilterBy.IsCompleted:
                    return orders.Where(o => o.OrderProducts.AsEnumerable().All(op => op.IsCompleted == true));
                default:
                    throw new ArgumentOutOfRangeException
                        (nameof(filterBy), filterBy, null);
            }
        }
    }
}

This line

return orders.Where(o => o.OrderProducts.AsEnumerable().All(op => op.IsCompleted == true));

With or without .AsEnumerable() for flattening child collection the result is the same. Same error as previous.

Update 10/03 Part 2

Here is an example of an API result if it helps getting a better idea of the hierarchy.

{
        "id": "1",
        "orderPicture": null,
        "saleType": 2,
        "clientName": "Toto",
        "clientPhone": "000000000",
        "fullAddress": "Toto road, 420",
        "smsCode": "XYZVDF",
        "creationDate": "2020-03-09T15:08:36.157",
        "visitDate": "2020-03-16T08:00:00",
        "orderProducts": [
            {
                "productTypeID": 1,
                "productName": "A sample product",
                "visitDate": "2020-03-16T08:00:00",
                "urgenceDate": null,
                "description": "7",
                "companyEmployeeNames": null,
                "isCompleted": false,
                "orderFiles": []
            }
        ]
    },
0 Answers
Related