Replies: 9 comments
-
I can see how it would be useful to be able to do conditional where statements, especially for scenarios where there is a query spec passed in with optional filter parameters. This would definitely be a worthwhile feature to implement. 🤔 For an immediate workaround, I think the easiest option would be to conditionally create two versions of the same query: use ctx = openContext()
let query =
if filterByLastName then
select {
for e in employeesTable do
where (e.LastName = "Smith")
}
else
select {
for e in employeesTable do
select e
}
let! results = query |> ctx.ReadAsync HydraReader.Read Another (more manual) option would be to conditionally modify the underlying use ctx = openContext()
let query =
select {
for e in employeesTable do
select e
}
// Manually edit SqlKata.Query
let kataQuery = query.ToKataQuery()
if filterByLastName then
kataQuery.Where("LastName", "=", "Smith") |> ignore
// Convert back to SelectQuery and run
let results =
kataQuery
|> SelectQuery<dbo.Employees>
|> ctx.ReadAsync HydraReader.Read If your conditional filtering is extensive enough, the 2nd approach may be worth it for more fine grained control of the filtering criteria. |
Beta Was this translation helpful? Give feedback.
-
As for implementing conditional queries, I can imagine a few options off the top of my head:
type EmployeeFilters = {
FirstName: string option
LastName: string option
}
let getEmployees(filters: EmployeeFilters) =
use ctx = openContext()
select {
for e in employeesTable do
whereIf (e.LastName = filters.LastName.Value, filters.LastName.IsSome)
whereIf (e.FirstName = filters.FirstName.Value, filters.FirstName.IsSome)
}
|> ctx.ReadAsync HydraReader.Read This approach might also need to handle things like
let getEmployees(filters: EmployeeFilters) =
let query =
select {
for e in employeesTable do
select e
}
let query =
if filters.LastName.IsSome then
query |> ConditionalFilters.where (fun e -> e.LastName = filters.LastName.Value)
if filters.FirstName.IsSome then
query |> ConditionalFilters.where (fun e -> e.FirstName = filters.FirstName.Value)
query |> ctx.ReadAsync HydraReader.Read This might also need to handle |
Beta Was this translation helpful? Give feedback.
-
Good idea with the workarounds, that'll get me by for now. With regards to implementing whereIf, I've tried a few things. Very similar to what you mention above: [<CustomOperation("whereIf", MaintainsVariableSpace = true)>]
member this.WhereCondition (state:QuerySource<'T>, [<ProjectionParameter>] whereExpressionAndCondition) =
let query = state |> getQueryOrDefault
if snd whereExpressionAndCondition then
this.Where(state, (fst whereExpressionAndCondition))
else
QuerySource<'T, Query>(query, state.TableMappings) Using it like so:
At first had the following error message:
When I realised F# couldn't automatically turn the where code into an expression with the tuple, so I made a helper in an attempt to build the where expression elsewhere. // Expression helper
type Spec =
static member Where<'T>(e: Expression<Func<'T, bool>>) = e
// Create the expression separately
let whereExp = Spec.Where<SalesOrderHeader>((fun o -> o.AccountNumber = Some "Test"))
let query =
select {
for o in orderHeaderTable do
whereIf (whereExp, true)
select (o)
} This however gives me the following error:
I was then curious as to whether I could use the whereExp directly with the already existing "where" function where (whereExp) That gives the error:
I think this is the biggest clue as to why it doesn't work with how I am using it, but I just don't know enough F# yet to know why \o_o/ If you have any hints as to what I might need to look into / read up on to understand this I'd be very grateful. |
Beta Was this translation helpful? Give feedback.
-
There’s not much material available on creating CEs so most of my success has just been experimentation and trial and error TBH. |
Beta Was this translation helpful? Give feedback.
-
Try this: /// Sets the WHERE condition if applyFilter is true
[<CustomOperation("whereIf", MaintainsVariableSpace = true)>]
member this.WhereIf (state: QuerySource<'T>, [<ProjectionParameter>] whereExpression, applyFilter) =
let query = state |> getQueryOrDefault
let where = LinqExpressionVisitors.visitWhere<'T> whereExpression (FQ.fullyQualifyColumn state.TableMappings)
if applyFilter
then QuerySource<'T, Query>(query.Where(fun w -> where), state.TableMappings)
else QuerySource<'T, Query>(query, state.TableMappings)
The tricky part is that it converts it to a curried function, so you have to call it like this: select {
for a in addressTable do
whereIf (a.AddressLine2 <> None) (1 = 1)
}
|
Beta Was this translation helpful? Give feedback.
-
Ah, I did try multiple parameters but it was probably the currying that threw me.
Thanks, I’ll have a play around and we can see how it feels to use.
… On 11 Sep 2021, at 18:54, Jordan Marr ***@***.***> wrote:
Try this:
/// Sets the WHERE condition if applyFilter is true
[<CustomOperation("whereIf", MaintainsVariableSpace = true)>]
member this.WhereIf (state: QuerySource<'T>, [<ProjectionParameter>] whereExpression: Expression<Func<'T, bool>>, applyFilter: bool) =
let query = state |> getQueryOrDefault
let where = LinqExpressionVisitors.visitWhere<'T> whereExpression (FQ.fullyQualifyColumn state.TableMappings)
if applyFilter
then QuerySource<'T, Query>(query.Where(fun w -> where), state.TableMappings)
else QuerySource<'T, Query>(query, state.TableMappings)
The tricky part is that it converts it to a curried function, so you have to call it like this:
test "Where If" {
let query =
select {
for a in addressTable do
whereIf (a.AddressLine2 <> None) (1 = 1)
}
query.ToKataQuery() |> toSql |> printfn "%s"
}
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.
|
Beta Was this translation helpful? Give feedback.
-
If I put out a new release that makes a few internal modules public, it would be really easy for you to extend SelectExpressionBuilder like this: let getQueryOrDefault (state: QuerySource<'Result>) =
match state with
| :? QuerySource<'Result, SqlKata.Query> as qs -> qs.Query
| _ -> SqlKata.Query()
type SelectExpressionBuilder<'T> with
/// Sets the WHERE condition if applyFilter is true
[<CustomOperation("whereIf", MaintainsVariableSpace = true)>]
member this.WhereIf (state: QuerySource<'T>, [<ProjectionParameter>] whereExpression, applyFilter) =
let query = state |> getQueryOrDefault
let where = LinqExpressionVisitors.visitWhere<'T> whereExpression (FQ.fullyQualifyColumn state.TableMappings)
if applyFilter
then QuerySource<'T, SqlKata.Query>(query.Where(fun w -> where), state.TableMappings)
else QuerySource<'T, SqlKata.Query>(query, state.TableMappings)
(Currently the That would make it much easier to experiment with potential changes to the API in a real project, because I suspect that some other things will come to light over time. |
Beta Was this translation helpful? Give feedback.
-
I don't necessarily need access to the LinqExpressionVisitors right now, as I can call it via this.Where. Perhaps we carry on as we are, rather than exposing something that you will probably want to bring back internally in the future. I'll continue and see where I get, then post back here with how things have gone and whether I do need access or not. |
Beta Was this translation helpful? Give feedback.
-
I've not been able to get back to where I was using this, but my initial feedback is that reversing the order of parameters seems easier to read because the boolean value is often shorter than the where clause itself, which then allows you to skip to the next line if you trying to think through the query being built. I definitely see no need to rush this in though, it would be good to use it in the field more first. |
Beta Was this translation helpful? Give feedback.
-
I was trying to add a conditional where clause in the CE but hitting dead ends. If I was using SqlKata directly I'd be able to wrap it in an if statement.
I tried to do similar in the CE but the compiler tells me:
I started to play around with adding a new customOperator that received a type which included the condition, but I ran into compiler errors I don't understand and quickly realised how very little I know about how computational expressions.
Firstly, is there a way of doing this with the library as it is today? If not, did you have an idea of how this might be possible? I'm happy to give it a go if pointed in the right direction.
Beta Was this translation helpful? Give feedback.
All reactions