Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using DTOs within the Hot Chocolate framework (using EFCore)

We are using EF Core within the Hot Chocolate framework to access some SQL databases. To overcome the issue with computed fields (described here) we make use of DTOs.

This works perfectly fine in the example below. When querying for "Foo", the "GetFoo" method is called which created a "fooDTO" object with the computed parameter.

[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<fooDTO> GetFoo([Service] ApiDbContext context) {
    return context.fooSet.AsEnumerable().Select(f => CreateFooDTO(f)).AsQueryable();
}

private static fooDTO CreateFooDTO(foo f){
    return new fooDTO{
        param1 = f.param1,
        param2 = f.param2,
        computedParam3 = param2 - param1,
        };
}

However, things go wrong when we want use DTOs when querying for "Bar" referring "Foo":

{
   bar {
      foo
   }
}

For that case we provided the following methods:

[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<barDTO> GetBar([Service] ApiDbContext context){
    return context.barSet.AsEnumerable().Select(b => CreateBarDTO(b)).AsQueryable();
}

private static barDTO CreateBarDTO(bar b){
    return new barDTO{
        param1 = b.param1,
        param2 = b.param2,
        param3 = CreateFooDTO(b.foo)
    };
}

Here "b.foo" seems to be always null, meaning that fooDTO is never materialized.

My questions:

  • Is this problem related to Hot Chocolate or EF Core?
  • How to properly use DTOs in this case?
like image 792
kvh Avatar asked Nov 18 '25 21:11

kvh


1 Answers

You have several choices here, using Automapper and it's ProjectTo or using libraries which helps in such case.

I would suggest to use LINQKit. It needs just configuring DbContextOptions:

builder
    .UseSqlServer(connectionString)
    .WithExpressionExpanding(); // enabling LINQKit extension

Since EF Core can work only with ExpressionTree, you have to pass this tree to LINQ query somehow. LINQKit has ability to mark methods and their Expression analogue together. Then during expanding it will inject needed parts during query translation.

Assuming that you have configured your DbContext to use LINQKit, you can extend your methods with ExpressionTree versions via ExpandableAttribute:

[Expandable(nameof(CreateBarDTOmpl))]
public static barDTO CreateBarDTO(bar b)
{
    throw new NotImplementedExpeption()
}

private static Expression<Func<bar, barDTO>> CreateBarDTOmpl()
{
    return b => new barDTO
    {
        param1 = b.param1,
        param2 = b.param2,
        param3 = CreateFooDTO(b.foo)
    };
}

[Expandable(nameof(CreateFooDTOImpl))]
public static fooDTO CreateFooDTO(foo f)
{
   throw new NotImplementedExpeption()
}

private static Expression<Func<foo, fooDTO>>fooDTO CreateFooDTOImpl()
{
    return f => new fooDTO
    {
        param1 = f.param1,
        param2 = f.param2,
        computedParam3 = f.param2 - f.param1,
    };
} 

Then usage is simple:

[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<fooDTO> GetFoo([Service] ApiDbContext context) 
{
    return context.fooSet.Select(f => CreateFooDTO(f));
}

It will generate the same projection as Automapper's ProjectTo but without configuring mapping. Later you may find how it useful to simplify a lot of similar predicates or other repetitive LINQ query parts.

like image 154
Svyatoslav Danyliv Avatar answered Nov 22 '25 04:11

Svyatoslav Danyliv



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!