Justin Toth's Blog

Justin is a web developer living in Maryland

Sorting, paging, and filtering with Linq to SQL

clock June 19, 2009 22:56 by author Justin Toth

We get a requirement from a client where we need to make a grid. It starts out as a simple grid, but then the client calls for sorting, then paging, then in-grid filtering of the results. We've all had to code up a solution for this in the past... We didn't want to load all of the results into C# and then do the sorting, paging, and filtering, as this would be too much data to store in memory. So instead we write a sproc that handles this for us, like so:

if exists (select * from syscomments where id = object_id ('dbo.User_GetUsers'))
begin
drop proc dbo.User_GetUsers
end

go

create proc dbo.User_GetUsers
@FirstName varchar(50),
@LastName varchar(50),
@LoginName varchar(50),
@SortColumn varchar(50),
@SortDirection varchar(50),
@PageSize int,
@PageNumber int,
@TotalRecordCount int out
as

--create temp table to store results.
declare @Users table
(
 RowNum int identity(1,1),
 UserID int,
 FirstName varchar(50),
 LastName varchar(50),
 LoginName varchar(50)
)

--insert into temp table the sorted and filtered results (before paging.)
insert into @Users
(
 UserID,
 FirstName,
 LastName,
 LoginName
)
select
 UserID,
 FirstName,
 LastName,
 LoginName
from
dbo.User (nolock)
--search filters
where
(len(@FirstName) = 0 or upper(FirstName) like '%' + upper(@FirstName) + '%')
and (len(@LastName) = 0 or upper(LastName) like '%' + upper(@LastName) + '%')
and (len(@LoginName) = 0 or upper(LoginName) like '%' + upper(@LoginName) + '%')
--sorting
order by
case when @SortColumn = 'FirstName' and @SortDirection = 'ASC' then FirstName end asc,
case when @SortColumn = 'FirstName' and @SortDirection = 'DESC' then FirstName end desc,
case when @SortColumn = 'LastName' and @SortDirection = 'ASC' then LastName end asc,
case when @SortColumn = 'LastName' and @SortDirection = 'DESC' then LastName end desc,
case when @SortColumn = 'LoginName' and @SortDirection = 'ASC' then LoginName end asc,
case when @SortColumn = 'LoginName' and @SortDirection = 'DESC' then LoginName end desc

--get total record count.
set @TotalRecordCount = (select count(1) from @Users)

--figure out where to page.
declare @StartRowNum int
set @StartRowNum = ((@PageNumber - 1) * @PageSize) + 1
declare @EndRowNum int
set @EndRowNum = @StartRowNum + @PageSize

--get paged results from temp table.
select *
from @Users
where RowNum >= @StartRowNum
and RowNum < @EndRowNum
order by RowNum asc

go

This code worked great in the past, but we're in the 3.5 era and the 4.0 era is fast approaching, we can do better!! In comes Linq to SQL to the rescue.

First we need to do a little prep work. We're going to create a UserSearch object, which will store our search parameters. Next, we're going to create a UserView object, which will declare what we want to return for each user. Do we need to do this? No, however it's a good practice to only return what you need, especially since this is an ajax grid (did I mention that??) and we're going to convert the results to JSON to pass to it. Lastly, we need to create an extension method that will help us with sorting later on. It is beyond the scope of the article how this extension method works (in other words I don't know how it works yet!)

public class UserSearch
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string LoginName { get; set; }
    }

public class UserView
    {
        public int UserID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string LoginName { get; set; }
        public int TotalRecordCount { get; set; } 
    }

 public static class DynamicOrderBy
    {
        public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source,
            string sortColumn, string sortDirection) where TEntity : class
        {
            string command = sortDirection == "ASC" ? "OrderBy" : "OrderByDescending";
            var type = typeof(TEntity);
            var property = type.GetProperty(sortColumn);
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var orderByExpression = Expression.Lambda(propertyAccess, parameter);
            var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
                                   source.Expression, Expression.Quote(orderByExpression));
            return source.Provider.CreateQuery<TEntity>(resultExpression);
        }
    }

