Also normal selects don't allow custom mapping to ...
# komapper
d
Also normal selects don't allow custom mapping to production DTOs (like templates do with
select { }
)? It's a pity to have to create an entity object only to map it afterwards to a DTO...? Especially since when using regular selects (w/o templates), we have the Meta classes to help retrieve fields to put them in the DTOs in a type-safe way...
t
Use
selectAsRecord
. You can map a
Record
to your DTO. https://www.komapper.org/docs/reference/query/querydsl/select/#selectasrecord
d
in a projection of less than four columns,
from the selectRecord docs...
What I'm looking for is more like:
Copy code
QueryDsl.from(a).mapper {
  Foo(
    a.id,
    a.name,
  )
}
Which would still use the entities for the queries, and type safety etc... but would allow mapping to DTOs without making them entities too... which is super useful for translating from the db layer to the domain layer...
If not all the columns are needed, one could still do:
Copy code
QueryDsl.from(a).select(a.id, a.name).mapper {
  Foo(
    a.id,
    a.name,
  )
}
the mapper has nothing to do with the sql being run, but rather only with mapping the result.
t
You can write as follows:
Copy code
val list = db.runQuery(QueryDsl.from(a))
list.map {
  Foo(
    it.id,
    it.name,
  )
}
Copy code
val list = db.runQuery(QueryDsl.from(a).select(a.id, a.name))
list.map {
  Foo(
    it.first,
    it.second,
  )
}
d
That's what I did before I asked, but I was wondering if this is just an extra step that Komapper has maybe some internal form that it converts to the Entity, then, I'd have to convert from that to the DTO... It seems like Record, does work a little like what I said, although the piece of docs that I quoted above seems to not be true... when calling selectAsRecord(), I suppose that gets all the fields, and when calling it with some fields, then it'll get only those... whereas the docs seem to imply that you can use it only with less than four columns...??? The only little thing I didn't like is that all the results are nullable... which leads to a bunch of code with !! spread out. But does selectRecord avoid some overhead rather than just letting Komapper map to the entity and mapping that to the DTO?
Btw, you can't know whether the field is nullable from whether the corresponding Entity's field is nullable? Then make an get() overload for the not null fields that can return without the null
it[a.id]
would normally return?
t
I was wondering if this is just an extra step that Komapper has maybe some internal form that it converts to the Entity, then, I’d have to convert from that to the DTO
The reason Komapper keeps the data as an entity is to make it type safe. I think the cost of instantiating an entity class is cheap enough. I would recommend converting the entities to DTOs.
d
And selectAsRecord() isn't type safe?
t
That’s right. It’s not type safe.
d
😮 And if I need only certain fields from the Entity? Isn't it a bit wasteful to load all of them? And so I'd loose type safety for using select(...) and selectAsRecord()...? It seems like Record uses this:
Copy code
override fun <T : Any> get(key: ColumnExpression<T, *>): T? {
        val value = map[key]
        return if (value == null) null else key.exteriorClass.cast(value)
    }
Don't you have enough information in the ColumnExpression to know the column's real type (from the entity), and maybe have another function like
getAsRealType(...)
instead of exteriorClass which I understand is the type being requested?
t
Isn’t it a bit wasteful to load all of them?
You may define a separate entity class with only the fields you need.
Don’t you have enough information in the ColumnExpression to know the column’s real type (from the entity), and maybe have another function like
getAsRealType(...)
instead of exteriorClass which I understand is the type being requested?
I am not sure I understand your idea. It would be great if you could provide an example implementation.
d
Maybe an inline reified
get()
backed up by a
get(key, asType: KClass)
could do the trick?
Now that I'm looking at it a bit better, I don't really understand in which way selectRecord isn't type-safe (apart from the nullable story...)? It returns the type of the column... so if the column's definition is right, it should be that the value is too...?
t
so if the column’s definition is right, it should be that the value is too...?
Yes