NConstruct Builder and Database Model Changes
Database Model Changes & NConstruct Builder
Category: Using NConstruct Builder
Target NConstruct Versions: 2.x
Document Release Date: November, 2009
Table of Contents
1. Problem Description
Database model changes implications
2. Solution
Separating generated and customized code
Data Transfer Object
Entity
Updating object model
3. Practical Examples
Case 1: A new field in existing table
Updated classes and files
ShippersDTO.Generated.cs class
ShippersEntity.Generated.cs class
ShippersEntity.hbm.xml file
ShippersEntity.cs
class
Case 2: A new table with a relation to an existing table
New classes and files
Updated classes and files
ShippersDTO.Generated.cs class
ShippersEntity.Generated.cs class
ShippersEntity.hbm.xml file
ShippersEntity.cs
class
4. Conclusion
1. Problem Description
Database model changes implications
There is probably no project without
changes in its database model after first deployment. Consequently,
object model has to be change, too.
The best practice for database model
changes is to create one or more SQL alter script which can be applied
on your development computer(s) and afterward on your customer's server
as well.
Updating object model in your .NET
application is another story. Usually, developer has to carefully translate
each change from underlying database model into application's object
model. In multitier application architecture, developer has usually
to update other layers of the application:
- business rules (application tier)
- user interface (presentation tier)
If code generation can create all or
most code for object-relational mapping classes and files, the same
technique can be used for updating object model if database model has
been changed.
This, in most projects iterative process,
can be represented by the following simple diagram:

One of the major problems which have
to be solved through the object model updating is to preserve the source
code which has been changed or customized after the first code generation
is used. Sometimes it's impossible to solve this problem completely,
but if a solution can handle most of this work, it can save a lot of
developer's time and reduces chances for creating many annoying bugs
which emerges from mapping classes and/or NHibernate HBM Xml files.
Let's have a closer look about using
NConstruct Builder in such cases.
2. Solution
Separating generated and customized code
Most of the source code NConstruct
Builder generates is located in [your application name].Server
project. Typical generated folder structure for the Server project
looks like this in the picture:
If we focus on one table in the database
model, for example - Shippers table, we can see there are four
generated files in the Server project:
- DTO\ShippersDTO.Generated.cs
- Entity\ShippersEntity.cs
- Entity\ShippersEntity.Generated.cs
- Entity\ShippersEntity.hbm.xml
Information for generating those files
is held in a Shippers table in the database model:

To support object model changes and to separate generated source code from
your own code, NConstruct Builder takes advantage of one very useful .NET
feature: partial classes. Where it's necessary or it might become necessary,
NConstruct Builder generates two partial classes or at least insert a
"Generated" keyword in the class' name.
Data Transfer Object
First file, ShippersDTO.Generated.cs, is just a simple Data Transfer Object (or DTO, see also
http://msdn.microsoft.com/en-us/library/ms978717.aspx" or
http://en.wikipedia.org/wiki/Data_transfer_object).
As DTO pattern definition states, DTO
class is used to transfer data between software application s).
As DTO pattern definition states, DTO class is used to transfer data
between software application subsystems. DTO classes don't have any behavior
except for storage and retrieval of its own data. NConstruct uses DTO classes
for table-like data presentation (e.g. grid UI control). As DTO classes do not
(and should not) have any business logic, there is no need to create two partial
classes. NConstruct Builder doesn't create two files but it just adds a
"Generated" keyword in the file's name and "partial" class definition.
Entity
For a data access layer based on NHibernate framework, two files per one
mapping must be created. One of them is simple business object class, and
another is NHibernate mapping file.
Without using the partial class feature, it would be enough to have two
files in our Shippers table example: Shippers entity object class (e.g.
ShippersEntity.cs) and Shippers NHibernate XML file (e.g.
ShippersEntity.hbm.xml).
But as we saw, there is a possibility that someone would like to add some
additional code in the object class. So, instead of one class, NConstruct
Builder generates two classes:
- ShippersEntity.cs
- ShippersEntity.Generated.cs
ShippersEntity.cs consists of only namespace and partial class definition:
namespace
Northwind.Server.Data.Northwind.Entity
{ public partial class ShippersEntity
{} }
|
This is the right place for a developer
to add any additional methods or other custom code.
ShippersEntity.Generated.cs
should therefore contain only generated code - only metadata information
extracted from the database. If database model is changed, developer
(or database administrator) applies SQL alter script on the database,
run again code generation tool and overwrite or replace only generated
files with the old ones. With such approach, all custom code in the
data access layer remains intact whenever database model is changed.
Updating object model
NConstruct Builder has an option to
generate only mapping and some other classes which might need updates
if a database model is changed.
This feature can be combined with partial
tables' selection in the Namespace definition step.

