LINQ Right Outer Join Problem
I am writing a right outer join query in SQL Server 2005 and it's working fine, but I am not able to convert it to LINQ.
Here is my query:
select b.number, COUNT(*) AS [AudioCount] from开发者_如何学Go audios a
right join months b on DATEPART(Month, a.[RecordedDate]) = b.number
group by number
Please help me convert it to LINQ.
Thanks & Regards, Anil Saklania
EDIT: Corrected query.
Depending on what you are looking for I have inverted it to be a left join but it is a left join from months to audio. This will enable you to return a count of zero when a month has no audio recordings. Used paolo's original testing data to test this out.
var audioMonths = from month in ListOfMonths
join audio in ListOfAudios on
month.number equals audio.RecordedDate.Month into audioLeftJoin
from audio in audioLeftJoin.DefaultIfEmpty()
select new
{
Month = month.number,
AudioId = audio != null ? audio.someProperty : null //Need some property on the audio object to see if it exists
};
var monthAudioCount = from audioMonth in audioMonths
group audioMonth by audioMonth.Month into grouping
select new
{
Month = grouping.Key,
AudioCount = grouping.Count(audioMonth => audioMonth.AudioId != null)
};
First, some notes from book: LINQ Pocket Reference by J. & B. Albahari:
1. Using an extra from translates to a SelectMany.
2. An into clause translates to a GroupJoin when it appears directly after a join clause.
Both of the excellent solutions above, by Mike and by Paolo, utilize a second, extra from clause in the query because that translates to a SelectMany. With SelectMany, a “sequence of sequences” ( a sequence of audio sequences ) is converted into a single flat collection result set. Then, to count the audios, that single flat output collection is, in a second step, grouped according to month. In both solutions above, that is done, and it works OK, but it also necessitates careful checking for nulls.
EXPLOITING THE NATURAL HIERARCHY. A cleaner alternative way is the use a GroupJoin instead of SelectMany. GroupJoin yields a hierarchical result set, rather than the flat result set of SelectMany. The hierarchical result set needs no grouping, of course, so we eliminate the second step.
Best of all, by utilizing the hierarchical result set of GroupJoin, we don’t have to check for nulls.
Thus we achieve another clean left outer join by this code, and borrowing Paolo's data:
static void Main(string[] args)
{
var ListOfAudios = new List<Audio>() {
new Audio() { someProperty = "test", RecordedDate = new DateTime(2011, 01, 01) },
new Audio() { someProperty = "test", RecordedDate = new DateTime(2011, 01, 02) },
new Audio() { someProperty = "test", RecordedDate = new DateTime(2011, 02, 01) },
new Audio() { someProperty = "test", RecordedDate = new DateTime(2011, 02, 02) }
};
var ListOfMonths = new List<Month>() {
new Month() {number=1, someMonthProperty="testMonth"},
new Month() {number=2, someMonthProperty="testMonth"},
new Month() {number=3, someMonthProperty="testMonth"}
};
var q = from month in ListOfMonths
join audio in ListOfAudios on month.number equals audio.RecordedDate.Month
into hierarch
select new
{
MonthNum = month.number,
AudioCnt = hierarch.Count()
};
foreach (var m in q)
{
Console.WriteLine("{0} - {1}", m.MonthNum,m.AudioCnt);
}
Console.ReadLine();
}
As per some of the comments to your question there are probably more straightforward ways to do what you want than translating your query to linq. However, just as an exercise, here's a way to write it:
var res = from audio in ListOfAudios
join month in ListOfMonths
on audio.RecordedDate.Month equals month.number into joinAudioMonth
from j in joinAudioMonth.DefaultIfEmpty()
group j by j.number into g
select new
{
number = g.Key,
cnt = g.Count()
};
EDIT: the code above does not do a RIGHT JOIN as you requested, here's a revised one based on Mike's answer. This one does not rely on a property of the Audio object (that might be null even if the object itself exists). But I'm being nitpicky, Mike's answer is basically the correct one.
var audioMonths =
from month in ListOfMonths
join audio in ListOfAudios on
month.number equals audio.RecordedDate.Month into monthAudioJoin
from joined in monthAudioJoin.DefaultIfEmpty()
select new
{
Month = month.number,
J = joined
};
var res = from audioMonth in audioMonths
group audioMonth by audioMonth.Month into grouping
select new
{
number = grouping.Key,
cnt = grouping.Count(a => a.J != null)
};
and here's how I tested it:
public class Audio
{
public string someProperty {get; set;}
public DateTime RecordedDate {get; set; }
}
public class Month
{
public string someMonthProperty {get; set;}
public int number {get; set; }
}
public static void Main (string[] args)
{
var ListOfAudios = new List<Audio>() {
new Audio(){someProperty="test", RecordedDate=new DateTime(2011,01,01)},
new Audio(){someProperty="test", RecordedDate=new DateTime(2011,01,02)},
new Audio(){someProperty="test", RecordedDate=new DateTime(2011,02,01)},
new Audio(){someProperty="test", RecordedDate=new DateTime(2011,02,02)}
};
var ListOfMonths = new List<Month>() {
new Month() {number=1, someMonthProperty="testMonth"},
new Month() {number=2, someMonthProperty="testMonth"},
new Month() {number=3, someMonthProperty="testMonth"}
// ...
};
var audioMonths =
from month in ListOfMonths
join audio in ListOfAudios on
month.number equals audio.RecordedDate.Month into monthAudioJoin
from joined in monthAudioJoin.DefaultIfEmpty()
select new
{
Month = month.number,
J = joined
};
var res = from audioMonth in audioMonths
group audioMonth by audioMonth.Month into grouping
select new
{
number = grouping.Key,
cnt = grouping.Count(a => a.J != null)
};
foreach(var r in res)
{
Console.WriteLine("{0} - {1}", r.number, r.cnt);
}
精彩评论