HotChocolate transform data from database?

Viewed 635

I'm attempting to use the Hot Chocolate GraphQL library to transform the shape of my data before sending it back to the client.

Say for instance I have this object I want to return:

public class SimplePersonData
{
    public string First { get; set; }
    public string Last { get; set; }
    public string HomeTown { get; set; }
}

and my database entities look like this:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public int HomeAddressId { get; set; }
    public virtual Address HomeAddress { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

Without GraphQL, I'd do this with Entity Framework as such (assume a typical EF dbcontext for my two classes)

public async Task<SimplePersonData> GetPersonData(int id)
{
    var person = await dbContext.People
        .Include(p => p.HomeAddress)
        .FirstOrDefaultAsync(p => p.Id == id);

    return new SimplePersonData
    {
        First = person.FirstName,
        Last = person.LastName,
        HomeTown = person.Address?.City
    };
}

With HotChocolate, I'd define a query class that has a resolver like such for fetching a person object. Using the [UseProjection] attribute, it'll do some background magic that knows how to fetch the data without overfetching:

public class Query
{
    [UseProjection]
    public async Task<Person> GetPerson([Service] DbContext dbContext, int id)
    {
        return await dbContext.People.FirstOrDefault(p => p.Id == id);
    }
}

But things start getting messy if I want to translate the object into my SimplePersonData object. I don't see any easy way to easily transform my data while continuing to use projections. Is this possible to do in HotChocolate, and if so, how can it be accomplished? The issue as I'm understanding it is that because my object that's being returned has different field names, there's no way for the projection engine to know exactly what to fetch. I considered using AutoMapper for this, but it appears to change the query being made to fetch everything from the database required to do the mapping.

[UseProjection]
public async Task<SimplePersonData> GetSimplePersonData([Service] DbContext dbContext, int id)
{
    // Using AutoMapper provides promising results here, but causes the data to overfetch.
    // If the GraphQL request doesn't include 'homeTown' that data will still be fetched and then discarded.
    return await dbContext.People.ProjectTo<SimplePersonData>(_mapper.MapperConfiguration).FirstOrDefaultAsync(p => p.Id == id);
}

// sample mapping profile:
public class SimplePersonDataMappingProfile : Profile
{
    public SimplePersonDataMappingProfile()
    {
        CreateMapping<Person, SimplePersonData>()
            .ForMember(dst => dst.First, exp => exp.MapFrom(src => src.FirstName))
            .ForMember(dst => dst.Last, exp => exp.MapFrom(src => src.LastName))
            .ForPath(dst => dst.HomeTown, exp => exp.MapFrom(src => src.Address.City));
    }
}

TLDR: Our domain schema doesn't directly relate to our database schema. Is there a way to employ the projection feature to fetch exactly what we need to translate our database data into our domain data?

0 Answers
Related