Open Risord opened 7 years ago
Unfortunately there's no way to have them share the same generated type.
What I'd like to do to support this in the future is let you specify paths to assemblies in rzsql.json. These would be called "row interface assemblies" or something like that, and the type provider would load up the interfaces from those assemblies and automatically add interface implementations for all the ones matched by a provided row type.
So in this case you might have something like this in MyRowInterfaceAssembly.dll, which should be built before MyAssemblyWithRezoomSQLQueries.dll:
type IItem =
abstract ItemId : int
abstract DataField : string
Then both getItemsQuery.Row
and getActiveItemsQuery.Row
could automatically implement that interface because they have the necessary columns, and you can write mapItem
to take an IItem
instead of a concrete row type.
I've held off on doing this because ultimately, I want it to be smart enough to do stuff like automatically map Id : int
to Id : ItemId
where ItemId
is a wrapper type around an int. But, I suppose it wouldn't hurt to introduce the feature in a bare-bones state where it'll only auto-implement the interface when column types match exactly.
Right now there are a couple things you can do to make this less painful.
Write a helper to convert to your domain type with F# inline functions. This is especially useful if you have some wrapper types in your domain model that RZSQL doesn't know about anyway, like type UserId = UserId of int
(highly recommended to avoid mixing up identifiers).
type ItemId = ItemId of int
type Item =
{ Id : ItemId
Name : string
}
let inline itemFromRow x =
{ Id = ItemId (^a : (member get_Id : unit -> int)(x))
Name = (^a : (member get_Name : unit -> string)(x))
}
Then you can use itemFromRow
on the different row types returned from your different queries.
The downside, of course, is that those inline constrained property invocations are ugly to read and write.
You can mitigate it somewhat by moving them into their own functions which can be reused for common column names like Id
, Name
, etc., but it's still not great.
Where possible, use fewer queries but make them configurable with parameters. E.g. for the minimal example, you could use:
select *
from Item i
where @activeFilter is null or i.IsActive = @activeFilter
And pass None
for activeFilter
to implement getItems
, Some true
to implement getActiveItems
.
Making identifiers to it's own types is quite clearly "the right way" and it's great that it's on your concern list.
Auto generated interface implementations sounds good way to work with this. Although this makes me hope that F# should have better support for general purpose compile time programming.
I think it should be also considered that should interfaces must be explicitly expressed like:
type itemQuery = SQL<"""[query]""", MyPrecompiledModule.IItem>
It may help with implementation and functionality would be much less magical. Also if interface is not actually valid I think it would be easier to produce much better error messages.
Is there way / plans to create types reusable?
If types have somewhat big complexity and there is lot of queries which have different filter / order logic but result data is identical you still have to duplicate mapping.
Minimal example: