I Need A Dip

Tuesday, January 31, 2006

Sorting Generics with IComparer<T>

I'm a huge fan of Generics.

I've replaced all of my code generated custom colletion classes with List<T>. But along with my simple implementation of CollectionBase I built in some custom sorting for my objects.

I know the correct way would be to implement IComparer with all my objects and sort that way, but the truth is I very rarely sort anything (only when displaying data t the user). I like just running my CodeSmith template against all 25 of my tables to generate some nice objects that take care of all the C.R.U.D. functions, along with the stored procs to make them work.

Meanwhile, back at the ranch, I need to sort my List<T>'s by a particular property value. Here was my solution:
   1:  public enum GenericComparerSortDirection { Asc, Desc } 
   3:  public class GenericComparer<T> : IComparer<T> 
   4:  {    
   5:      private string propertyName; 
   6:      private GenericComparerSortDirection theDirection; 
   8:      public GenericComparer(string propertyName, GenericComparerSortDirection eSortDirection) 
   9:      { 
  10:          this.propertyName = propertyName; 
  11:          this.theDirection = eSortDirection; 
  12:      } 
  14:      public int Compare(T x, T y) 
  15:      { 
  16:          // gets the value of the x property 
  17:          PropertyInfo property = x.GetType().GetProperty(propertyName); 
  18:          object valueOfX = property.GetValue(x, null); 
  20:          // gets the value of the y property 
  21:          property = y.GetType().GetProperty(propertyName); 
  22:          object valueOfY = property.GetValue(y, null); 
  24:          // now make the comparsion 
  25:          if (this.theDirection == GenericComparerSortDirection.Asc) 
  26:              return ((IComparable)valueOfX).CompareTo(valueOfY); 
  27:          else 
  28:              return ((IComparable)valueOfY).CompareTo(valueOfX); 
  29:      }
  30:  }

Now you can sort a generic list of custom objects like this:
   1:  List<MyClass> lstSorted = getABunchOfObjects(); 
   2:  lstSorted.Sort(new GenericComparer<MyClass>("PropertyName", GenericComparerSortDirection.Asc));

I understand that this isn't the most efficient way of sorting (using reflection) but like I said, I don't use this method often, and it works perfectly for my situation.

NOTE: Notice line 17 and 21 use GetType() rather than typeof(T) in case the properties aren't provided by T itself, but by a class derived from T.
Thanks to a suggestion from Jon Skeet

This implementation was inspired by my problem and the following post: http://www.dotnetjunkies.com/Tutorial/5091F698-EF8B-436B-A345-AFBEF18CE229.dcik

Sunday, January 29, 2006

Singleton pattern on the web

I would love some feed back so please don't hold back.

I have a webservice that is responsible for formatting tons of information that is then queried by other applications (agents) that utilize it (the info packets).

All this "formatting" and logic I am talking about needs to be done in a centralized place and accessed by the agents. That is why we chose to go with a web service. This formatting and logic also needs to be done and held in memory so the agents don't have to wait for it. So we decided to implement a sort of singleton pattern. What do you guys think?

   1:  public class PacketManager 
   2:  { 
   3:      private static object lockObject = new Object(); 
   4:      private const string CACHE_MANAGER = "Mngr:PacketManager"; 
   5:      private List<InfoPacket> _preparedPackets; 
   7:      private PacketManager() { initFunction(); } 
   9:      public static InfoPacket GetInfoPacket() { return Instance.getInfoPacket(); } 
  11:      public InfoPacket getInfoPacket() { ......some code ..... return this._preparedPackets[0]; } 
  13:      private static PacketManager Instance 
  14:      { 
  15:          get {
  16:              lock(lockObject){
  18:                  PacketManager oManager;
  20:                  object o = System.Web.HttpContext.Current.Application.Get(CACHE_MANAGER);
  21:                  if(o==null){
  22:                      oManager = new PacketManager();
  23:                      System.Web.HttpContext.Current.Application.Add(CACHE_MANAGER,oManager);
  24:                  } else oManager = (PacketManager)o;
  26:                  return oManager;                
  27:              }
  29:          }
  30:      }
  31:  } 

