dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.79k stars 3.19k forks source link

Using projection with nested expressions. #34866

Open boppercr opened 1 month ago

boppercr commented 1 month ago

Is there a way to still use projections with nested expression?

Here's some additionnal information. I have an entity that has one or many DTOs of it. Theses DTOs contain other DTOs from other entities, say a Blog has a Category which in turn has a SubCategory. I created a Mapper that works really well when there's only a few layers that looks like this :

public static class BlogMapper
{
    public static Expression<Func<Blog, BlogDTO>> ToBlogDTO = x =>
        new Blog
        {
            //Other properties
            Category = CategoryMapper.ToCategoryDTO.Compile.Invoke(x.Category);
        }
}

public static class CategoryMapper
{
    public static Expression<Func<Category, CategoryDTO>> ToCategoryDTO = x =>
        new Category
        {
            //Other properties
            SubCategory = SubCategoryMapper.ToSubCategoryDTO.Compile.Invoke(x.SubCategory);
        }
}

public static class SubCategoryMapper
{
    public static Expression<Func<SubCategory, SubCategoryDTO>> ToSubCategoryDTO = x =>
        new SubCategory
        {
            //Other properties
        }
}

Then I can use it like the following :

_context.Blogs.Select(BlogMapper.ToBlogDTO).ToList();

The issue arises because the query created by EF Core doesn't contain the SubCategory table and therefor its value is null when trying to map.

It feels like the projection stops at a certain level and I'm not sure I understand why.

The main thing is that I wouldn't want the BlogMapper to take care of the Category and SubCategory mappings since I know I'll already have dedicated mappers for this exact purpose. I'm not gonna lie, I feel like I'm either missing something or that it "doesn't make sense" to go at it like that but I can't seem to find the alternative.

I've tried an approach with simple constructors and they won't work with projection as well. I even tried to create an ExpressionVisitor to "flatten" the resulting expression somehow, but it didn't work as well. I can provide the code if need be, but like I said, I feel like I'm gone too far in the wrong direction.

Include provider and version information

EF Core version: 8.0.10 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 8.0

roji commented 1 month ago

This issue is lacking enough information for us to be able to fully understand what is happening. Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

boppercr commented 3 weeks ago

Sorry for the delay, I was away for a little time.

I added a small project to help understand better the issue. efcore-issue-34866.zip

Additionnal information

You can use the efcore-issue-34866.http file to test the API. The /current endpoint gives you the current behavior of EF Core when using expressions in the mapping.

You can see that the Expression<Func<Data, DTO>> is correctly translated to SQL but only to a single level.

The /expected endpoint gives you the expected behavior of EF Core when using expressions in the mapping which is the result when mapping manually.

The main goal is to not have to repeat the mapping behavior everytime it happens to be nested.

boppercr commented 12 hours ago

Does the code that I gave is sufficient to understand the issue or do you require more information?

roji commented 9 hours ago

@boppercr I unfortunately haven't gotten around to looking at this issue - with the .NET 9 release this week a lot of time went into that... As things stabilize we'll have more time to perform investigations - feel free to ping me again in a few weeks' time if there's still no answer, and thanks for your patience!