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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With