views:

271

answers:

3

Hi,

I'm coding this forum and since I'm new to LINQ I ran into this problem when the user hits the main page. I want a table displaying a list of forums like this:

Forum  --- Topics (count) --- Posts (count) --- LastPostUserId --- LastPostTime

I have the following SQL tables:

Forums:
ForumId (int32),
Title (string),
Description (string)

ForumThreads:
ThreadId (int32),
ForumId (int32),
UserId (guid),
Subject (string),
Views (int32),
CreateDate (DateTime)

ForumPosts:
PostId (int32),
ThreadId (int32),
UserId (guid),
Post (string),
CreateDate (datetime)

Thank you...

+2  A: 
from forum in forums
from posts in db.ForumPosts.Where(p => p.Thread.ForumId.Equals(forum.ForumId))
select new
{
Forum = forum.Title, 
Topics = forum.ForumThreads.Count(),
Posts = posts.Count(),
LastPostBy = posts.OrderByDescending(p => p.CreateDate).FirstOrDefault(p => p.UserId),
LastPostTime= posts.Max(p => p.CreateDate))
}

untested ofcourse, but try to start from here and check the SQL query(s) it executes and let me know if it needs optimizing.

Thomas Stock
A: 

This almost does the trick (although it generates some horrible SQL ;-)...)

from forum in Forums
let posts = ForumPosts.Where(p => p.ForumThreads.ForumId.Equals(forum.ForumId))
select new
{
    Forum = forum.Title,
    Description = forum.Description,
    Topics = forum.ForumThreads.Count(),
    Posts = posts.Count(),
    LastPostId = posts.OrderByDescending(p=>p.PostId).Take(1).Select(p=>p.PostId),
    LastPostThreadId = posts.OrderByDescending(p=>p.PostId).Take(1).Select(p=>p.ThreadId),
    LastPostUserId = posts.OrderByDescending(p=>p.PostId).Take(1).Select(p=>p.UserId),
    LastPostTime = posts.OrderByDescending(p=>p.PostId).Take(1).Select(p=>p.CreateDate)
}

Last thing - I have a relationship from SQL table 'ForumPosts' to 'Aspnet_Users' and I would like to display the column Aspnet_Users.UserName as LastPostUserName... how can that be done? And how would you optimize the whole query?

Morten
add the Aspnet_Users to your dbml and p.aspnet_user.Name should just work, no?
Thomas Stock
and for optimizing query, could you copy paste the generated sql?
Thomas Stock
+1  A: 

For displaying the user's name if you use membership and you don't want to include the aspnet_Users in your dbml:

...
LastPostUserId = posts.OrderByDescending(p=>p.PostId).Take(1).Select(p=> Membership.GetUser(p.UserId))
...

Another change to make your posted sample a bit better is to add the orderbydescending in the posts variable: Then you can drop the 4 times repeated OrderByDescending from the select clause:

from forum in Forums
let posts = ForumPosts.Where(p => p.ForumThreads.ForumId.Equals(forum.ForumId)).OrderByDescending(p=>p.PostId)
select new
{
    Forum = forum.Title,
    Description = forum.Description,
    Topics = forum.ForumThreads.Count(),
    Posts = posts.Count(),
    LastPostId = posts.Take(1).Select(p=>p.PostId),
    LastPostThreadId = posts.Take(1).Select(p=>p.ThreadId),
    LastPostUserId = posts.Take(1).Select(p=>p.UserId),
    LastPostTime = posts.Take(1).Select(p=>p.CreateDate)
}

Or even cleaner:

from forum in Forums
let posts = ForumPosts.Where(p => p.ForumThreads.ForumId.Equals(forum.ForumId))
let lastPost = posts.OrderByDescending(p=>p.PostId).Take(1)
select new
{
    Forum = forum.Title,
    Description = forum.Description,
    Topics = forum.ForumThreads.Count(),
    Posts = posts.Count(),
    LastPostId = lastPost.PostId,
    LastPostThreadId = lastPost.ThreadId,
    LastPostUserId = lastPost.UserId,
    LastPostUserName = Membership.GetUser(lastPost.UserId),
    LastPostTime = lastPost.CreateDate
}

Test this code when there are no last posts tho, I think it might throw an error if Take(1) is null..

Thomas Stock
Take(1) doesn't throw an error if it's null. But however it doesn't seem possible to declare variables as LastPostThreadId = lastPost.ThreadId - I have to use .Select() ?Another thing.. is it possible to return values as strongly-typed instead of IOrderedQueryable<DateTime> and so on?
Morten
Ah yes, that's because Take(1) could as well be Take(100), so the system doesn't know that you are returning a single ForumPost. That's why you should use .FirstOrDefault() instead of .Take(1). That will let you use lastPost.CreateDate and will return it as a DateTime instead of an IOrderedQueryable<DateTime>.
Thomas Stock
You will have to check on null anyway. Easiest way is:LastPostTime = (lastPost != null ? lastPost.CreateDate : null)
Thomas Stock
Beautiful :-) Thanks!
Morten
thanks for marking as accepted :-) good luck
Thomas Stock