LINQ ke SQL - Kiri Luar Gabung dengan beberapa kondisi bergabung

148

Saya memiliki SQL berikut, yang saya coba terjemahkan ke LINQ:

SELECT f.value
FROM period as p 
LEFT OUTER JOIN facts AS f ON p.id = f.periodid AND f.otherid = 17
WHERE p.companyid = 100

Saya telah melihat implementasi tipikal dari gabungan luar kiri (mis. into x from y in x.DefaultIfEmpty()Dll . ) Tetapi saya tidak yakin bagaimana cara memperkenalkan kondisi gabungan lainnya ( AND f.otherid = 17)

EDIT

Mengapa AND f.otherid = 17kondisi bagian dari GABUNGAN bukan dalam klausa WHERE? Karena fmungkin tidak ada untuk beberapa baris dan saya masih ingin baris ini dimasukkan. Jika kondisi tersebut diterapkan dalam klausa WHERE, setelah GABUNG - maka saya tidak mendapatkan perilaku yang saya inginkan.

Sayangnya ini:

from p in context.Periods
join f in context.Facts on p.id equals f.periodid into fg
from fgi in fg.DefaultIfEmpty()
where p.companyid == 100 && fgi.otherid == 17
select f.value

tampaknya setara dengan ini:

SELECT f.value
FROM period as p 
LEFT OUTER JOIN facts AS f ON p.id = f.periodid 
WHERE p.companyid = 100 AND f.otherid = 17

yang tidak cukup apa yang saya cari.

dan
sumber
Manis! Saya telah mencari ini untuk sementara waktu tetapi tidak yakin bagaimana mencari ini. Tidak yakin cara menambahkan tag ke jawaban ini. Berikut kriteria pencarian yang saya gunakan: filter linq to sql di join atau dari linq ke sql di mana klausa di join atau dari
Solburn

Jawaban:

243

Anda harus memperkenalkan kondisi bergabung Anda sebelum menelepon DefaultIfEmpty(). Saya hanya akan menggunakan sintaks metode ekstensi:

from p in context.Periods
join f in context.Facts on p.id equals f.periodid into fg
from fgi in fg.Where(f => f.otherid == 17).DefaultIfEmpty()
where p.companyid == 100
select f.value

Atau Anda bisa menggunakan subquery:

from p in context.Periods
join f in context.Facts on p.id equals f.periodid into fg
from fgi in (from f in fg
             where f.otherid == 17
             select f).DefaultIfEmpty()
where p.companyid == 100
select f.value
dahlbyk
sumber
1
Terima kasih telah berbagi .Dimana kualifikasi pada pernyataan defaultifempty dari .... Saya tidak tahu Anda bisa melakukan itu.
Frank Thomas
28

ini juga berfungsi, ... jika Anda memiliki beberapa kolom bergabung

from p in context.Periods
join f in context.Facts 
on new {
    id = p.periodid,
    p.otherid
} equals new {
    f.id,
    f.otherid
} into fg
from fgi in fg.DefaultIfEmpty()
where p.companyid == 100
select f.value
ZenXavier
sumber
12

Saya tahu ini " agak terlambat " tetapi untuk berjaga-jaga jika ada yang perlu melakukan ini dalam sintaksis Metode LINQ ( itulah sebabnya saya menemukan posting ini pada awalnya ), ini akan menjadi cara melakukannya:

var results = context.Periods
    .GroupJoin(
        context.Facts,
        period => period.id,
        fk => fk.periodid,
        (period, fact) => fact.Where(f => f.otherid == 17)
                              .Select(fact.Value)
                              .DefaultIfEmpty()
    )
    .Where(period.companyid==100)
    .SelectMany(fact=>fact).ToList();
Prokuror
sumber
2
Sangat berguna untuk melihat versi lambda!
Pelajar
2
.Select(fact.Value)seharusnya.Select(f => f.Value)
Petr Felzmann
5

