- Posted by Justin on August 31, 2008
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.
- Posted by justin on February 26, 2008
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();
}
- Posted by justin on February 7, 2007
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