Posts Tagged ‘LINQ’

Return of the PagedList

// June 15th, 2009 // 7 Comments » // C#, LINQ, MVC

It has been nearly a year since I posted an updated version of the PagedList<T> functionality originally created by Scott Guthrie and posted by Rob Conery. Since then I have used the class in a number of projects and find it indispensable.

A few days ago, Craig Stuntz reported an interesting observation: when the first page is returned, the class performs a Skip(0). Suprisingly, this is not free. With that in mind, I set out to correct that issue as well as incorporate a few changes I’ve made over the past year. The result is nearly identical to the last posted version, just a bit more readable. Additionally…
(more…)

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

PagedList Strikes Back

// July 8th, 2008 // 9 Comments » // C#

There is an updated version of the PagedList<T> code available here.

A few months ago I posted about my changes to Rob Conery’s PagedList class. Since writing that article many comments have been left about how to further improve the design, which I have since incorporated into a new, further improved PagedList class. For those who aren’t familiar, the PagedList class allows scenarios such as the following:

   1: public void ListProducts( int pageIndex )
   2: {
   3:     int pageSize = 10;
   4:     var products = productRepository.GetAllProducts()
   5:         .ToPagedList( pageIndex, pageSize );
   6:     return View( products );
   7: }

(more…)

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

Rob Conery’s PagedList Class (Updated)

// April 8th, 2008 // 13 Comments » // C#

NOTE:
A new, improved version of this class is now available at:

http://www.squaredroot.com/post/2008/07/08/PagedList-Strikes-Back.aspx

Robert Muehsig has posted a great user control for the MVC framework that adds pagination links to the bottom of a paged list. In it he used a slightly customized version of Rob Conery’s PagedList class that Rob was kind enough to post way back when the first CTP was released. This reminded me that I should probably post the version I have customized, as I think it makes it a bit easier to use and maintain. I’ve included the code below.

   1: using System;
   2: using System.Linq;
   3:  
   4: namespace System.Collections.Generic
   5: {
   6:  
   7:     public interface IPagedList
   8:     {
   9:         int TotalPages { get; }
  10:         int TotalCount { get; }
  11:         int PageIndex { get; }
  12:         int PageSize { get; }
  13:         bool HasPreviousPage { get; }
  14:         bool HasNextPage { get; }
  15:         bool IsFirstPage { get; }
  16:         bool IsLastPage { get; }
  17:     }
  18:  
  19:     public class PagedList<T> : List<T>, IPagedList
  20:     {
  21:  
  22:         public PagedList( IEnumerable<T> source, int index, int pageSize )
  23:         {
  24:  
  25:             //### set source to blank list if source is null to prevent exceptions
  26:             if( source == null )
  27:                 source = new List<T>();
  28:  
  29:             //### set properties
  30:             this.TotalCount = source.Count();
  31:             this.PageSize = pageSize;
  32:             this.PageIndex = index;
  33:             if( this.TotalCount > 0 )
  34:                 this.TotalPages = (int)Math.Ceiling( (double)this.TotalCount / (double)this.PageSize );
  35:             else
  36:                 this.TotalPages = 0;
  37:             this.HasPreviousPage = ( this.PageIndex > 1 );
  38:             this.HasNextPage = ( this.PageIndex < this.TotalPages );
  39:             this.IsFirstPage = ( this.PageIndex == 1 );
  40:             this.IsLastPage = ( this.PageIndex == this.TotalPages );
  41:  
  42:             //### argument checking
  43:             if( index < 1 || index > this.TotalPages )
  44:                 throw new ArgumentOutOfRangeException( "PageIndex out of range." );
  45:             if( pageSize < 1 )
  46:                 throw new ArgumentOutOfRangeException( "PageSize cannot be less than 1." );
  47:  
  48:             //### add items to internal list
  49:             if( this.TotalCount > 0 )
  50:                 this.AddRange( source.Skip( ( index - 1 ) * pageSize ).Take( pageSize ).ToList() );
  51:  
  52:         }
  53:  
  54:         public int TotalPages { get; private set; }
  55:         public int TotalCount { get; private set; }
  56:         public int PageIndex { get; private set; }
  57:         public int PageSize { get; private set; }
  58:         public bool HasPreviousPage { get; private set; }
  59:         public bool HasNextPage { get; private set; }
  60:         public bool IsFirstPage { get; private set; }
  61:         public bool IsLastPage { get; private set; }
  62:  
  63:     }
  64:  
  65:     public static class Pagination
  66:     {
  67:         public static PagedList<T> ToPagedList<T>( this IEnumerable<T> source, int index, int pageSize )
  68:         {
  69:             return new PagedList<T>( source, index, pageSize );
  70:         }
  71:     }
  72:  
  73: }

