有没有办法避免模式匹配

dem*_*mas 3 f#

我有分层数据的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

Fyo*_*kin 7

确实不可能在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"-你的意思是查询在这种情况下,第二或第四级?我会建议一个更好的数据结构,排除无意义的输入.