С leftOuterJoin .DefaultIfEmpty() не нужен.

Документация по leftOuterJoin выражениям запросов в MSDN неоднократно подразумевает, что при используя leftOuterJoin .. on .. into .., вы все равно должны использовать .DefaultIfEmpty() для достижения желаемого эффекта.

Я не считаю это необходимым, потому что я получаю одинаковые результаты в обоих этих тестах, которые отличаются только тем, что второй не .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) Вы можете это подтвердить?

Если это так, то я понял это только после того, как написал эту альтернативную аугментацию типа в попытке лучше справиться с непревзойденными результатами, и я был удивлен, увидев все еще nulls в моем выводе!

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) Есть ли способ получить None вместо null/Some null из leftOuterJoin?

3) Что я действительно хочу сделать, так это выяснить, есть ли непревзойденные 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) }

Я понял следующее, но это не очень 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)}

person Jason Kleban    schedule 24.09.2014    source источник


Ответы (1)


Краткая версия: выражения запросов используют нули. Это трудное место в языке, но его можно сдержать.

Я делал это раньше:

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

Это позволит вам сделать:

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))}

Который заключает каждый результат в параметр (поскольку вы не знаете, будет ли I. Стоит отметить, что F # использует null для представления None во время выполнения в качестве оптимизации. Поэтому, чтобы проверить, действительно ли это то, что вы хотите, примите решение по варианту, например:

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))}
person Christopher Stevenson    schedule 24.09.2014
comment
Потрясающе, спасибо. Итак, вернемся к 1) действительно ли документация ошибочна? Я бы поставил на это. - person Jason Kleban; 24.09.2014
comment
Хм... как вы думаете, должно быть поведение? - person Christopher Stevenson; 24.09.2014
comment
После небольшого исследования DefaultIfEmpty ничего не делает, так как I сглажено. - person Christopher Stevenson; 24.09.2014
comment
Да, во всех случаях, верно? Я имею в виду, что способ C # LINQ выполнить левое соединение - это join .. into .. from ...DefaultIfEmtpy(), поэтому я думаю, что единственная причина иметь здесь ключевое слово join и leftOuterJoin заключается в том, что вам не нужно оба с этой ерундой. И независимо от провайдера я бы подумал, что пустой набор — это пустой набор. Я не знаю, что может измениться в поставщике, что повлияет на это (не то, чтобы я был квалифицирован, чтобы признать это). - person Jason Kleban; 24.09.2014