问题
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 null
s 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