Changes from Rob’s version:

  • Added a "TotalPages" property.

    If you’re going to loop through each of the pages to display page navigation, you’ll obviously need this.
  • Changed "IsPreviousPage" to "HasPreviousPage".

    It just sounds better.
  • Changed "IsNextPage" to "HasNextPage".

    See above.
  • Added a "IsFirstPage" property.

    The opposite way of using the above two properties. I prefer this way, but kept the original way for backwards compatibility (except the naming).
  • Added a "IsLastPage" property.

    See above.
  • Changed the first constructor to accept IEnumerable<T> rather than IQueryable<T>.

    I’m not exactly sure why Rob originally made it IQueryable. I’m aware that by passing an IQueryable (LINQ) object to this constructor you’ll avoid retrieving the entire set (only taking the results needed), but since IQueryable inherits from IEnumerable everything should be hunky-dory. He probably had a reason and I’m going to wind up breaking all of my stuff, but IEnumerable is just so much handier. =)
  • Removed the second constructor.

    The second constructor took List<T>, which is unnecessary after changing the first constructor to accept IEnumerable.
  • Cleaned up property declarations a bit.

    Mainly to make the page a bit shorter, but also to prevent the multiple calculations that could happen in the original. Also the original allowed the changing of certain properties after an instance was created, which would put the instance into an inconsistent state.
  • Added argument checking and handled a few exception scenarios more gracefully.

    Trying to make debugging a bit friendlier.
  • Removed the second extension method that didn’t specify a pageSize.

    I don’t really think that baking in an extension method that sets pageSize to 10 is a good idea, I’d prefer pageSize to be explicitly set elsewhere by the calling code.
  • Moved the code to the "System.Collections.Generic" namespace.

    I’m sure a lot of you are breaking out in a cold sweat to see me putting something into a System.* namespace, but I kind of feel like this is something that the .Net team just "forgot". =) Move it wherever makes you comfortable.

Please note that I took many of these ideas from the commentary below Rob’s original post. I’m sure many of you are using something similar, but I thought it would be useful to get something posted online that is a bit more fleshed out than the original example.

Thanks for the great work Rob & Robert!

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

Storing LINQ Objects in SQL-Based Session State

// January 30th, 2008 // 3 Comments » // LINQ

Scott Hanselman recently posted about various options you have for session storage while using ASP.Net. In the comments of his post I brough up an issue I recently encountered at work (where we use SQL Server session state):

LINQ-To-Sql generated objects are not marked [Serializable] and cannot be stored in out-of-process session storage.

To get around this I have whipped up the following helper class which will serialize LINQ-To-Sql objects if you set the DataContext's Serializable property to "Unidirectional".

Note: The following class does not current work for storing List<X> where X is a LINQ-To-Sql object. I'll be working to resolve that sometime later this week.

   1: using System;
   2: using System.IO;
   3: using System.Runtime.Serialization;
   4: using System.Text;
   5: using System.Xml;
   6: using System.Web;
   7: 
   8: namespace SquaredRoot.Helper
   9: {
  10:   public class SessionHelper
  11:   {
  12: 
  13:     private SessionHelper(){}
  14: 
  15:         public static T Get<T>( string key ) where T : class
  16:         {
  17: 
  18:             object o = HttpContext.Current.Session[key];
  19:             if( o == null )
  20:                 return default( T );
  21: 
  22:             if( o.GetType() == typeof( string ) && typeof( T ) != typeof( string ) )
  23:             {
  24:                 string s = o as string;
  25:                 Stream stream = new MemoryStream( Encoding.Unicode.GetBytes(s) );
  26:                 XmlDictionaryReader xml = XmlDictionaryReader.CreateTextReader( stream, new XmlDictionaryReaderQuotas() );
  27:                 DataContractSerializer dcs = new DataContractSerializer( typeof( T ) );
  28:                 T t = (T)dcs.ReadObject( xml, true );
  29:                 xml.Close();
  30:                 return t;
  31:             }
  32:             else
  33:                 return o as T;
  34: 
  35:         }
  36: 
  37:         private static bool HasClassAttribute( object o, Type attribute, bool inherit )
  38:         {
  39:             return ( o.GetType().GetCustomAttributes( attribute, inherit ).Length > 0 );
  40:         }
  41: 
  42:         public static void Set( string key, object item )
  43:         {
  44: 
  45:             object value = item;
  46:             if( HasClassAttribute( item, typeof(DataContractAttribute), false )
  47:         && !HasClassAttribute( item, typeof(SerializableAttribute), false ) )
  48:             {
  49:                 DataContractSerializer dcs = new DataContractSerializer(item.GetType());
  50:                 StringBuilder sb = new StringBuilder();
  51:                 XmlWriter writer = XmlWriter.Create(sb);
  52:                 dcs.WriteObject( writer, item );
  53:                 writer.Close();
  54:                 value = sb.ToString();
  55:             }
  56: 
  57:             if( HttpContext.Current.Session[key] == null )
  58:         HttpContext.Current.Session.Add( key, value );
  59:             else
  60:         HttpContext.Current.Session[key] = value;
  61: 
  62:         }
  63: 
  64:     }
  65: }
Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

