Skip to main content

Features of the Code Generator

I just updated my code generator to optionally generate validation attributes. This simple change includes App.config file entries for all check boxes, and a new checkbox for "Validation" - aka validation attibute generation. While I was making this change, I realized that I really need to pass a CodeGenerationContext object to the DbTable, DbField and ModelGenerator classes. The requester can populate the context, and pass it to the code generator.

Anyway, enough about the code, let's talk about the templates. I made a simple template this weekend to generate a DataGridView column array, suitable for databinding. I'm sure my new template will need some tweaks to handle Foreign Keys better (it currently just displays them as TextBox). Let's look at a template.

##FILENAME:PR_${table.GetClassName()}_Insert.sql
##
## Generate a SQL stored procedure to insert a record into the
## specified table and return the newly created primary key
##
## FUTURE: The generated procedure can support journalling, etc.
##

#set ($procname = "PR_${table.GetClassName()}_Insert")

#set ($prefix = '')
#set ($lbrace = '[')
#set ($rbrace = ']')
#set ($paramtypes = '')
#set ($nfields = '')
#set ($params = '')
#foreach($f in $fields)
#if (!$f.IsPrimaryKey())
#set ($nfields = "$nfields$prefix$lbrace${f.Column_Name}$rbrace")
#set ($paramtypes = "$paramtypes$prefix@p${f.Column_Name} ${f.GetSqlType()}")
#set ($params = "$params$prefix@p${f.Column_Name}")
#set ($prefix = ', ')
#end
#end

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[$procname]')
AND type in (N'P', N'PC'))
BEGIN
DROP PROCEDURE [dbo].[$procname];
END;

## we are executing the stored procedure as a string, so double up on any quotes.
EXEC dbo.sp_executesql @statement = N'
CREATE PROCEDURE [dbo].[$procname]
($paramtypes)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].$lbrace${table}$rbrace ($nfields)
VALUES ($params) ;
SELECT @@IDENTITY AS ID ;
END;
'
GO

The lines with ## are comments, but line 1 is actually a second template, and will be used to generate the output filename. Below the comment section is a block of variable assignments that set a field list $nfields, parameter list with data types $paramtypes, and un-typed parameter list $params. Once we have these variables, it is quite easy to write our SQL stored procedure. You might notice that the stored procedure is preceded by a conditional DROP PROCEDURE statement, and the new CREATE PROCEDURE is executed as a string. Have you noticed that we have not mentioned "Active Record" yet? If a person knows NVelocity well enough, they can create a template to do some amazing things. The only requirement is that the template can derive it's functions, properties, method calls, etc. from the basic table name, primary key, foreign key, SQL type and .Net data type information.

If you are looking for inspiration, try writing an active record partial that contains a custom Find(string field1, string field2, etc.) with NULL values ignored in the internal Criterion list. For extra credit, break out the criterion array building into a Find_Query(...) method that can also be used to retrieve a count instead of finding the records.

Comments

Jim Reineri said…
Thanks for the good work. I appreciate your efforts. It is saving me some significant time. I am continuing to use it and will forward any ideas/code that I create.
One question, why is "System.ComponentModel" in the using statement?
Jim Reineri said…
I figured out that ComponentModel is require for the "NotifyPropertyChanged" method.

I have been having some trouble with generated code from "ModelTemplate.vm". For my situation I had to change line 87 in "ModelTemplate.vm" to:
${f.GetPrivateVariableName()} = (value.${f.GetPropertyName}ID > 0) ? value : null;.

Line 243 in "DbFieldInfo.cs" is the fourth line in the code block of the "GetInEqualityTest()" method. It had to be changed to"

return "(" + GetPrivateVariableName() + " == null) || (value == null) || (value." + GetPropertyName() + "ID != " + GetPrivateVariableName() + "." + GetPropertyName()+ "ID)";"
.

These line numbers are from a Subversion checkout that I did today.

Without these changes I was getting property names of "ID" which did not work for me.

I have not addressed the issue, but it seems that the line that is generated by the "GetInEquality()" method should be generated in the template code. Is there a specific reason that you did it in the generater code, or is that just how it evolved?
Roy Tate said…
I will look closer, but this is another case where I relied on my table and field naming convention. Foreign keys mapped as ... USER.ADDRESS_ID relates to ADDRESS.ID. This information is now exposed in the DbFieldInfo class. I can quickly change that template to use the INFORMATION_SCHEMA.

Popular posts from this blog

Castle ActiveRecord with DetachedCriteria

My current development environment is Visual Studio Express C# Edition (read that as free ), Castle ActiveRecord's latest svn trunk(usually within a few days), and NHibernate svn trunk. As of NHibernate version 1.2.0, there is a very cool new class out there ... DetachedCriteria. This class lets you set all of your Castle relational attributes like BelongsTo, HasMany, etc. as lazy fetch, and over-ride this for searches, reports, or anytime you know ahead of time that you will be touching the related classes by calling detachedCriteria.SetFetchMode(..., FetchEnum.Eager). As a good netizen, I have tried to contribute to NHibernate and Castle ActiveRecord even if only in the smallest of ways . Oh yeah, I tried mapping to a SQL VIEW, and it worked GREAT! I received a comment after my last post, indicating that there is a better way, and I am sure of it, but the view guaranteed that I only have one database request for my dataset. NHibernate was wanting to re-fetch my missing as

Castle ActiveRecord with Criteria and Alias

Update May 25, 2007: ActiveRecord now supports DetachedCriteria, which eliminates the need for the SlicedFindAll that I wrote below. It is nice when a library moves to add support for such commonly needed functions. So in summary, use Detached criteria instead of the code below. It is still a nice example of using NHibernate sessions. I have a history log, where each history record "belongs to" a service record. I have to treat this as a child-to-parent join, since some children are orphans. I wanted to use the FindAll(Criteria), but I wanted the option to have optional criteria, orders and aliases. My solution was to create an ARAlias class to represent an Associated Entity and an alias, and then build an ARBusinessBase class with the following method: public static T[] SlicedFindAll(int firstResult, int maxResults, Order[] orders, ARAlias[] aliases, params ICriterion[] criteria) { IList list = null; ISessionFactoryHolder holder = ActiveRecordMediator.GetSessionF

Castle ActiveRecord calling a Stored Procedure

Update: I have contributed patch AR-156 that allows full integration of Insert, Update and Delete to ActiveRecord models . If you've been reading my blog lately, you know that I have been seriously testing the Castle ActiveRecord framework out. I really love it, but I have an existing Microsoft SQL Server database with many stored procedures in it. I have tested the ActiveRecord model out, and I am sure that I will learn enough to be able to use it for standard CRUD (create, read, update, delete aka. insert, select, update, delete) functionality. BUT ... If I really want to integrate with my existing billing procedures, etc, I will have to be able to call stored procedures. I have taken two approaches ... write the ARHelper.ExecuteNonQuery(targetType, dmlString) method that gets a connection for the supplied type, executes dmlString, and closes it. write the ARHelper.RegisterCustomMapping(targetType, xmlString) method that allows me to add mappings that refer to my auto-gener