ActiveRecord Codesmith Template Updated

I have made a bunch of changes to the ActiveRecord template.  Below is the list of all of the changes. 

Codesmith.zip (18.14 kb)

Changes:

Lookup table Enum and SQL Generation

I have always used enumerations for all of my lookup tables so that I don't have to hard code the lookup ID value in code.  Until now I had generated these by hand but I finally got around to creating the ability to generate the enum and the sql insert statement.  The template expects an integer primary key and a single string based column.  You can have other values in the table but the template will ignore them. 

The SQL script can either be generated with each table in it's own or as a single file.  The template checks to see if the primary key is an identity column and adds the identity_insert tag.  As well the generate script checks to see if the lookup value based on it's primary key is already in the table before trying to add it.  This allows you to run the script multiple times wthout any issues.

Views as ActiveRecord Classes

I added the ability to specify views to be used to generate ActiveRecord classes.  In order to properly generate the class you have to add an extended property to the view called PrimaryKey and specify the column that is the primary key. 

Self-Referencing Tables

I changed the code to look for self-referencing tables and append List to the end of the property.

Ability to not generate Tests

Added a flag to turn off the test code generation.  All the test classes are is a placeholder anyway which so folks didn't want.

Output Structure

I changed the output structure to better organize the code based on the type of database entity (enum, table, view, sql, etc)

PropertyNames structure for use with nHibernate Expression classes