Now that our prep work is done, it's time to write our method, which will handle sorting, paging, and filtering, and return the results as JSON for use in our ajax grid.

public string GetPageJSON(string sortColumn, string sortDirection, int pageNumber, int pageSize, UserSearch search)
        {
            var query = from u in db.User
                        select new UserView
                        {
                            UserID = u.UserID,
                            FirstName = u.FirstName,
                            LastName = u.LastName,
                            LoginName = u.LoginName
                        };
            //searching.
            if (search.FirstName.Length > 0) query = query.Where(u => u.FirstName.Contains(search.FirstName));
            if (search.LastName.Length > 0) query = query.Where(u => u.LastName.Contains(search.LastName));
            if (search.LoginName.Length > 0) query = query.Where(u => u.LoginName.Contains(search.LoginName));
            //sorting.
            query = query.OrderBy(sortColumn, sortDirection);
            //get total record count.
            int totalRecordCount = query.Count();
            //paging.
            query = query.Skip((pageNumber - 1) * pageSize).Take(pageSize);
            //set total record count.
            var list = query.ToList();
            if (list.Count > 0)
            {
                list[0].TotalRecordCount = totalRecordCount;
            }
            //return json.
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            return serializer.Serialize(list);
        }

Make sure you add a reference to System.Web.Extensions and then you can hit control + . on JavaScriptSerializer to get its namespace (System.Web.Script.Serialization). As you can see, we first declare what we want to return from our user table. Next we do our filtering with some simple where conditions. After that, we sort using our custom extension method. Next we get the total number of records so that we can let our pager know, then we get one page of data, using the nifty Linq to SQL Skip and Take methods. We then call .ToList() on our query, which for the first time, calls the database, runs the query, and returns the results. We then set our TotalRecordCount in our results and lastly serialize our results into JSON.

So that's it! Once the JSON has been passed into the javascript using whatever method you like, maybe a web service or ASP.NET Ajax Extensions pagemethods, you can then say:

var results = eval(json);

The results object will then contain the List of business objects, pretty nifty huh??



Foreign Keys and Eager Loading in the Entity Framework

clock May 30, 2009 12:47 by author Justin Toth

Let's say that you create 2 database tables, a User table and a Company table. A user can be part of a company so the User table has a CompanyId foreign key. Then you go ahead and create your Entity Framework Model, which recognizes the foreign key and creates the relationship between the business objects. You create a registration form that allows the saving of a user, and you have a Company dropdown on there where the user can pick which company they belong to. When they click save, you want to not only create the User but set up the correct relation between that user and the selected company. If you're new to the Entity Framework, your first attempt will probably look something like this:

using (MyEntities dataContext = new MyEntities())
            {
                User user = new User();
  user.Company = new Company();
  user.Company.CompanyId = 1;//TODO: get selected company id
  dataContext.AddToUser(user);
  dataContext.SaveChanges();
            }

However this won't work, it'll most likely throw an exception about how you need to attach an existing key rather than trying to add a new one. The problem is you're trying to add a user but also are trying to add the company, when the company already exists. The solution is to add a reference to the company rather than a new company object:

using (MyEntities dataContext = new MyEntities())
            {
                User user = new User();
  user.CompanyReference.EntityKey = new EntityKey("MyEntities.Company", "CompanyId", 1);
  dataContext.AddToUser(user);
  dataContext.SaveChanges();
            }

This will correctly add the company reference to the user table.

Now let's say that you have a grid where you want to view a list of all users and which companies they belong to. Again, your first stab might look like this:

using (MyEntities dataContext = new MyEntities())
            {
                return dataContext.User.ToList();
            }

If you view sql profiler while running this, you'll see that it only grabs the record from the User table, it doesn't know to grab the associated Company record as well. To fix this, we use the Include statement to implement "eager loading", which will also load the company object related to the user:

using (MyEntities dataContext = new MyEntities())
            {
                return dataContext.User.Include("Company").ToList();
            }

This will fill the User.Company object and you're good to go...



About the author

Justin

Justin is a senior .NET developer who has been working with .NET since 2003. His personal website is located at http://tothsolutions.com.

Sign in