To be clear, the generator would create seven files:
- Basic entity class (e.g. Group.vb)
- Collection class (GroupCollection.vb)
- A DAL class (e.g. GroupDAL.vb)
- Insert stored procedure
- Update stored procedure
- Delete stored procedure
- Select stored procedure to get a single record by primary key(s)
The first step was to add more smarts to the templates so that the generator could handle some difficult situations (multiple primary keys, non-identity single primary keys) and to add in some additional base functionality (collection sorting, object caching, OnValidate checking that the IList interface is not being abused).
For a good article on strongly typed Collection classes check out this one on Builder AU.
Next we looked at the range of customisation we already had in existing data layer classes and ways of separating this out so that we could add ways of getting data that meet specific business requirements without polluting our generated classes. The answer of course was to use partial classes.
Our DAL classes now start like this:
1 Imports System
2 Imports System.Data.SqlClient
3 Imports System.Data
4 Imports System.Text
5 Imports System.Collections
6 Imports Elcom.Common.Data
7
8 ''' <summary>
9 ''' Data Access layer (DAL) class for Group entity objects.
10 ''' </summary>
11 ''' <remarks>
12 ''' Auto-generated by Elcom Code Generator at 4/01/2008 4:01:03 PM.
13 ''' </remarks>
14 Partial Public Class GroupDAL
15 Inherits BaseDAL
16
17 ''' <summary>
18 ''' Constructor method is declared and made private to ensure
19 ''' class is a singleton.
20 ''' </summary>
21 Private Sub New()
22 End Sub
23
24 ''' <summary>
25 ''' Public 'Instance' method allows external objects to access
26 ''' the current singleton instance.
27 ''' </summary>
28 ''' <returns>
29 ''' Returns a reference to the single GroupDAL object
30 ''' in memory.
31 ''' </returns>
32 Public Shared ReadOnly Property Instance() As GroupDAL
33 Get
34 If _instance Is Nothing Then
35 SyncLock GetType(GroupDAL)
36 If _instance Is Nothing Then
37 _instance = new GroupDAL()
38 End If
39 End SyncLock
40 End If
41 Instance = _instance
42 End Get
43 End Property
The DAL additional class looks like this:
1 Imports System
2 Imports System.Data.SqlClient
3 Imports System.Data
4 Imports System.Text
5 Imports System.Collections
6 Imports Elcom.Common.Data
7
8 ''' <summary>
9 ''' Data Access Layer class for Group
10 ''' </summary>
11 ''' <remarks>
12 ''' Old code brought in as Additional partial class by Angus
13 ''' on 4/01/2008 for CM 5.5.
14 ''' </remarks>
15 Partial Public Class GroupDAL
16
Unfortunately we were still left with a major issue, some of the customisation we had made involved making changes to the way we saved some data items (e.g. encrypting users' passwords before saving them to the database), or having additional read-only data items that were not instantiated in the database table but properly belonged to the entity. The partial class solution allowed us to add extra properties, but did not enable us to override the basic Save() method, so what were we to do?
One option was to implement an alternative Save() method that differed either via parameter signature or name, but that would leave the incorrect one hanging around. Another trade off would be to customise the generated class file and just live with the necessity to not generate that file (problematic and prone to human error).
The answer turned out to be partial methods. The VB team blogged about these last year:
“Partial methods enable code generators to write extremely flexible code by creating a lot of "hooks", where developers can provide their own custom functionality that integrates into the "boiler plate" code created by the generator. Because the hooks are optimized away if they aren't used, no performance penalty is introduced by defining them. This is really useful if generated code needs to be used in high performance scenarios. For example, partial methods are used by the DLINQ designer for exactly this purpose. Developers wishing to invoke custom code on their data objects when properties are set can do so without requiring all users of DLINQ to suffer performance problems.”So we ended up adding in calls to partial methods in all of our property setting code:
14
15 Public Property intGroupID() As Int32
16 Get
17 Return _intGroupID
18 End Get
19 Set (ByVal value As Int32)
20 ' partial event method to allow insertion of
21 ' behaviour using partial class
22 OnSettingintGroupID(value)
23 _intGroupID = value
24 End Set
25 End Property
26
We also ended up adding pre/post save calls to partial methods to allow us to intervene and tweak either data to be saved or to handle other behaviour post the save operation. An example of a typical save operation is below:
70
71 ''' <summary>
72 ''' Public method allows external objects to save changes to a Group object.
73 ''' </summary>
74 Public Sub Save(ByRef objGroup As [Group], ByVal blnForUpdate As Boolean)
75 Dim parameters As ArrayList = New ArrayList()
76 Dim sproc As String
77
78 parameters.Add(New SqlParameter("@strName",objGroup.strName))
79 parameters.Add(New SqlParameter("@intURLID",objGroup.intURLID))
80 parameters.Add(New SqlParameter("@blnAllowAddressUpdateInEshop",objGroup.blnAllowAddressUpdateInEshop))
81 parameters.Add(New SqlParameter("@blnShowAdminInTopMenu",objGroup.blnShowAdminInTopMenu))
82 parameters.Add(New SqlParameter("@strLDAPGroupName",objGroup.strLDAPGroupName))
83 parameters.Add(New SqlParameter("@strLDAPIdentifier",objGroup.strLDAPIdentifier))
84 parameters.Add(New SqlParameter("@blnSystemsAdministrator",objGroup.blnSystemsAdministrator))
85 parameters.Add(New SqlParameter("@blnRestrictAccessBaseFol",objGroup.blnRestrictAccessBaseFol))
86 parameters.Add(New SqlParameter("@blnRestrictArticleOptAttr",objGroup.blnRestrictArticleOptAttr))
87 parameters.Add(New SqlParameter("@blnRestrictFolderOptAttr",objGroup.blnRestrictFolderOptAttr))
88 parameters.Add(New SqlParameter("@blnForceSelectTemplate",objGroup.blnForceSelectTemplate))
89 parameters.Add(New SqlParameter("@blnRestrictChangeLayout",objGroup.blnRestrictChangeLayout))
90 parameters.Add(New SqlParameter("@blnRestrictAddElements",objGroup.blnRestrictAddElements))
91 parameters.Add(New SqlParameter("@blnRestrictCreateTempFromArt",objGroup.blnRestrictCreateTempFromArt))
92 parameters.Add(New SqlParameter("@blnAllowEventStatusUpdate",objGroup.blnAllowEventStatusUpdate))
93 parameters.Add(New SqlParameter("@blnAllowVenueLimitUpdate",objGroup.blnAllowVenueLimitUpdate))
94 parameters.Add(New SqlParameter("@blnAllowAddAttendee",objGroup.blnAllowAddAttendee))
95 parameters.Add(New SqlParameter("@blnAllowDeleteAttendee",objGroup.blnAllowDeleteAttendee))
96 parameters.Add(New SqlParameter("@blnAllowOnbeHalfRegistration",objGroup.blnAllowOnbeHalfRegistration))
97 parameters.Add(New SqlParameter("@blnAllowMoveEvent",objGroup.blnAllowMoveEvent))
98
99 ' partial event method to allow insertion of behaviour using partial class
100 PreSave(parameters, objGroup, blnForUpdate)
101
102 If blnForUpdate And objGroup.intGroupID > 0 Then
103 parameters.Add(New SqlParameter("@intGroupID",objGroup.intGroupID))
104 sproc = "proc_GroupUpdate"
105 ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure, sproc, parameters)
106 Else
107 sproc = "proc_GroupInsert"
108
109 Dim intNewKey as Integer
110 intNewKey = CType(ExecuteScalar(ConnectionString, CommandType.StoredProcedure, sproc, parameters), Integer)
111 objGroup.intGroupID = intNewKey
112 End If
113
114 ' partial event method to allow insertion of behaviour using partial class
115 PostSave(objGroup, blnForUpdate)
116
117 ' refresh the cached version of this object
118 Dim cacheName As String = String.Format("Group:{0}", "" + objGroup.intGroupID.ToString() + "" )
119 RemoveFromCache(cacheName)
120 AddToCache(cacheName, objGroup)
121 End Sub
122
The end result for us was a much more flexible system of generated code which meant that we can now fix problems with out data classes in the templates directly and then just re-generate the code and replace the files in our project folders and commit to our source code repository (it helps that we use Subversion via TortoiseSVN).
Note: Thanks to Guy Burstein for taking the time to migrate the Copy Source as HTML addin to VS2008, even though it seems to have problems with my my colour scheme. Mazal Tov with that new MS job too!
No comments:
Post a Comment