Opsi lain yang valid adalah untuk menyebarkan gabungan di beberapa klausa LINQ , sebagai berikut:

public static IEnumerable<Announcementboard> GetSiteContent(string pageName, DateTime date)
{
    IEnumerable<Announcementboard> content = null;
    IEnumerable<Announcementboard> addMoreContent = null;
        try
        {
            content = from c in DB.Announcementboards
              // Can be displayed beginning on this date
              where c.Displayondate > date.AddDays(-1)
              // Doesn't Expire or Expires at future date
              && (c.Displaythrudate == null || c.Displaythrudate > date)
              // Content is NOT draft, and IS published
              && c.Isdraft == "N" && c.Publishedon != null
              orderby c.Sortorder ascending, c.Heading ascending
              select c;

            // Get the content specific to page names
            if (!string.IsNullOrEmpty(pageName))
            {
              addMoreContent = from c in content
                  join p in DB.Announceonpages on c.Announcementid equals p.Announcementid
                  join s in DB.Apppagenames on p.Apppagenameid equals s.Apppagenameid
                  where s.Apppageref.ToLower() == pageName.ToLower()
                  select c;
            }

            // Add the specified content using UNION
            content = content.Union(addMoreContent);

            // Exclude the duplicates using DISTINCT
            content = content.Distinct();

            return content;
        }
    catch (MyLovelyException ex)
    {
        // Add your exception handling here
        throw ex;
    }
}
MA1aham1
sumber
bukankah itu lebih lambat daripada melakukan seluruh operasi dalam satu query LINQ?
Umar T.
@ umar-t, Ya kemungkinan besar, mengingat ini lebih dari delapan tahun yang lalu ketika saya menulisnya. Secara pribadi saya suka sub-kueri berkorelasi yang didalilkan oleh Dahlbyk di sini stackoverflow.com/a/1123051/212950
MAbraham1
1
"Serikat" adalah operasi yang berbeda dari "bergabung bersama". Ini seperti penambahan vs perkalian.
Suncat2000
1
@ Suncat2000, terima kasih atas koreksinya. Selamat Hari Thanksgiving! 👪🦃🙏
MAbraham1
0

Dapat ditulis menggunakan kunci gabungan gabungan. Juga jika ada kebutuhan untuk memilih properti dari sisi kiri dan kanan, LINQ dapat ditulis sebagai

var result = context.Periods
    .Where(p => p.companyid == 100)
    .GroupJoin(
        context.Facts,
        p => new {p.id, otherid = 17},
        f => new {id = f.periodid, f.otherid},
        (p, f) => new {p, f})
    .SelectMany(
        pf => pf.f.DefaultIfEmpty(),
        (pf, f) => new MyJoinEntity
        {
            Id = pf.p.id,
            Value = f.value,
            // and so on...
        });
Petr Felzmann
sumber
-1

Sepertinya saya ada nilai dalam mempertimbangkan beberapa penulisan ulang untuk kode SQL Anda sebelum mencoba menerjemahkannya.

Secara pribadi, saya akan menulis permintaan seperti itu sebagai gabungan (meskipun saya akan menghindari nol sepenuhnya!):

SELECT f.value
  FROM period as p JOIN facts AS f ON p.id = f.periodid
WHERE p.companyid = 100
      AND f.otherid = 17
UNION
SELECT NULL AS value
  FROM period as p
WHERE p.companyid = 100
      AND NOT EXISTS ( 
                      SELECT * 
                        FROM facts AS f
                       WHERE p.id = f.periodid
                             AND f.otherid = 17
                     );

Jadi saya kira saya setuju dengan semangat jawaban @ MAbraham1 (meskipun kode mereka tampaknya tidak terkait dengan pertanyaan).

Namun, tampaknya kueri dirancang khusus untuk menghasilkan hasil kolom tunggal yang terdiri dari baris duplikat - memang duplikat nol! Sulit untuk tidak sampai pada kesimpulan bahwa pendekatan ini cacat.

suatu hari nanti
sumber