One thing that I have always disliked about using the nHibernate expression classes was that the properties are string values and cannot be compile time checked.  This unfortuantely leads to only finding errors at runtime for the expression queries.  To solve this problem, I implement the suggestions from the Summer of Nhibernate Screencast (http://www.summerofnhibernate.com ) to create a static class for each ActiveRecord class with the name of each property as a string.  I also add a string value for all of the BelongsTo relationships to get return the RelationshipName.ID since you need that in many instances.  As well I created an ActiveRecordTableNames.cs file that has a listing of all of the tables names.

 


SEVDNUG Presentation: ActiveRecord

On Thursday I gave an quick overview of Castle ActiveRecord (http://www.castleproject.org ) as part of the Southeast Valley .NET User Group (http://www.sevdnug.org ).  I thought the presentation went fairly well.  It is hard to really dig into too much code in 15 minutes, so I mainly concentrated on the features of ActiveRecord.  Below is the links to the code that present.  I used nHibernate 1.2 and ActiveRecord RC3. 

AR Code Demo.zip (1.13 mb)

  • ARCodeDemo.DataObjects: ActiveRecord classes
  • ARCodeDemo.Tests: nUnit Tests
  • ARCodeDemo.Web: Sample Web Application. 
  • Dependencies:  the required dll's to reference for the projects.

Database.zip (3.65 mb)

  • Database used for the sample code
  • Need to just re-attached the database
  • App.Config/Web.Config expects a user of SEVDNUG_User with password of SEVDNUG_User

nHibernate: Great screencast series

Stepen Bohlen is running a great screencast series called Summer of NHibernate .  So far he has 6 sessions posted.  If you are new to NHibernate it is a great way to get up to speed quickly. 


Latest ActiveRecord Codesmith Template

I have made some changes to the Codesmith template that I created for ActiveRecord.  One of the major changes made was for nullable database fields to use the c# nullable type (e.g. int?, DateTime?).

 Download: ActiveRecord.zip (13.15 kb)


ActiveRecord, ObjectDataSource, and GridView Custom Paging


Updated 4/25/2008: Add sample source code

Problem: How to efficiently implement paging in GridView using an ObjectDataSource

For small amounts of data the built-in paging for the GridView worked fairly well and takes practically no work to do it. However, once you get to a decent amount of data, it becomes incredible slow and provide for a horrible user experience. The problem is that basically the GridView and ObjectDataSource are returning all of the records each time from the database and only showing the ones for the current page. This is a very inefficient use of bandwidth.

Solution:

To get around this problem, you can implement paging with the ObjectDataSource. When you implement paging with the ObjectDataSource, it automatically passes in the values for the startIndex and maximumRows to your select statement and needs to have a SelectCount method defined so that it can get the total number.

Sounds really easy right? Once you figure out how to get the RowCount for the query it is. For simple queries you can use the CountQuery class in ActiveRecord. However for more complicated queries that use a DetachedCriteria, there is currently no overload for CountQuery that take a DetachedCriteria. Hopefully this will be in a future release since there was a patch submitted on March 5th for this feature.

Luckily both NHibernate and ActiveRecord are open source so I was able to look thru their code and tests. In ActiveRecord it is well structure with a directory in their ActiveRecord Framework project called Queries.

In my research it became clear that a Projection query was what I needed but I still was not sure

So I finally found a ScalarProjectionQuery class that took a DetachCriteria. Below is an example call to the method. query is a DetachedCriteria

ScalarProjectionQuery<WorkOrder, int> proj = new ScalarProjectionQuery<WorkOrder, int>(Projections.RowCount(), query);

Int count = proj.Execute();

To implement the actual paging for the ObjectDataSource , I used the ActiveRecord SlicedFindAll method for my model along with the Sql 2005 Dialect.

Gotcha:

With turning on paging for the ObjectDataSource is that it automatically passes the startIndex and maximumRows parameters into your defined Select method as the last 2 parameters and if you use the designer to configure the ObjectDataSource, it adds those parameters to the parameter collection defined in the html code. This in turn causes the startIndex and maximumRows to be passed twice into the SelectMethod .

Sample Code:

ObjectDataSource Definition:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="FindUserBlogs"
        TypeName="MyClassThatMyFindMethodIsIn" OldValuesParameterFormatString="original_{0}"
        OnSelecting="ObjectDataSource1_Selecting" EnableViewState="False" EnablePaging="True"
        SelectCountMethod=" FindUserBlogsCount">
        <SelectParameters>
            <asp:Parameter Name="userID" Type="Int32" />
        </SelectParameters>
    </asp:ObjectDataSource>

ActiveRecord Method Definition:

public static Blog[]  FindUserBlogs (int userID, int startRowIndex, int maximumRows) {
     // use SlicedFindAll to start at a specific row and limit the results }

public static int FindUserBlogsCount (int userID) {
    //query is a DetachCriteria    
     ScalarProjectionQuery<Blog, int> proj = 
             new ScalarProjectionQuery<Blog, int>(Projections.RowCount(), query);
      return proj.Execute();
}

Gotcha with gridview custom paging and disabled viewstate

I ran into a problem with a GridView with custom paging throwing a arithmetic overflow error when I click on the Last pager button. It turns out for whatever reason, Microsoft stores the GridView's total page count in viewstate instead of the control state like the rest of the GridView properties. Luckily it is really easy to work around this issue. All you need to do is change the Last pager button's command argument in the code-behind to the GridView.PageCount property and the error goes away.


NHibernate, ActiveRecord and SQL2005

Recently I needed to do paging for a GridView using an ObjectDataSource and ActiveRecord. I wanted to use the SQL2k5 ROW_NUMBER function that I blogged about in this post to do real paging in sql but everything I had seen on using NHibernate with SQl2k5 said to use the SQL2k dialect. There was no indication that a SQL2k5 dialect even existed. However, during a search for another NHibernate question, I found a SQL2k5 dialect that someone had written to do exactly what I wanted. The code was available off the NHibernate contrib. site so I figured it worked and I could integrate it into my solution. As I was looking to figure out how dialects are integrated, I found that the dialect is already part of the NHibernate 1.2 release but none of the documents are updated to reflect this. To use the dialect, you just need to set <add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2005Dialect"/> in your ActiveRecord configuration.


Update ActiveRecord CodeSmith Template

I have updated my ActiveRecord CodeSmith template to provide support for one-to-one relationships and fixed a bug with the naming of the many-to-many private variable/public property in the generate code.

Download ActiveRecord.zip


nHibernate Expression Notes

Expression.Sql: used to add sql to the where clause. To do stuff like MyTableName.Column you would use {alias}.Column and nHibernate would replace {alias} with the actual alias that it used for the MyTableName table.

Expression.Ge: This is used for greater than or equal to.


Binding ActiveRecord to a DataGrid in Read Only Mode

As I have been playing with ActiveRecord (http://www.castleproject.org ) one of the things I need to do was to bind a ActiveRecord class to a datagrid to view data. I did not need to worry about insert/update/delete in the grid.  So you may be saying big deal, this is easy. You are correct if you are just binding primitive types (int, string, etc). However, if your ActiveRecord class has any complex objects such as other ActiveRecord class then you can not by default get to the data by just setting the DataSource to the results of the FindAll() method.

To solve the databinding problem, I found ObjectDataSource worked really well.  On the datagrid I wanted to display a collection of WorkOrders and the Tool.ToolName value as a column. To get the workorders, I used the ActiveRecord FindAll() method.

ActiveRecord Class:

 

using System;

using System.Collections.Generic;

using System.Text;

using Castle.ActiveRecord;

 

namespace MyARDemo

{

    [ActiveRecord("WorkOrders")]

    [System.ComponentModel.DataObject]

    public partial class WorkOrder : DomainObject<WorkOrder>

    {

        private DateTime _createdOn;

        private string _createdOnShift;

        private string _createdOnWorkWeek;

        private string _description;

        private DateTime _lastUpdatedOn;

        private int _id;

        private Tool _tool;

        private User _createdByUser;

        private User _lastUpdatedByUser;

 

        public WorkOrder()

        {

        }

 

 

        [PrimaryKey(PrimaryKeyType.Native, "WorkOrderID", Access = PropertyAccess.FieldLowercaseUnderscore)]

        public int ID

        {

            get { return _id; }

            set { _id = value; }

        }

 

        [Property(NotNull = true, Column = "CreatedOn")]

        public DateTime CreatedOn

        {

            get { return _createdOn; }

            set { _createdOn = value; }

        }

 

        [Property(NotNull = true, Length = 2, Column = "CreatedOnShift")]

        public string CreatedOnShift

        {

            get { return _createdOnShift; }

            set { _createdOnShift = value; }

        }

 

        [Property(NotNull = true, Length = 6, Column = "CreatedOnWorkWeek")]

        public string CreatedOnWorkWeek

        {

            get { return _createdOnWorkWeek; }

            set { _createdOnWorkWeek = value; }

        }

 

        [Property(NotNull = true, SqlType = "VARCHAR(MAX)", Column = "Description")]

        public string Description

        {

            get { return _description; }

            set { _description = value; }

        }

 

        [Property(NotNull = true, Column = "LastUpdatedOn")]

        public DateTime LastUpdatedOn

        {

            get { return _lastUpdatedOn; }

            set { _lastUpdatedOn = value; }

        }

 

        [BelongsTo("ToolID")]

        public Tool Tool

        {

            get { return _tool; }

            set { _tool = value; }

        }

 

        [BelongsTo("CreatedByUserID")]

        public User CreatedByUser

        {

            get { return _createdByUser; }

            set { _createdByUser = value; }

        }

 

        [BelongsTo("LastUpdatedByUserID")]

        public User LastUpdatedByUser

        {

            get { return _lastUpdatedByUser; }

            set { _lastUpdatedByUser = value; }

        }

    }

}

 

using System;

using System.Collections.Generic;

using System.Text;

using Castle.ActiveRecord;

 

namespace MyARDemo

{

    [ActiveRecord("Tools")]

    public partial class Tool : DomainObject<Tool>

    {

        private bool _customized;

        private bool _deleted;

        private int _lastUpdatedByID;

        private DateTime _lastUpdatedOn;

        private string _toolName;

        private int _id;

        private IList<WorkOrder> _workOrders;

 

        public Tool()

        {

            _workOrders = new List<WorkOrder>();

        }

 

 

        [PrimaryKey(PrimaryKeyType.Native, "ToolID", Access = PropertyAccess.FieldLowercaseUnderscore)]

        public int ID

        {

            get { return _id; }

            set { _id = value; }

        }

 

        [Property(NotNull = true, Column = "Customized")]

        public bool Customized

        {

            get { return _customized; }

            set { _customized = value; }

        }

 

        [Property(NotNull = true, Column = "Deleted")]

        public bool Deleted

        {

            get { return _deleted; }

            set { _deleted = value; }

        }

 

        [Property(NotNull = true, Column = "LastUpdatedByID")]

        public int LastUpdatedByID

        {

            get { return _lastUpdatedByID; }

            set { _lastUpdatedByID = value; }

        }

 

        [Property(NotNull = true, Column = "LastUpdatedOn")]

        public DateTime LastUpdatedOn

        {

            get { return _lastUpdatedOn; }

            set { _lastUpdatedOn = value; }

        }

 

        [Property(NotNull = true, Length = 50, Column = "ToolName")]

        public string ToolName

        {

            get { return _toolName; }

            set { _toolName = value; }

        }

 

        [HasMany(typeof(WorkOrder), Lazy = true, Table = "WorkOrders", ColumnKey="ToolID", Inverse=true)]

        public IList<WorkOrder> WorkOrders

        {

            get { return _workOrders; }

            set { _workOrders = value; }

        }

    }

}

 

WebForm Code:

On the DataGrid, I left AutoGeneration on for the primitive types and for the complex type Tool.ToolName, I added a Templated Column.

<Columns>

     <asp:TemplateField HeaderText="Tool">

         <ItemTemplate>

             <asp:Label ID="Label1" runat="server" Text='<%# Eval("Tool.ToolName") %>'></asp:Label>

         </ItemTemplate>

     </asp:TemplateField>

</Columns>

 

I know that there is a ARDataSource project in the AR Contrib but it would not compile for me and since I did not need the more than a ReadOnly view I did not work to fix the errors. I may ultimately end up writing my own data source control. There are several articles that talk about how to do it and even some code from other ORM data source controls.

Nikhil Kothari's Weblog : Data Source Controls Summary: http://www.nikhilk.net/DataSourceControlsSummary.aspx

Vault of Thoughts - .NET Blog - How To Use MyObjectDataSource: http://vaultofthoughts.net/HowToUseMyObjectDataSource.aspx

NHibernateDataSource: A DataSourceControl for ASP.NET 2.0 - The Code Project - ASP.NET: http://www.codeproject.com/useritems/NHibernateDataSource.asp

Paul Wilson's .NET Blog : Introducing the WilsonORMapper DataSource Control: http://weblogs.asp.net/pwilson/archive/2006/05/17/446919.aspx