With leftOuterJoin, .DefaultIfEmpty() is unnecessary

狂风中的少年 提交于 2019-12-10 17:33:25

问题


The documentation for leftOuterJoin Query Expressions on MSDN repeatedly implies through the samples that when using leftOuterJoin .. on .. into .. that you must still use .DefaultIfEmpty() to achieve the desired effect.

I don't believe this is necessary because I get the same results in both of these tests which differ only in that the second one does not .DefaultIfEpmty()

type Test = A | B | C
let G = [| A; B; C|]
let H = [| A; C; C|]

printfn "%A" <| query {
    for g in G do
    leftOuterJoin h in H on (g = h) into I
    for i in I.DefaultIfEmpty() do 
    select (g, i)}

printfn "%A" <| query {
    for g in G do
    leftOuterJoin h in H on (g = h) into I
    for i in I do 
    select (g, i)}

// seq [(A, A); (B, null); (C, C); (C, C)]
// seq [(A, A); (B, null); (C, C); (C, C)]

1) Can you confirm this?

If that's right, I realized it only after writing this alternate type augmentation in an attempt to better deal with unmatched results and I was surprised to still see nulls in my output!

type IEnumerable<'TSource> with
    member this.NoneIfEmpty = if (Seq.exists (fun _ -> true) this) 
                              then Seq.map (fun e -> Some e) this 
                              else seq [ None ]

printfn "%A" <| query {
    for g in G do
    leftOuterJoin h in H on (g = h) into I
    for i in I.NoneIfEmpty do 
    select (g, i)}

// seq [(A, Some A); (B, Some null); (C, Some C); (C, Some C)]

2) Is there a way to get None instead of null/Some null from the leftOuterJoin?

3) What I really want to do is find out if there are any unmatched g

printfn "%A" <| query {
    for g in G do
    leftOuterJoin h in H on (g = h) into I
    for i in I.NoneIfEmpty do
    where (i.IsNone)
    exists (true) }

I figured this next one out but it isn't very F#:

printfn "%A" <| query {
    for g in G do
    leftOuterJoin h in H on (g = h) into I
    for i in I do
    where (box i = null) 
    exists (true)}

回答1:


Short version: Query Expressions use nulls. It's a rough spot in the language, but a containable one.

I've done this before:

let ToOption (a:'a) =
    match obj.ReferenceEquals(a,null) with
    | true -> None
    | false -> Some(a)

This will let you do:

printfn "%A" <| query {
    for g in G do
    leftOuterJoin h in H on (g = h) into I
    for i in I do 
    select ( g,(ToOption i))}

Which wraps every result in an option (since you don't know if there is going to be an I. It's worth noting that F# uses null to represent None at run-time as an optimization. So to check if this is indeed what you want, make a decision on the option, like:

Seq.iter (fun (g,h) -> 
              printf "%A," g; 
              match h with 
              | Some(h) -> printfn "Some (%A)" h 
              | None -> printfn "None")  
    <| query {
    for g in G do
    leftOuterJoin h in H on (g = h) into I
    for i in I do 
    select ((ToOption g),(ToOption i))}


来源:https://stackoverflow.com/questions/26008069/with-leftouterjoin-defaultifempty-is-unnecessary

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!