Wednesday, February 15, 2006

A neat way of getting around some OR mapping issues with temporary Domain classes.

I recently discovered a nice pattern for dealing with self joins, and many-to-many joins in a Data Access Layer. Here's the problem. In your domain design you have an class with a collection of itself. This is a common pattern for tree structures.
class MyEntity
{
   MyEntityCollection _myEntities;

   MyEntityCollection MyEntities
   {
       get{ retrun _myEntities; }
   }
}
To do a similar thing in a relational database one would normally have two tables, the entity table and a table to map the relationships between the entities.
create table MyEntity(
   id int
)

create table MyEntityMap(
   from_id int,
   to_id int
)
Now, how do we recreate object map in memory from the relational data. My solution is to use a temporary domain class to represent the map table. It has a reference to the entities on both ends of the relationship and a method to wire them up.
class MyEntityMap
{
   MyEntity _fromEntity;
   MyEntity _toEntity;

   public void WireUp()
   {
       _fromEntity.MyEntities.Add(_toEntity);
   }
}
The MyEntityMapCollection class has a method WireUp() that calls WireUp() on every MyEntityMap object.
class MyEntityMapCollection
{
   ArrayList _myEntityList = new ArrayList();

   ...

   public void WireUp()
   {
      foreach(MyEntity myEntity in _myEntityList)
      {
         myEntity.WireUp();
      }
   }
}
Then in our data access layer we can simply get all the MyEntity objects from the database, storing pointers to them in an object store, then get all the MyEntityMap objects. Each MyEntityMap object gets the pointers for the correct objects from the object store. The last task is to call WireUp() on the MyEntityMapCollection. Here's the DAL code.
public MyEntity GetEntityMap()
{
   MyEntityCollection myEntities = SelectAllEntitiesStoredProcedureWrapper();
   MyEntityMapCollection myMap = SelectAllEntityMapStoredProcedureWrapper();
   myMap.WireUp();

   // some method to get the root MyEntity
   return GetRootEntity(myEntities);
}

Friday, February 03, 2006

Seriously hard core debugging

This is a great blog: If broken it is, fix it you should. She goes really deep into dot net debugging tools and there's some great advice about common application problems. One of my favorite bug bears is ASP Session State. I've seen quite a few applications that fall into the trap of overusing the Session State and then having huge issues with scalability. One such was a mission critical £100 million billing and provisioning system for one of the worlds top cable companies. The original developers had put everything in session state and it simply wouldn't scale. This was classic ASP so they ended up putting an expensive cisco sticky router in front of it, but even that didn't solve all the problems. I remember having a lengthy discussion at the time with someone who said that all these problems will be solved with ASP.NET because you can move the session state handling off to SQL Server. My point was that if you're writing an enterprise application with a SQL server back end, why not maintain your application state by restoring your domain model directly via your Data acces layer on each page hit. That way you'll have real control over state and you wont hit all the scalability issues that Tess goes into in her blog.

Wednesday, February 01, 2006

A VS Macro to count projects classes and functions

I was playing around writing Macros for VS 2003 recently, exploring the CodeModel API and came up with this little solution profiler that counts the number of projects, classes and functions in the current solution. Microsoft has a load of VS 2005 automation samples you can download here
Option Explicit On 

Imports EnvDTE
Imports System.Diagnostics

Public Module SolutionProfiler

    Private Const newline = vbLf
    Private m_textDocument As TextDocument
    Private m_editPoint As EditPoint
    Private m_indent As Integer = 0
    Private m_writeOn As Boolean = False

    Private m_numberOfProjects As Integer = 0
    Private m_numberOfClasses As Integer = 0
    Private m_numberOfFunctions As Integer = 0
    Private m_linesOfCode As Integer = 0

    Public Sub ProfileSolution()

        DTE.ItemOperations.NewFile("General\Text File")
        m_textDocument = DTE.ActiveDocument.Object("TextDocument")
        m_editPoint = m_textDocument.StartPoint.CreateEditPoint()

        Dim solution As Solution = DTE.Solution

        For Each project As Project In solution.Projects
            WriteLine(project.Name)
            WriteProject(project)
        Next

        WriteSummary()

    End Sub

    Private Sub WriteSummary()

        m_writeOn = True
        WriteLine(String.Format("Number of Projects  = {0}", m_numberOfProjects))
        WriteLine(String.Format("Number of Classes   = {0}", m_numberOfClasses))
        WriteLine(String.Format("Number of Functions = {0}", m_numberOfFunctions))
        WriteLine(String.Format("Number of LOK       = {0}", m_linesOfCode))

    End Sub

    Private Sub WriteProject(ByVal project As Project)

        m_numberOfProjects += 1

        Dim codeModel As CodeModel = project.CodeModel
        If Not codeModel Is Nothing Then
            TabIn()
            For Each childElement As CodeElement In codeModel.CodeElements
                If TypeOf childElement Is CodeClass Then
                    WriteClass(childElement)
                End If
                If TypeOf childElement Is CodeEnum Then
                    WriteLine("Enum: " & childElement.FullName)
                End If
                If TypeOf childElement Is CodeInterface Then
                    WriteLine("Interface: " & childElement.FullName)
                End If
            Next
            TabOut()
        End If

    End Sub

    Private Sub WriteClass(ByVal codeClass As CodeClass)

        If codeClass Is Nothing Then
            Return
        End If

        m_numberOfClasses += 1

        WriteLine("Class: " & codeClass.FullName)

        TabIn()
        WriteLine("Properties")
        TabIn()
        For Each member As CodeElement In codeClass.Members
            If TypeOf member Is CodeProperty Then
                WriteProperties(member)
            End If
        Next
        TabOut()
        WriteLine("Functions")
        TabIn()
        For Each member As CodeElement In codeClass.Members
            If TypeOf member Is CodeFunction Then
                WriteFunction(member)
            End If
        Next
        TabOut()
        TabOut()

    End Sub

    Private Sub WriteProperties(ByVal codeProperty As CodeProperty)

        If codeProperty Is Nothing Then
            Return
        End If

        WriteLine(codeProperty.Name)

    End Sub

    Private Sub WriteFunction(ByVal codeFunction As CodeFunction)

        If codeFunction Is Nothing Then
            Return
        End If

        m_numberOfFunctions += 1
        WriteLine(codeFunction.Name)

        Dim startPoint As TextPoint = codeFunction.StartPoint
        Dim endPoint As TextPoint = codeFunction.EndPoint

        Dim lines As Integer = endPoint.Line - startPoint.Line
        m_linesOfCode += lines

    End Sub

    Private Sub TabIn()
        m_indent += 1
    End Sub

    Private Sub TabOut()
        m_indent -= 1
    End Sub

    Private Sub WriteLine(ByVal line As String)

        If m_writeOn Then
            m_editPoint.Insert(New String(vbTab, m_indent) & line & newline)
        End If

    End Sub

End Module