Add/Update/Delete with LINQPad

// December 5th, 2007 // 6 Comments » // LINQ

Danny Douglass recently posted about LINQPad, a query tool that understands LINQ. I spent some time today using it and am very impressed. It did take a while, however, to look through the numerous sample scripts and figure out exactly how to insert/update/delete data, so I thought I would share what I've found. The biggest changes between working in LINQ-To-SQL in a Visual Studio 2008 project and writing a LINQ query in LINQPad are the lack of a DataContext, the difference between C# Expressions and C# Statements, and the addition of a Dump command.

No Data Context: When you need to interact with a database via LINQ in a Visual Studio 2008 project, you do so by creating a LINQ-To-SQL DBML file that generates a data context for you. This data context is in charge of maintaining your database connection and is what you use to submit changes to the database. Because there is no data context readily available to you (and no .dbml file) in LINQPad, the way you go about this is slightly different. There is a globally-scoped subroutine, "SubmitChanges()," that should be called whenever you wish to commit an action to the database.

C# Expressions vs. C# Statements: By default LINQPad opens in "C# Expression" mode. In this mode you can type a simple query like "from r in Regions select r" and run it to see the results. As far as I can tell there is no way to insert/update/delete data in this mode. By switching to "C# Statement(s)" mode you are able to declare variables, control flow statements, and reference objects; this is the mode you need to be in to insert/update/delete data. To enter "C# Statement(s)" mode, select it from the "Type" drop-down box at the top of the window as show below:

LINQPad

object.Dump(): When you write a query in "C# Expression" mode the result of that query is automatically rendered to the Results frame (shown below). Because "C# Statement(s)" mode gives you the capability to run many queries in one execute, displaying the results of those queries must be manually invoked. To do so simply call the ".Dump()" method on the resultset of the query you want to display. ".Dump()" is implemented as an extension method available on all objects, so whether you are retrieving a single object, a list of objects, or an anonymous type the Dump method will be available to display your data.

LINQPad-Results

Below I have included examples of several ways to query data, as well as an example each for inserting, updating, and deleting data.

    1 // select (LINQ Syntax)

    2 var regions =

    3     from r in Regions

    4     where r.RegionID > 0

    5     select r;

    6 regions.Dump();

    7 

    8 // insert

    9 Region newRegion = new Region()

   10 {

   11     RegionID = 99,

   12     RegionDescription = "Lorem ipsum…"

   13 };

   14 Regions.InsertOnSubmit( newRegion );

   15 SubmitChanges();

   16 

   17 // select (LINQ Syntax, no temp variable)

   18 (from r in Regions

   19     where r.RegionID > 0

   20     select r).Dump();

   21 

   22 // update

   23 Region region =

   24     (from r in Regions

   25         where r.RegionID == 99

   26         select r).Single();

   27 region.RegionDescription = "…dolor sit amet…";

   28 SubmitChanges();

   29 

   30 // select (.Where Lambda expression)

   31 ( Regions.Where( r => r.RegionID > 0 ) ).Dump();

   32 

   33 // delete

   34 Region removeRegion =

   35     Regions.Where( r => r.RegionID == 99 ).Single();

   36 Regions.DeleteOnSubmit( removeRegion );

   37 SubmitChanges();

   38 

   39 // select (Regions & Territories, joined and

   40 //        combined by anonymous type)

   41 (from r in Regions

   42     join t in Territories

   43         on r.RegionID equals t.RegionID

   44     select new{

   45         Region = r,

   46         Territory = t

   47     }).Dump();

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com