0

In my domain I have defined an entity Person

public class Person
{
    public string Id { get; set; } = String.Empty;
    public string Tag { get; set; } = String.Empty;
    public string Notes { get; set; } = String.Empty;
}

and in my application layer I have a shallow DTO which I use to embed in reponses of which I can be certain that the user will query the details if I do not embed them:

public class PersonDetailsShallow
{
    public string PersonId { get; internal set; } = null!;
    public string Tag { get; internal set; } = null!;
}

Since the mapping from Person to PersonDetailsShallow happens in various places, I created a static helper function for that:

public static PersonDetailsShallow ToPersonDetailsShallow(this Person person)
{
    return new PersonDetailsShallow()
    {
        PersonId = person.Id,
        Tag = person.Tag
    };
}

Then, whenever I need a shallow person, I create it as follows:

// using MediatR;

public async Task<MediatorResult<List<PersonDetails>>> Handle(AllPersonsQuery request, CancellationToken cancellationToken)
{
    await Task.CompletedTask;

    return dbContext.Persons
        .Select(p => p.ToPersonDetailsShallow())
        .ToList();
}

EF core however does not/ cannot recognise that the mapper function makes no use of Person.Notes and pulls it from the database by issuing the following query:

SELECT "p"."id", "p"."notes", "p"."tag"
FROM "persons" AS "p"

instead of issuing the more efficient query

SELECT "p"."id", "p"."tag"
FROM "persons" AS "p"

when doing the mapping like this:

var allPersons = dbContext.Persons
    .Select(p => new PersonDetailsShallow()
    {
        Id = p.Id,
        Tag = p.Tag
    })
    .ToList();

However, since I am using PersonDetailsShallow in so many different places, it would become a maintenance-nightmare if anything ever changes in the entity Person, because I would have the same mapping logic all over the place.

How can I have the benefit of maintaining the mapping logic in one place only while also getting the benefit of having optimised queries as I would when putting the mapping logic all over the place?


I am aware that I could just create a separate query that fetches exactly the information needed to create a PersonDetailsShallow and return it, but then, if for example I have a set of collection of PurchaseDetails-DTOs, each of which I would want to embed a PersonDetailsShallow, then I would need to query the PersonDetailsShallow for each PurchaseDetails separately, taking EF Core the possibility obtain all required data with one single query.

There must be some method UseMapping(person => new PersonDetailsShallow() {...}) that I am missing, where I can provide the mapping function as a parameter or something.

Benj
  • 889
  • 1
  • 14
  • 31
  • 1
    `IQuerable.Select()` takes an `Expression` argument. You could define the expression in a static variable... Or define an extension methods `IQueryable AsShallow(IQueryable query)`? – Jeremy Lakeman Mar 17 '22 at 05:40
  • Thank you both, that helped me to figure it out and I will write a self-answer in a bit. – Benj Mar 17 '22 at 06:13

0 Answers0