user1112150
user1112150

Reputation: 13

Linq To Sql - return table result and count

i'm very new to linq to sql and in need of a little assistance.

Basically i'm building a message board in C#. I have 3 database tables - basic info is as follows.

FORUMS forumid name

THREADS threadid forumid title userid

POSTS postid threadid text userid date

Basically I want to bring back everything I need in one query. I want to list a page of THREADS (for a particular FORUM) and also display the number of POSTS in that THREAD row and when the last POST was for that THREAD.

At the moment i'm getting back all THREADS and then looping through each the result set and making calls to the POST table seperately for the POST count for a Thread and the Latest Post in that thread but obviously this will cause problems in terms of hitting the database as the Message Board gets bigger.

My Linq To SQL so far:

    public IList<Thread> ListAll(int forumid)
    {
        var threads =
            from t in db.Threads
            where t.forumid == forumid
            select t;
        return threads.ToList();
    }

basicaly i now need to get the number of POSTS in each thread and the date of the last post in each thread.

Any help would be most appreciated :)

EDIT

Hi guys. Thanks for tyour help so far. Basically i'm almost there. However, I left an important part out of my initial question in the fact that I need to retrieve the user name of the person making the last POST. Therefore I need to join p.userid with u.userid on the USERS table. So far I have the following but just need to amend this to join the POST table with the USER table:

    public IList<ThreadWithPostInfo> ListAll(int forumid)
    {
        var threads = (from t in db.Threads
                                   where t.forumid == forumid
                                   join p in db.Posts on t.threadid equals p.threadid into j
                       select new ThreadWithPostInfo() { thread = t, noReplies = j.Count(), lastUpdate = j.Max(post => post.date) }).ToList();
        return threads;
    }

UPDATE:

    public IList<ThreadWithPostInfo> ListAll(int forumid)
    {
        var threads = (from t in db.Threads
                       from u in db.Users
                       where t.forumid == forumid && t.hide == "No" && t.userid == u.userid
                                   join p in db.Posts on t.threadid equals p.threadid into j
                       select new ThreadWithPostInfo() { thread = t, deactivated = u.deactivated, lastPostersName = j.OrderByDescending(post => post.date).FirstOrDefault().User.username, noReplies = j.Count(), lastUpdate = j.Max(post => post.date) }).ToList();
        return threads;
    }

I finally figured that part of it out with thanks to all of you guys :). My only problem now is the Search Results method. At the moment it is like this:

    public IList<Thread> SearchThreads(string text, int forumid)
    {
        var searchResults = (from t in db.Threads
                            from p in db.Posts
                            where (t.title.Contains(text) || p.text.Contains(text)) && t.hide == "No"
                            && p.threadid == t.threadid
                            && t.forumid == forumid
                            select t).Distinct();
        return searchResults.ToList();
    }

Note that I need to get the where clause into the new linq code:

        where (t.title.Contains(text) || p.text.Contains(text)) && t.hide == "No"

so incorporating this clause into the new linq method. Any help is gratefully received :)

SOLUTION:

I figured out a solution but I don't know if its the best one or most efficient. Maybe you guys can tell me because i'm still getting my head around linq. James I think your answer was closest and got me to near to where I wanted to be - thanks :)

   public IList<ThreadWithPostInfo> SearchThreads(string text, int forumid)
    {
        var searchResults = (from t in db.Threads
                             from p in db.Posts
                             where (t.title.Contains(text) || p.text.Contains(text)) && t.hide == "No"
                             && p.threadid == t.threadid
                             && t.forumid == forumid
                             select t).Distinct();

        //return searchResults.ToList();           

        var threads = (from t in searchResults 
                            join p in db.Posts on t.threadid equals p.threadid into j
                       select new ThreadWithPostInfo() { thread = t,  lastPostersName = j.OrderByDescending(post => post.date).FirstOrDefault().User.username, noReplies = j.Count(), lastUpdate = j.Max(post => post.date) }).ToList();
        return threads;
    }

Upvotes: 0

Views: 3075

Answers (4)

JamieSee
JamieSee

Reputation: 13010

I think what you're really looking for is this:

        var threadsWithPostStats = from t in db.Threads
                                   where t.forumid == forumid
                                   join p in db.Posts on t.threadid equals p.threadid into j
                                   select new { Thread = t, PostCount = j.Count(), LatestPost = j.Max(post => post.date) };

