Technology with opinion

Thursday, November 19, 2009

Mapping Date & Time fields With NHibernate

Let's say you have an entity or POCO that you have mapped from NHibernate (or any ORM) and the database has separated out the Date & Time values into separate fields.  Your best best is to map these Date & Time fields into properties (DateTime for Date and TimeSpan for Time).  Then create a helper property in your POCO to add them together.  I'd like to add that I'm not a fan of separating these sorts of things in the database, but some people do this for some reason:

HBM:


POCO

In my case either of these could be null (yes another great idea) which means I had to handle the nullability however this illustrates at least in worse case scenerio what will happen.  Date & Time data types are available in SQL Server 2008 and work with NHibernate 2.1 with .Net 2.0 or above.

Monday, November 16, 2009

Associating Users & Logins in SQL Server after Database Restore

Often times when you restore a database in SQL Server from one server to the next the Logins will become disassociated with the User a specific database.  We ran into this today and another software engineer found a good script from a guy he knows:



Executing this script should find unassociated logins and reassociate them with the users in the current database

Friday, November 06, 2009

CI Done Simple with CruiseControl.Net + MSBuild

If you want to setup Continuous Integration the learning curve for automated builds can be moderate for some. CruiseControl.Net (CCNet) allows you to integrate with automated build technologies such as NAnt, MSBuild, etc.  The advantage of this is that you can specify the Visual Studio Solution so that MSBuild knows the references and source code to build.

This example assumes using TFS for source code control, which definitely isn't my favorite SCM tool but odds are if you want to keep it simple and you're not using NAnt then you're probably in a shop that uses TFS.

Sunday, October 25, 2009

Alt.Net Google Reader Bundle

Google Reader is a great way to subscribe to different RSS feeds because it enables certain social networking features on top of them.  If you are "Sharing" with other users then you will see what other users in your network liked and their comments.  It also makes it very easy to send links to people via email.

I created an ALT.Net Google Reader bundles a while back that I try to keep updated with different ALT.Net blogs that I subscribe to.  To subscribe to this bundle click the link below and then click the link "Subscribe".
http://www.google.com/reader/bundle/user/03066432769507324653/bundle/Alt.Net

You can expand the feeds to see what you are subscribing to and even remove certain feeds after you add the bundle.  If you have an ALT.Net blog and would like your blog added to this bundle just send me a message on Twitter or leave a comment here.

Tuesday, September 29, 2009

Practical use for NUNit 2.5's [TestCase]

Since NUnit 2.5, NUnit has supported a concept of Test Cases.  Briefly a test case to NUnit is a test which runs with a different set of parameters and can expect a different set of results.  This has the potential to minimize the amount of code required to write new test methods.

To this point I've been using standard [Test] attributes and since seeing this attribute in the documentation I've been thinking about uses for it.  Now that I've started using [TestCase] I've thought of some other uses for it cutting my test fixtures down to 1/2 or less the size previously.

Below is an simple example of the code without the [TestCase] using the traditional [Test] attribute:


The above code can be trimmed down to one function using the [TestCase] attribute:



The other beautiful thing about [TestCase]s is that it collapses each test case underneath a node which actually looks cleaner:



A couple of notes on the code above.  The compiler (C# here) will not allow you to use object initializers as a parameter to the [TestCase] so you cannot create a test function Validate(object myUser) and do something like below.



This is because the compiler will only allow constants in attributes, however if you really need something like this, NUnit allows a work around. This is documented here.

Sunday, September 27, 2009

Houston TechFest Samples

Houston TechFest was a lot of fun, I attended some very interesting sessions and had the opportunity to present on the topic of NHibernate.  I have uploaded the samples and they are downloadable by clicking here.  The presentation is available here.

Wednesday, September 23, 2009

Unit Testing - Mocking to test Edge Cases with NHibernate

Sometimes in code you want to test edge cases, some of these can be difficult to test even with integration tests because of database constraints, etc.  This may be a business key or unique identifier (that's not a primary key)- may be enforced or not.  The key is you make an assumption that in code you want to test for.  Mocking dependencies is the key here, to create behavior that would not otherwise be possible.

Unlike the pattern illustrated by Ayende or that I added onto here, I wanted NHibernate's SessionFactory and Session objects to return data that I could not return from the database because of unique index constraints.

In this scenerio I'm returning data from the database that matches certain values and it should always return a list that contains either 0 or 1 objects otherwise it throws an exception:


Problem is that integration tests are unlikely to uncover this error with normal data and end user testing won't uncover it either.  This is simple, but I want to make certain that the exception is correctly thrown.

The only tricky part really is that after mocking the ISessionFactory & ISession objects you need to also mock each ICriteria object returned from the Session object.  So with Rhino Mocks after telling it to expect a call to CreateCriteria that returns an ICriteria, we also need to create expectations for this result calling Add and its result calling list.  This is because fluent interfaces use method chaining which makes this part a little tedious:


This allows us to now test what would actually happen if more than one Animal objects were returned.  NHibernate makes this easy because there are interfaces for pretty much every object.

FYI: this code uses Rhino Mocks, NBuilder, NUnit & NHibernate

Friday, September 11, 2009

NHibernate, Spring.Net & Dependency Injection

Inverson of Control (IoC) is a very powerful concept. The basic premise of IoC is to invert elements of your programming paradigm for flexibility and extensibility. Most procedural or OO code the linkages are hard code. Most data access code, take DAO for instance, need a handle to a connection object. Fortunately if you are using a good persistence framework like NHibernate you don't have to directly deal with the ADO Connection object as it handles that for your, however you need to efficiently manage an NHibernate Session.
How to do so is very important in ASP.Net applciations as you may have dozens of request per second and if you handle this poorly your application performance will suffer. Handle it well and you will often have better performance than straight ADO.Net applications.
Let's take two simple application layers UI and Data Layer. Our data layer contains our DAOs (data access objects) which perform direct interaction with our database and of course our UI contains all necessary user interface related code.
UI Code

The public setter that we declare here exists for dependency injection (DI), this will allow Spring.Net to set (or inject) our dependencies for us. We configure Spring to set this pages dependencies in our configuration file like the following:

This section is telling Spring that the page (Applications/Default.aspx) has a property named GenericAppSectionDao which needs to be set to a reference of an object named GenericAppSectionDao

At your applications first run (first request for a website) Spring will create all of these objects in memory for you and set them up (unless you configure them differently of course). This requires an upstart cost but saves performance with each request after the first.

Next we need to tell Spring how to create this object GenericAppSectionDao:


The first object element creates an abstract object so that Spring knows to create before the objects that are dependent on it. Next we tell Spring to create an instance of a generic class called Dao that provides basic Dao functions and we tell Spring that it's dependent upon BaseDao (again so that Spring knows to create this object after it creates its parent). The generic Dao class looks like the following:



Lastly is BaseDao which provides a public setter to set the SessionFactory with a public Getter to get the current session. This enables SessionScopes which are a good idea for web apps. BaseDao code is below


You could obviously perform the same exact methods with any IoC framework (Castle or anything else). The only difference should be the XML files. Dependency Injection (DI) is a form of IoC and reinforces best practices for writing proper unit tests because you can isolate each layer you are testing and mock its dependencies. It also facilitates AOP so you can do all sorts of stuff (transactions, logging, etc) between application layers automatically.

Wednesday, August 26, 2009

Working Around DLL Hell in .Net

Assembly Bindings with different Public Key Tokens

There was a little fiasco started when log4net accidentally generated a new public key token for log4net from version 1.2.9.0 to 1.2.10.0.  This became evident to me when I tried to do an assembly redirect in the .config file for NAnt so that it would use the newer version of log4net.  You get the public key token from any .Net assembly by executing the following command on it:

If you perforrm this against log4net 1.2.9 & 1.2.10 you will get b32731d11ce58905 & 1b44e1d426115821.  Therefore if you are executing a custom task that requires log4net (or who's dependencies require log4net) .Net will not let you redirect the two version because as far as Microsoft is concerend, they are not two different version of the same thing, they are different assemblies altogether.  However since they are different assemblies technically the work around is to rename one of the new assembly (1.2.10 in this scenerio) filename to log4net-1.2.10.dll.  Next in the NAnt.exe.config add the following within the <configuration> element

This will force .Net to load the new assembly alongside the old one thus avoiding any compatibility issues, bypassing DLL hell for good.

Tuesday, August 18, 2009

.Net Programmer Toolbox Summer '09

So for the Summer of 2009 this is the software that is in this .Net programmer's toolbox:
($ denotes how expensive it is. FOSS - free open source software)
  1. Visual Studio 2008 with SP1 - $$$
  2. NHibernate 2.1 - ORM framework - FOSS
  3. Spring Framework 1.3 - IoC/DI framework - FOSS
  4. NAnt 0.86 - .Net build tool - FOSS
  5. NUnit 2.5 - .Net Unit Testing framework - FOSS
  6. Telerik AJAX Controls 2009 - great inexpensive web controls for .Net.  They also have a fairly liberal license. - $$
  7. Resharper - inexpensive Visual Studio add-on for refactoring and overall enhancing your IDE - $
  8. Sqlite ADO.Net Provider - lightweight database great for Unit Testing or client-side database - FOSS
  9. StyleCop & StyleCopExt - Microsoft's source code analysis tool & my extentions - free
  10. MyGeneration 1.3 - .Net code generation tool - FOSS
  11. NBuilder - tired of writing hundreds of lines of code to create objects in memory for unit testing? This is for you - FOSS
  12. Rhino Mocks - best mocking framework for .Net. Supports generics - FOSS

Monday, August 17, 2009

Centering a DIV within HTML

I'm by far from an HTML or DIV expert but I try to stay compliant.  One of the change to HTML 4.01 standard requires that tables no longer be used for layouts but instead only be used for grid type data.  Putting that debate aside working with DIVs is a little tricky if you have grown accustom to HTML tables.  Until recently, what has long evaded me has been centering content within HTML without a TABLE or the deprecated CENTER tag until now.

There is a trick using negative margins that does it nicely and seems to work well in FireFox & IE.  First you will need to figure out a fixed size for your DIV (a good size that you content will fit within).  Let say your DIV is 300 pixels wide, next you multiply it times -.5 which will give you your left margin of -150 pixels.  You will always position this centered DIV using 50% left.  This will center your HTML content inside the DIV horizontally.  Perform the same technique with height & top margin in order to center vertically.

This will look like the following:

See Also: W3C Standard on HTML 4.01 Tables

Tables should not be used purely as a means to layout document content as this may present problems when rendering to non-visual media. Additionally, when used with graphics, these tables may force users to scroll horizontally to view a table designed on a system with a larger display. To minimize these problems, authors should use style sheets to control layout rather than tables.

Tuesday, August 04, 2009

Using Spring.Net Proxy Factory with NHibernate

The latest version of NHibernate (2.1) discouples the framework from the dependency of relying on Castle (think DynamicProxy) therefore allowing you to plug in any framework for you are proxying.

Overview: NHibernate uses proxies to facilitate lazy loading, this proxy mechanism allows NHibernate to intercept the calls to your properties and classes thus delaying a call to the database that may not be needed (Lazy Loading).

After you have downloaded NHibernate 2.1 and extracted the contents you will notice a new folder titled "Required_For_LazyLoading". Inside this folder are 3 separate folders containing binaries for the following proxies: Castle: LinFu & Spring. Since I'm using Spring framework in the rest of my project this allows me to have one less dependency (which is a good thing). As I understand it LinFu is the fastest proxy of the bunch so if you aren't using Castle or Spring already you may want to consider it.

The problem is that the NHibernate is distributed with an older verison of Spring so we want to tell the .Net framework that it's okay to use the new version. To do so were are going to use Assembly Redirection in our App.config or Web.config. The section below belongs inside your <configuration> section:


Monday, June 15, 2009

NHibernate 101 Materials

I had the opportunity to speak at CINNUG's ORM Firestarter and present the topic on NHibernate 101.  The presentation is an overview of the NHibernate framework.  The sample app forward engineers a database based on the Hibernate mapping files.  There is also a sample web app which uses Open Session in View (OSIV) using NHibernate Session Scopes.



Tuesday, June 09, 2009

Unit Testing NHIbernate with SessionScopes

Ayende has a great article on Unit Testing NHibernate. However if are you using NHibernate Session Scopes you need to adapt it. I wanted to keep my tests simple, I want to test my DAOs and inject into them an ISessionFactory since this is where it's getting it's Session from. SessionScopes are a nice way in NHibernate not have to worry about managing your sessions, you want to control the behavior. You may end up doing this with AOP or in your web request however that's not pertinent to my test since the design of my DAOs have the Session Factory Injected.

We are reusing DAOs within Web code to integration code and the session management behavior is different in each one. I am also using the Spring.Net however this should work anywhere that you are injecting ISessionFactory and you are using SessionFactory.GetCurrentSession() to get your session.

Basically we're using the same code as in his example except we are using Rhino Mock to actually mock the SessionFactory. In addition we are exposing the SessionFactory so that you can inject it into your DAOs. We are still exposing the Session for use in your test fixture.

    public class InMemoryDatabaseTest : IDisposable
{
private static Configuration Configuration;
private MockRepository _mocks;
private static ISessionFactory _realSessionFactory;
private ISession _session;
private ISessionFactory _sessionFactory;

protected ISession Session
{
get { return _sessionFactory.GetCurrentSession(); } // simulates how DAOs are getting their Sessions
}

///
/// Gets the SessionFactory
///
public ISessionFactory SessionFactory
{
get { return _sessionFactory; }
}

public InMemoryDatabaseTest(Assembly assemblyContainingMapping)
{
_mocks = new MockRepository();

if (Configuration == null)
{
Configuration = new Configuration()
.SetProperty(NHibernate.Cfg.Environment.ReleaseConnections, "on_close")
.SetProperty(NHibernate.Cfg.Environment.Dialect, typeof(SQLiteDialect).AssemblyQualifiedName)
.SetProperty(NHibernate.Cfg.Environment.ConnectionDriver, typeof(SQLite20Driver).AssemblyQualifiedName)
.SetProperty(NHibernate.Cfg.Environment.ConnectionString, "data source=:memory:")
.SetProperty(NHibernate.Cfg.Environment.ProxyFactoryFactoryClass, typeof(DefaultProxyFactoryFactory).AssemblyQualifiedName)
.AddAssembly(assemblyContainingMapping);
_realSessionFactory = Configuration.BuildSessionFactory();
}

// Here we are mocking the Session Factory because we are using Session Scopes.
// However we want to return the same session every time
_sessionFactory = _mocks.CreateMock<ISessionFactory>();
_session = _realSessionFactory.OpenSession();
Expect.Call(_sessionFactory.GetCurrentSession()).IgnoreArguments().Return(_session).Repeat.Any();
_mocks.ReplayAll();

new SchemaExport(Configuration).Execute(true, true, false, true, _session.Connection, Console.Out);
}

public void Dispose()
{
Session.Dispose();
}
}
The Mock is the glue that makes this work so that you can adhere to the restrictions of your DAO implementation. Then you can execute the test much like Ayende does in his own, regardless of how your perform transaction management in your actual application.

    [TestFixture]
public class FelineDao_Tests : InMemoryDatabaseTest
{
public FelineDao_Tests() : base(typeof(Feline).Assembly) { }

[TestFixtureSetUp]
public void Initialize()
{
// Insert Test data
using (ITransaction tx = Session.BeginTransaction())
{
Session.Save(GetSnowLeopard());
Session.Save(GetDomesticCat());
tx.Commit();
}

Session.Clear();
}

[Test]
public void FindAll_Retrieval()
{
using (ITransaction tx = Session.BeginTransaction())
{
Dao dao = new Dao();
dao.SessionFactory = SessionFactory;
IList list = dao.FindAll();
Assert.AreEqual(2, list.Count);
tx.Commit();
}
}

[Test]
public void FindById_Retrieval_InValid_Id()
{
using (ITransaction tx = Session.BeginTransaction())
{
Dao dao = new Dao();
dao.SessionFactory = SessionFactory;
Feline o = dao.FindById(3);
Assert.IsNull(o);
tx.Commit();
}
}

[Test]
public void FindById_Retrieval_Valid_Id()
{
using (ITransaction tx = Session.BeginTransaction())
{
Dao dao = new Dao();
dao.SessionFactory = SessionFactory;
Feline o = dao.FindById(1);
Assert.IsNotNull(o);
tx.Commit();
}
}

private Feline GetSnowLeopard()
{
Feline feline = new Feline();
feline.Name = "Snow Leopard";
feline.Length = 74;
return feline;
}

private Feline GetDomesticCat()
{
Feline feline = new Feline();
feline.Name = "Domestic Cat";
feline.Length = 24;
return feline;
}
}

Props to Ayende for the original implementation of this and for his work on Rhino Mocks.

Thursday, April 30, 2009

Deriving a Date Range for a Day

Many times the database will contain data with different times and we need to get all data for one day.  In .Net this is done by:
 
Console.WriteLine("Start Date: {0}", DateTime.Now.Date); 
Console.WriteLine("End Date: {0}", DateTime.Now.AddDays(1).Date.AddMilliseconds(-1)); 

This would output: 

Start Date: 4/30/2009 12:00:00 AM
End Date: 4/30/2009 11:59:59 PM

 Let's say in NHibernate we want a criteria expression to retrieve all objects with a DateTime that falls within an entire date (regardless of time)

return Session.CreateCriteria(typeof(Person))
    .Add(Expression.Between("DateOfBirth", dateOfBirth.Date, dateOfBirth.AddDays(1).Date.AddMilliseconds(-1)))
    .List< Person >();

This would return all People that were born on the same day (regardless of the time of birth) since the Time portion of the Date are usually stored in the database.

Tuesday, April 28, 2009

Don't Blame NHibernate When It's ADO.Net

I can't count the number of times I have troubleshot an 'NHibernate' problem when it was either a severe design flaw or a logic bug.  From trying to write to views without indexes, working with tables without primary keys or simply causing ADO.Net to execute a query that gives an OutOfMemory exception before NHibernate gets the data.  NHibernate is a very flexible ORM, partially because there are a lot of poorly written databases out there and an ORM gives you a lot once you have taken the upfront cost in time to design.

When troubleshooting query issues, one of the first things I will do is enable 'show_sql' or look at the logs, then I will execute this same statement through a query tool.  If query analyzer or Toad cannot execute a query then it's not an NHibernate problem.  You can even go the next step and execute the query in ADO.Net itself to prove your point but that is usually not necessary unless you are dealing with a more persistent individual.

Some common exceptions with large results or complex queries will be ADO throwing query timeout exceptions or maybe an out of memory exception.  The first just means that the query took a long time to run, the fix for this could be optimizing your query or increasing the query timeout.  The later may be fixed by narrowing your results or the columns you are selecting if the table has a lot of columns.

But neither of them are NHibernate's fault.  More analysis and thought should be done before we just blame the first non-Microsoft product.  NHibernate has a lot of features for those situations that when even good design fails, this is why it's the premier ORM for .Net.

Thursday, April 16, 2009

NHibernate Performance Tuning

OR/M as a central part of your application architecture will require tuning over time.  Below are some tweaks which can have incremental to significant performance effects on your code.
  1. Enable 2nd level cache and have a strategy for using it for some of your classes. reference 
  2. Enable batch processing for persistence of objects.  This will allow NHibernate to send batches of your transactions (Inserts, Updates and Deletes) to the server in a batch. reference 
  3. Have a good strategy for flushing and transaction management.  Remember that if you commit a transaction that you do not need to flush the object.  reference 
  4. Make read-only classes immutable. reference 
  5. Understand lazy loading and use effectively, for instance when you may but not always need an associated object(s) reference 
  6. To bypass lazy loading understand eager fetching and use when you know you need associated object(s) and want to bypass lazy loading. reference 
  7. Remove any columns and properties that you will never or should never need or use.
  8. Using value type objects for your properties and components as opposed to reference types, when you have a choice, this will lower memory footprint.
  9. Avoid composite keys (exception legacy databases).  Composite keys are problematic for database design as well as OR/M therefore avoid them.  reference 
  10. Consider your logging strategy for production (less verbose) and usually disable show_sql in production.
Orin's NHib Profiler can also help you find improvements you can make to your configuration of NHibernate.

Wednesday, April 01, 2009

StyleCop Extensions Project

I've started a small project on Google Code for creating common StyleCop extensions.  Currently it enforces one rule that requires underscores to prefix instance variables.  If you have an idea for a new rule then create an Issue for it as an Enhancement. StyleCop Extension Project Home 

Thursday, February 26, 2009

Alt.Net Open Spaces Houston Conference

April 3rd - April 5th, 2009
Grassroots in our area have planned the first ever Open Spaces conference in Houston.  This is a very affordable ($25) and flexible conference for people to be able to attend since it starts on Friday and runs through the weekend.

What is Open Spaces?
Open space is guided by a few simple but powerful principles:
Whoever shows up is the right group. Whatever happens is the only thing that could have. Whenever it starts is the right time. When it's over, it's over.
An Open Space conference's agenda is decided upon by the conference participants during the opening of the event.
You can identify an issue relating to the theme of the conference for which you have a genuine passion and for which you will take real responsibility.
Think of powerful ideas that really grab you to the point that you will take personal responsibility to make sure that something gets done at the conference.
You may lead sessions, you may participate as an attendee, you may take responsibility for capturing proceedings, or you may just hang out in the halls and talk with interesting people.

Registration is now Open

Tuesday, February 10, 2009

Concatenating Date and Time in T-SQL

Sometimes you just want to concatenate the date section from a DateTime and the time section from another DateTime.  These could even be the new SQL Server Date or Time data types.  The easiest way I've found to do this is by combining DATEPART with DATEADD.  You get rid of the parts of each DateTime that you don't want and then add based on the lowest common denominator this being millisecond.

'ProcessedDate' = DATEADD(ms, DATEDIFF(ms, 0, [ProcessingTime]), DATEADD(dd, 0, DATEDIFF(dd, 0, [ProcessingDate])))
 

Monday, February 09, 2009

Generating Seed Data Through Select Statements

Often times you will need to generate seed data for a table such as a cross reference table when the data for this already exists in the database already.  Sometimes this is done when you decide to refactor your table design to include a cross reference table (many to many) where a one to many relationship existed previously.  If you have the seed data in your table already you can generate a host of SQL statements (usually INSERTS) for your database.

By issuing a SQL Statement like below, you need to understand the SQL Statement that you desire.  In this instance if you have a cross reference table that you need to generate between Customers and Orders you would have a desired INSERTS like below:
INSERT INTO customer_orders_xref VALUES (1, 1)
INSERT INTO customer_orders_xref VALUES (1, 2) 
INSERT INTO customer_orders_xref VALUES (3, 1)

In instances where you can have dozens or hundreds of desired SQL statements this can be tedious and error prone.  Instead if the data exists in some form in the database you can have it outputted with a simple SQL statement like the one below:

SELECT 'INSERT INTO customer_orders_xref VALUES (' + CAST(c.order_id AS VARCHAR(10)) + CAST(c.customer_id AS VARCHAR(10)) + ')'
FROM orders o
INNER JOIN customer c ON o.customer_id = c.customer_id
WHERE c.name LIKE '%Williams%'

If the first parameter was a fixed value you would just hard code it in your SQL.

SELECT 'INSERT INTO customer_orders_xref VALUES (2, ' + CAST(c.customer_id AS VARCHAR(10)) + ')'
FROM orders o
INNER JOIN customer c ON o.customer_id = c.customer_id
WHERE c.name LIKE '%Williams%'

 You could use techniques like this to generate all sorts of SQL Statements.  Let's say we added an Account Alias column (acct_alias) to the Account table and now we need to change them all to some sort of intelligible name.

SELECT 'UPDATE account SET acct_alias = ''Account# ' + c.name + ''') WHERE account_id = ' + c.account_id FROM customer 

For more complicated SQL generation you can use a code generation tool such as MyGeneration (free Open Source).

Friday, January 16, 2009

Getting started with NAnt Scripts (Automating Builds)

It doesn't take much work to setup a project to use NAnt scripts however the inevitable question is "Where do I start".  I would recommend starting by downloading the latest release 0.86 or the daily build from NAnt's website.  Next it's good to have a basic script for your assemblies and one for your ASP.Net website that you can copy and paste.

Basic .Net Assembly Build Script:
<?xml version="1.0" encoding="utf-8" ?>
<project xmlns="http://nant.sf.net/release/0.86-beta1/nant.xsd" name="Company.Portal.Domain" default="build">
  <property name="debug" value="false" />
  <property name="optimize" value="true"/>
  <property name="bin_name" value="${project::get-name()}.dll" />
  <property name="nant.settings.currentframework" value="net-2.0"/>
  <property name="output_bin" value="build\${bin_name} "/>
  <property name="build_path" value="..\build\" />
  <property name="build_bin" value="${build_path}${bin_name}"/>
  <target name="clean" description="Cleans the build folder which forces a rebuild.">
    <delete dir="build"  if="${directory::exists('build')}" />
  </target>
  <target name="debug" description="Specifies assembly to be compiled with debug symbols.">
    <property name="debug" value="true" overwrite="true" />
    <call target="build"/>
    <copy file="build\${project::get-name()}.pdb" todir="${build_path}" overwrite="true" />
  </target>
  <target name="build" description="Compiles assembly.">
    <mkdir dir="build" if="${not directory::exists('build')}" />
    <csc target="library" output="${output_bin}" debug="${debug}" optimize="${optimize}" >
      <sources>
        <include name="**\*.cs" />
      </sources>
      <references>
        <include name="..\lib\*.dll" />
        <include name="${build_path}*.dll"/>
        <exclude name="${build_bin}"/>
      </references>
      <resources dynamicprefix="true" prefix="${project::get-name()}">
        <include name="**\*.hbm.xml" />
      </resources>
    </csc>
    <mkdir dir="${build_path}" if="${not directory::exists(build_path)}"  />
    <copy file="${output_bin}" todir="${build_path}" overwrite="true" />
    <copy file="${output_bin}" todir="..\bin" overwrite="true" />
  </target>
</project>

 Basic ASP.Net NAnt Script
<?xml version="1.0" encoding="utf-8" ?>
<project xmlns="http://nant.sf.net/release/0.86-beta1/nant.xsd" name="Company.Portal.Web" default="build">
  
  <property name="debug" value="false" />
  <property name="optimize" value="true"/>
  <property name="bin_name" value="${project::get-name()}.dll" />
  <property name="output_bin" value="build\${bin_name} "/>
  <property name="build_path" value="..\build\" />
  <property name="build_bin" value="${build_path}${bin_name}"/>
  <property name="nant.settings.currentframework" value="net-2.0"/>
  <property name="web_path" value="..\web\"/>
  <property name="web_bin_path" value="${web_path}bin"/>
  
  <target name="clean" description="Cleans the build folder which forces a rebuild.">
    <delete dir="build"  if="${directory::exists('build')}" />
    <delete dir="${web_path}"  if="${directory::exists(web_path)}" failonerror="false" />
  </target>

  <target name="debug" description="Specifies assembly to be compiled with debug symbols.">
    <property name="debug" value="true" overwrite="true" />
    <call target="build"/>
    <copy file="build\${project::get-name()}.pdb" todir="${build_path}" overwrite="true" />
  </target>

  <target name="build" description="Compiles assembly.">
    <mkdir dir="build" if="${not directory::exists('build')}" />
    <csc target="library" output="${output_bin}" debug="${debug}" optimize="${optimize}" >
      <sources>
        <include name="**\*.cs" />
        <include name="**\*.resx" />
      </sources>
      <references>
        <include name="..\lib\*.dll" />
        <include name="${build_path}*.dll"/>
        <exclude name="${build_bin}"/>
      </references> 
    </csc>
    
    <copy file="${output_bin}" todir="${build_path}" overwrite="true" />
    <copy file="${output_bin}" todir="..\bin" overwrite="true" />
    <!-- Setup Web Site -->
    <mkdir dir="${build_path}" if="${not directory::exists(build_path)}"  />
    <mkdir dir="${web_path}" if="${not directory::exists(web_path)}"  />
    <mkdir dir="${web_bin_path}" if="${not directory::exists(web_bin_path)}"  />
    <copy todir="${web_bin_path}" overwrite="true">
      <fileset basedir="${build_path}">
        <include name="*.dll"/>
      </fileset>
    </copy>
    <copy todir="${web_bin_path}" overwrite="true">
      <fileset basedir="..\lib\">
        <include name="*.dll"/>
      </fileset>
    </copy>
    <copy todir="${web_path}" overwrite="true">
      <fileset>
        <include name="**.ascx"/>
        <include name="**.aspx"/>
        <include name="**.master"/>
        <include name="**.config"/>
        <include name="Config\**"/>
        <include name="IMG\**"/>
        <include name="App_Themes\**"/>
      </fileset>
    </copy>
  </target>
</project>

Setup:
  1. Install NAnt (your bin dir will likely be C:\Program Files\nant-0.86\bin)
  2.  Add NAnt bin directory to your path environmental variable so you can use nant from command-line interface anywhere
    1. Right-click "My Computer" then click "Properties"
    2. Click the "Advanced" tab then the "Environmental Variables" button
    3. Under the "System Variables" list find the item named "Path" then click "Edit"
    4. Add to the end of the "Variable value ";C:\Program Files\nant-0.86\bin"
    5. Click OK, OK, OK
    6. Now test it by clicking "Start" -> "Run"
    7. Type "cmd" then hit enter
    8. In the command prompt type "nant" then hit enter.  If it outputs a few lines and says BUILD FAILED then your path variables are correct
  3. In your solution directory create a folder called "lib" and put your libraries (anything 3rd party or not included with the target CLR version)
  4. Create your NAnt script in the target project directory naming your NAnt script: "default.build"
  5. Navigate to this location through command prompt and run one of the following commands
    1. nant - will do a build
    2. nant clean - will clean the build
    3. nant clean build - will clean the build and rebuild (recommended)
  6. Optionally you can create a make file at the solution directory which simply executes nant for each of the projects in the solution.  I call mine "make.cmd" and put it in your solution directory (one directory above your project directories assuming all project directories are siblings)
Sample Make File (make.cmd):
nant -buildfile:Company.Domain.Common\default.build %*
nant -buildfile:Company.Portal.Web\default.build %*

Usage: "make clean build"

Now you have NAnt scripts so that you can setup continuous integration for your project using something like CruiseControl.Net .