我有分层数据的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
}
Run Code Online (Sandbox Code Playgroud)
我不喜欢它,并想知道是否有任何方法可以使用boolean operators
以避免重写它pattern matching
?
确实不可能在query
计算中使用自定义函数,因为计算的内部query
被引用(作为F#Quotation)然后(最终)转换为SQL,并且自定义函数不能因此被转换.
但是,与C#不同,F#确实在代码引用中提供了代码重用功能 - 它被称为" 拼接 ".
考虑一个例子:
let q = query { for x in listOfInts do yield x + 42 }
> q.Expression
val it : Expression = [1; 2; 3; ... ].Select(_arg1 => (_arg1 + 42))
Run Code Online (Sandbox Code Playgroud)
让我说我真的不喜欢+ 42
那边,我想把它抽象出来.好吧,我可以这样做:
let add42 = <@ fun i -> i + 42 @>
let q = query { for x in listOfInts do yield (%add42) x }
Run Code Online (Sandbox Code Playgroud)
如果我们现在检查q.Expression
,我们会发现它与以前的版本相同:
> q.Expression
val it : Expression = [1; 2; 3; ... ].Select(_arg1 => (_arg1 + 42))
Run Code Online (Sandbox Code Playgroud)
这就是这里发生的事情.add42
是一个代码引用,包含一个向其参数添加42的函数.所述%add42
表述"插入"(又名"接头")在较大的报价的中间的那个报价,导致像这样的表达式:
let q = query { for x in listOfInts do yield (fun i -> i + 42) x }
Run Code Online (Sandbox Code Playgroud)
然后在从F#代码引用转换到该表达式时简化该表达式System.Linq.Expressions.Expression
,从而得到与第一个版本相同的表达式.
要添加的最后一部分:拼接引号不必是"常量",它们也可以由函数生成.在构建整体报价期间对这些函数进行评估,然后对它们的结果进行拼接.例如,我可以像这样重新定义上面的代码:
let add a = <@ fun x -> x + a @>
let q2 = query { for x in list do yield (% add 42) x }
Run Code Online (Sandbox Code Playgroud)
现在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
}
Run Code Online (Sandbox Code Playgroud)
另外请注意,您构建了您的功能的方式,其行为没有很好地与"孔"输入定义-即id1="x"
,id2=""
,id3="y"
-你的意思是查询在这种情况下,第二或第四级?我会建议一个更好的数据结构,排除无意义的输入.