Here is more information on the Singleton Pattern: http://www.yoda.arachsys.com/csharp/singleton.html

Friday, January 27, 2006

sp_xml_preparedocument - Date problem

I'm no word-smith, so here was my problem:

My dataset looked like the following when it's serialized.
   1:  <NewDataSet>
   2:    <xs:schema id="NewDataSet" xmlns=""
   3:  xmlns:xs="http://www.w3.org/2001/XMLSchema"
   4:  xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
   5:      <xs:element name="NewDataSet" msdata:IsDataSet="true"
   6:  msdata:UseCurrentLocale="true">
   7:        <xs:complexType>
   8:          <xs:choice minOccurs="0" maxOccurs="unbounded">
   9:            <xs:element name="List_101">
  10:              <xs:complexType>
  11:                <xs:attribute name="InvoiceID" type="xs:int" />
  12:                <xs:attribute name="Email" type="xs:string" />
  13:                <xs:attribute name="DomainID" type="xs:int" />
  14:                <xs:attribute name="SourceID" type="xs:int" />
  15:                <xs:attribute name="TypeEnum" type="xs:int" />
  16:                <xs:attribute name="ImportID" type="xs:int" />
  17:                <xs:attribute name="DateStamp" type="xs:dateTime" />
  18:              </xs:complexType>
  19:            </xs:element>
  20:          </xs:choice>
  21:        </xs:complexType>
  22:      </xs:element>
  23:    </xs:schema>
  24:    <List_101 InvoiceID="55" Email="joeshmoe@joe.com" DomainID="2421"
  25:  SourceID="12" TypeEnum="2" ImportID="20"
  26:  DateStamp="2005-09-18T17:39:00-07:00" />
  27:  </NewDataSet>

My stored procedure looked like the following:
   1:  CREATE Proc xml_List_101
   2:  @listdata nText
   3:  AS
   4:  Declare @hDoc int
   5:  exec sp_xml_preparedocument @hDoc OUTPUT, @listdata
   6:  Insert Into List_101
   7:  Select 
   8:   [InvoiceID], [Email], [DomainID], [SourceID], [TypeEnum], [ImportID], [DateStamp]
   9:  From 
  10:  OPENXML(@hDoc, '/NewDataSet/List_101')
  11:  WITH (
  12:  [InvoiceID] Integer,[Email] varchar(150),[DomainID] Integer,[SourceID] Integer,[TypeEnum] Integer,[ImportID] Integer,[DateStamp] DateTime)
  13:  Exec sp_xml_removedocument @hDoc

First off, my dataset was a lot larger then the one I am showing you. There were many more "rows".

I was trying to insert the XML data into a table called List_101, and that table has a 'DateStamp' column that is a datetime type. I kept getting the "Syntax error converting datetime from character string." because of the DateTime format from DataSet.WriteXML(). (because of the -07:00 offset)

So I ended up using the CAST(left(DateStamp,10) AS datetime) so it was in the correct format and changed the WITH to DateStamp varchar(10). The sp_xml_preparedocument doesn't blow up anymore and the data gets successfuly inserted into List_101 with the proper DateStamp (which is a datetime in the table) because it converts the varchar(10) on its own.

Final working stored proc:
   1:  CREATE Proc xml_List_101
   2:  @listdata nText
   3:  AS
   5:  Declare @hDoc int
   6:  exec sp_xml_preparedocument @hDoc OUTPUT, @listdata
   8:  Insert Into List_101
   9:  Select 
  10:   [EmailID], [Email], [DomainID], [SourceID], [TypeEnum], [ImportID], CAST(LEFT(DateStamp],10) AS datetime)From 
  11:  OPENXML(@hDoc, '/NewDataSet/List_101')
  12:  WITH (
  13:  [EmailID] Integer,[Email] varchar(150),[DomainID] Integer,[SourceID] Integer,[TypeEnum] Integer,[ImportID] Integer,[DateStamp] varchar(10)) 
  14:  Exec sp_xml_removedocument @hDoc

Hopefully this helps the next person.