In this step, you may select only those
tables, where your database has been changed. NConstruct Builder will
however include related table(s) if you've changed their relations:
table relations and their object's reflections are just as important
part of object model as objects definitions are.
If you decide to generate only entity,
DTO, action and some other classes after your database model is changed,
use "Create entity, DTO, NHibernate *.Hbm.xml, Enumerations,
Action, Resource *.resx, and Web Pages.xml files only" checkbox
in the last step of NConstruct Builder wizard.

You may use the same project location
value as your project is located, or, if you are not sure about changes
you've made in your source code, you may use a different location and
overwrite or merge files manually.
3. Practical Examples
Case 1: A new field in existing table
Difficulty level:easy
difficult
Prerequisites:
- Visual Studio 2008 solution
for a default Northwind database generated by the NConstruct
Builder.
- Default project location
used in this example: "C:\NConstructProjects\Northwind".
Let's add one new field in our database
model. Assume we'd like to store fax number in a new field in Shippers
table in the Northwind database.

SQL alter script for upgrading database
would look like this one:
|
use Northwind
go
alter table Shippers add Fax varchar(50) null
go
|
To test NConstruct Builder doesn't
destroy our programming work; add one dummy method in the ShippersEntity.cs
class:
|
namespace Northwind.Server.Data.Northwind.Entity
{
public partial class ShippersEntity
{
public void
SomeMethod()
{
//No implementation
needed.
}
}
}
|
Run NConstruct Builder and select only
Shippers table in the Namespace definition step.

In the last step, tick the "Create
entity, DTO, NHibernate *.Hbm.xml, Enumerations, Action, Resource *.resx,
and Web Pages.xml files only" checkbox and leave the original
project location value.

Click on the Finish button and
wait for NConstruct Builder to generate new files.

Open generated Northwind solution
again and check what is changed.
Updated classes and files
Generated DTO class has one new field
(_Fax), one new property (Fax) and changed constructors
according to the change in the database model:
|
...
private String
_Fax;
public ShippersDTO(Int32 id, String companyname, String fax, String phone) :
this(id, companyname, fax, phone, 0)
{}
public ShippersDTO(Int32 id, String companyname, String fax, String phone,
int count)
{
Id
= id;
_Companyname
= companyname;
_Fax
= fax;
_Phone
= phone;
Count
= count; }
[VisibleAttribute, SortAttribute(1), RootPropertyName(ShippersEntity.FaxProperty)]
public virtual String
Fax
{
get {return _Fax;}
set {_Fax = value;}
}
|
Note: other, unchanged parts of the
ShippersDTO.Generated.cs class source code are not shown in the
code snippet above.
ShippersEntity.Generated.cs
class
Generated entity class has one new
field (_Fax), one new property (Fax) according to the
change in the database model:
|
...
private String
_Fax;
....
[VisibleAttribute, SortAttribute(1), FieldLengthAttribute(50), FilterableFieldAttribute]
public virtual String
Fax
{
get {return _Fax;}
set {_Fax = value;}
}
|
Note: other, unchanged parts of the tity.Generated.cs class source code are not shown in the
code snippet above.
ShippersEntity.hbm.xml file
Generated NHibernate XML file has one
new property definition according to the change in the database model:
|
<property name="Fax" column="Fax" access="field.pascalcase-underscore" not-null="false" type="System.String" insert="true" update="true"/>
|
Note: other, unchanged parts of the
ShippersEntity.hbm.xml file are not shown in the code snippet above.
ShippersEntity.cs class
Next thing to verify is our customized
class, ShippersEntity.cs. It should still contain manually written
method named SomeMethod().
|
namespace Northwind.Server.Data.Northwind.Entity
{
public partial class ShippersEntity
{
public void
SomeMethod()
{
//No implementation needed.
}
}
}
|
ShippersEntity.cs class is unchanged as it's meant to be.
Case 2: A new table with a relation to an existing table
Difficulty level: easy
difficult
Prerequisites:
- Visual Studio 2008 solution
for a default Northwind database generated by the NConstruct
Builder.
- Default project location
used in this example: "C:\NConstructProjects\Northwind".
In this case, we'll add a new table
and create a relation to one existing table in our database model. Assume
we'd like to add information about country for shippers: therefore we
have to create Countries table with some basic fields and 1-M
relation to the Shippers table.

SQL alter script for upgrading database
would look like this one:
|
use Northwind
go
create table Countries (
CountryID int identity not null ,
Code varchar (10) null ,
[Name] varchar (200) null,
constraint PK_Countries primary key (CountryID))
go
alter table Shippers add CountryID int null
go
alter table Shippers add constraint FK_Shippers_Countries foreign key (CountryID) references Countries (CountryID)
go
|
To test NConstruct Builder doesn't destroy our programming work; add one dummy method in the ShippersEntity.csclass:
|
namespace Northwind.Server.Data.Northwind.Entity
{
public partial class ShippersEntity
{
public void
SomeMethod()
{
//No implementation needed.
}
}
}
|
Run NConstruct Builder and select only Countries and Shippers tables in the Namespace definition
step.