Per your comment and updated question, I'm adding this restatement:

       var threadsWithPostsUsers = from t in db.Threads
                                   where t.forumid == forumid
                                   join p in db.Posts on t.threadid equals p.threadid into threadPosts
                                   let latestPostDate = threadPosts.Max(post => post.date)
                                   join post in db.Posts on new { ThreadID = t.threadid, PostDate = latestPostDate } equals new { ThreadID = post.threadid, PostDate = post.date} into latestThreadPosts
                                   let latestThreadPost = latestThreadPosts.First()
                                   join u in db.Users on latestThreadPost.userid equals u.userid
                                   select new { Thread = t, LatestPost = latestThreadPost, User = u  };

Upvotes: 0

AD.Net
AD.Net

Reputation: 13399

   public IList<Thread> ListAll(int forumid)
    {
        var threads =
            from t in db.Threads
            where t.forumid == forumid
            select new 
                    {
                      Thread = t,
                      Count = t.Post.Count,
                      Latest = t.Post.OrderByDescending(p=>p.Date).Select(p=>p.Date).FirstOrDefault()
                    }
    }

Should be something like that

Upvotes: 1

fajarhide
fajarhide

Reputation: 58

May be Too many database calls per session ....

Calling the database,. whether to query or to write, is a remote call, and we want to reduce the number of remote calls as much as possible. This warning is raised when the profiler notices that a single session is making an excessive number of calls to the database. This is usually an indication of a potential optimization in the way the session is used.

There are several reasons why this can be:

  • A large number of queries as a result of a Select N + 1
  • Calling the database in a loop
  • Updating (or inserting / deleting) a large number of entities
  • A large number of (different) queries that we execute to perform our task

For the first reason, you can see the suggestions for Select N + 1. Select N + 1 is a data access anti-pattern where the database is accessed in a suboptimal way. Take a look at this code sample :

    // SELECT * FROM Posts
var postsQuery = from post in blogDataContext.Posts
                 select post;

foreach (Post post in postsQuery)
{   
    //lazy loading of comments list causes:    
    // SELECT * FROM Comments where PostId = @p0   
    foreach (Comment comment in post.Comments)
    {       
        //print comment...   
    }
}

In this example, we can see that we are loading a list of posts (the first select) and then traversing the object graph. However, we access the collection in a lazy fashion, causing Linq to Sql to go to the database and bring the results back one row at a time. This is incredibly inefficient, and the Linq to Sql Profiler will generate a warning whenever it encounters such a case.

The solution for this example is simple. Force an eager load of the collection using the DataLoadOptions class to specify what pieces of the object model we want to load upfront.

var loadOptions = new DataLoadOptions();
loadOptions.LoadWith<Post>(p => p.Comments);
blogDataContext.LoadOptions = loadOptions;

// SELECT * FROM Posts JOIN Comments ...
var postsQuery = (from post in blogDataContext.Posts
                 select post);

foreach (Post post in postsQuery)
{   
    // no lazy loading of comments list causes    
    foreach (Comment comment in post.Comments)
    {       
        //print comment...   
    }
}

next is updating a large number of entities is discussed in Use Statement Batching, and can be achieved by using the PLinqO project, which is a set of extensions on top of Linq to Sql. How cool would it be to store items in cache as a group. Well, guess what! PLINQO is cool! When storing items in cache, just tell PLINQO the query result needs to belong to a group and specify the name. Invalidating cache is where the coolness of grouping really shows up. No coupling of cache and actions taken on that cache when they are in a group. Check out this example :

public ActionResult MyTasks(int userId)
{
    // will be separate cache for each user id, group all with name MyTasks
    var tasks = db.Task
        .ByAssignedId(userId)
        .ByStatus(Status.InProgress)
        .FromCache(CacheManager.GetProfile().WithGroup("MyTasks"));

    return View(tasks);
}

public ActionResult UpdateTask(Task task)
{
    db.Task.Attach(task, true);
    db.SubmitChanges();

    // since we made an update to the tasks table, we expire the MyTasks cache
    CacheManager.InvalidateGroup("MyTasks");
}

PLinqO supports the notion of query batching, using a feature called futures, which allow you to take several different queries and send them to the database in a single remote call. This can dramatically reduce the number of remote calls that you make and increase your application performance significantly.

cmiiw ^_^

Upvotes: 1

ryan1234
ryan1234

Reputation: 7275

Wouldn't hurt to get familiar with group by in LINQ and aggregates (Max, Min, Count).

Something like this:

var forums = (from t in db.Threads 
      group t by t.forumid into g
      select new { forumid = g.Key, MaxDate = g.Max(d => d.ForumCreateDate) }).ToList();

Also check out this article for how to count items in a LINQ query with group by:
LINQ to SQL using GROUP BY and COUNT(DISTINCT)

LINQ aggregates: LINQ Aggregate with Sub-Aggregates

Upvotes: 0

Related Questions