我有分层数据的SQL表:
最高层次结构为5。 例如,第4行和第5行是第1行的子节点。
我注意让query expression
获得给定的儿童记录。现在我有pattern matching
:
let private queryForChild (db: dbml.MobileDataContext) id1 id2 id3 id4 id5 et =
match (id1, id2, id3, id4, id5) with
| _, "", "", "", "" -> query {
for rows in db.ItemType do
where (rows.Id1 = id1 && rows.Id2 <> "" && rows.Id3 = "" && rows.Id4 = "" && rows.Id5 = "" && rows.EntityType = et)
select rows
}
| _, _, "", "", "" -> query {
for rows in db.ItemType do
where (rows.Id1 = id1 && rows.Id2 = id2 && rows.Id3 <> "" && rows.Id4 = "" && rows.Id5 = "" && rows.EntityType = et)
select rows
}
| _, _, _, "", "" -> query {
for rows in db.ItemType do
where (rows.Id1 = id1 && rows.Id2 = id2 && rows.Id3 = id3 && rows.Id4 <> "" && rows.Id5 = "" && rows.EntityType = et)
select rows
}
| _, _, _, _, "" -> query {
for rows in db.ItemType do
where (rows.Id1 = id1 && rows.Id2 = id2 && rows.Id3 = id3 && rows.Id4 = id4 && rows.Id5 <> "" && rows.EntityType = et)
select rows
}
| _, _, _, _, _ -> query {
for rows in db.ItemType do
where (rows.Id1 = "-1")
select rows
}
我不喜欢它并且想知道有没有办法使用boolean operators
重写它以避免pattern matching
?
答案 0 :(得分:7)
确实无法在query
计算中使用自定义函数,因为query
计算的内部被引用(作为F# Quotation),然后(最终)转换为SQL和自定义函数无法如此翻译。
然而,与C#不同,F#确实在代码报价中提供了代码重用功能 - 它被称为“splicing”。
考虑一个例子:
let q = query { for x in listOfInts do yield x + 42 }
> q.Expression
val it : Expression = [1; 2; 3; ... ].Select(_arg1 => (_arg1 + 42))
让我说我真的不喜欢那边+ 42
,我想把它抽象出来。好吧,我可以这样做:
let add42 = <@ fun i -> i + 42 @>
let q = query { for x in listOfInts do yield (%add42) x }
如果我们现在检查q.Expression
,我们会发现它与之前的版本相同:
> q.Expression
val it : Expression = [1; 2; 3; ... ].Select(_arg1 => (_arg1 + 42))
这是这里发生的事情。 add42
是一个代码引用,包含一个向其参数添加42的函数。 %add42
表达式“插入”(又名“拼接”)引号在较大引号的中间,产生如下表达式:
let q = query { for x in listOfInts do yield (fun i -> i + 42) x }
此表达式在从F#代码引用转换为System.Linq.Expressions.Expression
期间得到简化,从而产生与第一个版本相同的表达式。
要添加的最后一部分:拼接引号不必是“常量”,它们也可以由函数生成。在构建整体报价期间对这些函数进行评估,然后对它们的结果进行拼接。例如,我可以像这样重新定义上面的代码:
let add a = <@ fun x -> x + a @>
let q2 = query { for x in list do yield (% add 42) x }
现在add
是一个以42
为参数并生成包含另一个函数的代码报价的函数。呼!
现在我们可以将所有这些应用到您的案例中:使自己成为一个函数,将idx
作为参数并生成函数的引用,该函数在拼接后将应用于row.idx
:
// NOTE: I'm not sure if this logic is correct. You'll have to verify it.
//
// For the i-th ID:
// * if all previous IDs are non-empty,
// but the i-th ID itself is empty,
// then the condition should check for i-th ID being non-empty.
// This means "query rows of i-th level".
// * if all previous IDs are non-empty,
// and the i-th ID itself is non-empty,
// then the condition should check for i-th ID being equal to
// This means "query rows of j-th level", where j > i
// * Otherwise, the condition should check for
// the i-th ID being empty.
// This means "query rows of j-th level", where j < i
let compare prevIds thisId =
if List.all ((<>) "") prevIds
then if thisId = ""
then <@ fun id -> id <> "" @>
else <@ fun id -> id = thisId @>
else <@ fun id -> id = "" @>
let private queryForChild (db: dbml.MobileDataContext) id1 id2 id3 id4 id5 et =
query {
for rows in db.ItemType do
where (
(% compare [] id1) rows.Id1 &&
(% compare [id1] id2) rows.Id2 &&
(% compare [id1; id2] id3) rows.Id3
(% compare [id1; id2; id3] id4) rows.Id4
(% compare [id1; id2; id3; id4] id5) rows.Id5 &&
rows.EntityType = et )
select rows
}
另请注意,您构建函数的方式,其行为未定义为具有“漏洞”的输入 - 即id1="x"
,id2=""
,id3="y"
- 您是否要查询在这种情况下的第二或第四级?我会建议一个更好的数据结构,排除无意义的输入。
答案 1 :(得分:2)
也许,你可以使用这样的东西:
let equalOrNotEmpty a b =
match a with
| "" -> b <> ""
| a -> a = b
并且用法是
let private queryForChild (db:dbml.MobileDataContext) id1 id2 id3 id4 id5 =
query {
for rows in db.ItemType do
where (equalOrNotEmpty rows.Id1 id1
&& equalOrNotEmpty rows.Id2 id2
&& equalOrNotEmpty rows.Id3 id3
&& equalOrNotEmpty rows.Id4 id4
&& equalOrNotEmpty rows.Id5 id5
&& rows.EntityType = et)
select rows
}