In the last step, tick the "Create entity, DTO, NHibernate *.Hbm.xml, Enumerations, Action, Resource *.resx, and Web Pages.xml files only" checkbox and leave the original project location value.

Click on the Finish
button and
wait for NConstruct Builder to generate new files.

Open generated solution
again and check what's new and what's changed.
New classes and files
A new set of classes has been generated
because we've added new table in the Northwind database:
| File
name |
Location |
Purpose |
| CountriesEntity.Generated.cs |
Northwind.Server\Data\Northwind\Entity |
Generated entity class |
| CountriesEntity.cs |
Northwind.Server\Data\Northwind\Entity |
Empty partial class |
| CountriesEntity.hbm.xml |
Northwind.Server\Data\Northwind\Entity |
NHibernate mapping |
| CountriesDTO.Generated.cs/td>
| Northwind.Server\Data\Northwind\DTO |
DTO class for CountiresEntity |
| CountriesAction.cs |
Northwind.Client\Presentation\Actions\Northwind |
Menu action class |
However, if we want to find them in
our solution, there's none of them. The reason NConstruct Builder
doesn't do this automatically is the possibility of manual changes in
the project file (Northwind.Server.csproj in this case) and problems
with merging new and existing project file.
So, we have to add newly generated
files to our projects manually. It's good to start with the core classes
- those in the Northwind.Server project:
- Add CountriesEntity.Generated.cs,
CountriesEntity.cs, and CountriesEntity.hbm.xml files to
Northwind.Server\Data\Northwind\Entity folder in the Northwind.Server
project.
- Use Add/Existing Item...
action on the Northwind.Server\Data\Northwind\Entity folder:
folder:

- Change file types to "All
files (*.*)" to display ty.hbm.xml file as
w file as
well:

- Select CountriesEntity.Generated.cs,
CountriesEntity.cs, and CountriesEntity.hbm.xml files and
click Add button:

- One very important thing
to do here is to change Build Action property for CountriesEntity.hbm.xml
file to "Embedded Resource" - if you forget to change
it, NHibernate won't find mapping file in your project:

- Add Generated.cs
file to Northwind.Server\Data\Northwind\DTO folder in the
Northwind.Server project; the procedure is the same as previously
described.
- Add CountriesAction.cs
file to Northwind.Client\Presentation\Actions\Northwind folder
in the Northwind.Client project.
Updated classes and
files
ShippersDTO.Generated.cs
class
Generated DTO class has one new field
(_CountryName), one new property (CountryName) and changed
constructors according to the change in the database model:
|
...
private String
_CountryName;
public ShippersDTO(Int32 id, String companyname, String countryName, String phone) :
this(id, companyname, countryName, phone, 0)
{}
public ShippersDTO(Int32 id, String companyname, String countryName, String phone,
int count)
{
Id
= id;
_Companyname
= companyname;
_CountryName
= countryName;
_Phone
= phone;
Count
= count;
}
[VisibleAttribute,
SortAttribute(1), ManyToOnePropertyAttribute(CountriesEntity.NameProperty), RootPropertyName(ShippersEntity.CountryProperty)]
public virtual String
CountryName
{
get {return _CountryName;}
set {_CountryName = value;}
}
|
Note: other, unchanged parts of the
ShippersDTO.Generated.cs class source code are not shown in the
code snippet above.
ShippersEntity.Generated.cs
class
Generated entity class has one new
field (_Country), one new property (Country) according
to the change in the database model:
|
...
private CountriesEntity _Country;
...
[VisibleAttribute, FilterableFieldAttribute, ManyToOnePropertyAttribute("Id", false)]
public virtual CountriesEntity Country
{
get {return _Country;}
set {_Country = value;}
}
|
Note: other, unchanged parts of the
ShippersEntity.Generated.cs class source code are not shown in the
code snippet above.
ShippersEntity.hbm.xml file
Generated NHibernate XML file has one
new many-to-one definition according to the change in the database
model:
|
<many-to-one name="Country" access="field.pascalcase-underscore" not-null="false" outer-join="auto" insert="true" update="true">
<column name="CountryID"/>
</many-to-one>
|
Note: other, unchanged parts of the
ShippersEntity.hbm.xml file are not shown in the code snippet above.
ShippersEntity.cs class
Next thing to verify is our customized
class, ShippersEntity.cs. It should still contain manually written
method named SomeMethod().
|
namespace Northwind.Server.Data.Northwind.Entity
{
public partial class ShippersEntity
{
public void
SomeMethod()
{
//No implementation needed.
}
}
}
|
ShippersEntity.cs class is unchanged
as it's meant to be.
4. Conclusion
Application's source code modifications
can be accomplished with the aid of NConstruct Builder when a database
model changes. This process is fully automated within NConstruct Builder
in simpler cases.
Some manual work in Visual Studio IDE
for more complex cases can't be avoided, though.
NConstruct future versions might include
more automation support for database changes. They will be described
in "What's new" documents and in separate technical articles.
Comments and questions...
|