I was working on generating C# classes, using as starting point .hbm NHibernate mapping files. As usual, I wrote an example with AjGenesis, my open source code generation engine.
You can download a first example from my Skydrive:
Examples > AjGenesis > NHibernateMappingExample01.zip
(the code is in the trunk, in the current change set, under examples\NHibernateMappinp:
but if you want to go directly to the example, the Skydrive download I mentioned has all you need to run this demo, including AjGenesis trunk code compiled to binaries).
After expanding the file, you have this content:
To create C# classes, execute at command prompt:
GenerateClasses AjFirstExample
GenerateClasses AjTest
To create a .NET project with the .cs and .hbm files, run:
GenerateProject AjFirstExample
GenerateProject AjTest
The generated files are created under Build folder.
The two example projects are AjFirstExample, with two simple plain mappings, and AjTest, with a more interesing mapping, with bags and many to one relations.
Currently, each project is described by a simple Project.xml:
<Project Name="AjTest">
</Project>
This is one of the mapping files in Projects\AjTest\Mappings, Department.hbm:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="AjTest.Entities"
namespace="AjTest.Entities"
>
<class name="Department" table="departments">
<id name="Id" column="Id" type="Int32">
<generator class="native"/>
</id>
<property name="Description" type="String"/>
<bag name="Employees" lazy="true" inverse="true" cascade="all">
<key column="IdDepartment"/>
<one-to-many class="AjTest.Entities.Employee, AjTest.Entities"/>
</bag>
</class>
</hibernate-mapping>
This is the generated code for this mapping, Department.generated.cs:
using System;
using System.Collections.Generic;
using Iesi.Collections.Generic;
namespace AjTest.Entities
{
public class Department {
public int Id { get; set; }
public string Description { get; set; }
public IList<Employee> Employees { get; set; }
public Department()
{
this.Employees = new List<Employee>();
}
}
}
Lets take a look to generation process. This is GenerateProject.cmd:
@echo off
set ProjectName=%1%
if "%1%"=="" set ProjectName=AjFirstExample
Bin\AjGenesis.Console.exe Projects\%ProjectName%\Project.xml Tasks\AddMappings.ajg Tasks\BuildCSharp.ajg
xcopy Libraries\*.* Build\%ProjectName%\CSharp\Src\Libraries /Y /Q
The main line is the one containing AjGenesis.Console.exe invocation. Project.xml is loaded in memory. The AddMapping.ajg task is loaded and executed, and then, BuildCSharp.ajg task is processed. AddMapping.ajg code (written in AjBasic, the dinamic language currently used by AjGenesis):
' Add mappings from directory if not specified in Project model
Include("Utilities/Utilities.tpl")
if not Project.Mappings then
Project.Mappings = CreateList()
di = new System.IO.DirectoryInfo("Projects/${Project.Name}/Mappings")
for each fi in di.GetFiles("*.hbm.xml")
filename = fi.Name
Project.Mappings.Add(filename.Substring(0, filename.Length - 8))
end for
end if
It adds the name of mapping files contained in the Mapping folder of the project. A more interesing task is GenerateCSharp.ajg. First, it loads the NHibernate library to use its hbm parser:
include "Utilities/Utilities.tpl"
include "Utilities/FileUtilities.tpl"
include "Utilities/TypeUtilities.tpl"
Include("Utilities/NHibernateUtilities.tpl")
include "Templates/CSharp/UtilitiesCs.tpl"
include "Templates/CSharp/CSharpFunctions.tpl"
AssemblyManager.LoadFrom("Libraries/NHibernate.dll")
parser = new NHibernate.Cfg.MappingSchema.MappingDocumentParser()
Then, it creates the solution and project objects:
if not Project.BuildDir then
Project.BuildDir = "Build/${Project.Name}/CSharp"
end if
message "Creating Directories..."
FileManager.CreateDirectory(Project.BuildDir)
FileManager.CreateDirectory("${Project.BuildDir}/Sql")
FileManager.CreateDirectory("${Project.BuildDir}/Src")
FileManager.CreateDirectory("${Project.BuildDir}/Src/Libraries")
message "Defining Solution and Projects..."
Project.Solution = CreateObject()
Project.Solution.Guid = "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"
Project.Solution.Projects = CreateList()
message "Defining Entities Project..."
PrjEntities = CreateObject()
PrjEntities.Includes = CreateList()
PrjEntities.Guid = CreateGuid()
PrjEntities.COMGuid = CreateGuid()
Project.Solution.Projects.Add(PrjEntities)
Project.Entities = CreateList()
Then, it iterates on each hbm file, to get information about the entities to generate:
for each MappingName in Project.Mappings
filename = "Projects/${Project.Name}/Mappings/${MappingName}.hbm.xml"
mapping = parser.Parse(OpenAsStream(filename))
for each hbmclass in mapping.Items where IsType(hbmclass, "HbmClass")
Entity = CreateObject()
Project.Entities.Add(Entity)
Entity.ClassName = hbmclass.name
Entity.Namespace = mapping.namespace
' Namespace as default project name for Entities Project
if not PrjEntities.Name then
PrjEntities.Name = mapping.namespace
PrjEntities.Directory = "${Project.BuildDir}/Src/${PrjEntities.Name}"
FileManager.CreateDirectory(PrjEntities.Directory)
end if
Entity.Properties = CreateList()
if hbmclass.Id then
Property = CreateObject()
Property.Name = hbmclass.Id.name
Property.Type = HbmTypeToCSharp(hbmclass.Id.type1, Entity.Namespace)
Entity.Properties.Add(Property)
end if
for each item in hbmclass.Items
if IsType(item, "HbmProperty") then
Property = CreateObject()
Property.Name = item.name
Property.Type = HbmTypeToCSharp(item.type1, Entity.Namespace)
Entity.Properties.Add(Property)
end if
if IsType(item, "HbmManyToOne") then
Property = CreateObject()
Property.Name = item.name
Property.Type = HbmTypeToCSharp(item.class, Entity.Namespace)
Entity.Properties.Add(Property)
end if
if IsType(item, "HbmSet") then
Property = CreateObject()
Property.Name = item.name
Property.IsSet = true
Property.Type = HbmTypeToCSharp(item.Item.class, Entity.Namespace)
Entity.Properties.Add(Property)
end if
if IsType(item, "HbmBag") then
Property = CreateObject()
Property.Name = item.name
Property.IsList = true
Property.Type = HbmTypeToCSharp(item.Item.class, Entity.Namespace)
Entity.Properties.Add(Property)
end if
end for
end for
end for
You can extend the capabilities, processing more tags (I should write an example using Meta tags), and detecting more NHibernate mapping idioms. Now, the task generates the code:
for each Entity in Project.Entities
TransformerManager.Transform("Templates/CSharp/Entity.tpl", "${PrjEntities.Directory}/${Entity.ClassName}.generated.cs", Environment)
PrjEntities.Includes.Add(CreateFileCs("${Entity.ClassName}.generated"))
end for
The tasks not only generates the .cs files, but it creates a solution and a C# project, copying and embedding the original mapping files:
for each MappingName in Project.Mappings
filename = "Projects/${Project.Name}/Mappings/${MappingName}.hbm.xml"
targetfilename = "${PrjEntities.Directory}/${MappingName}.hbm.xml"
System.IO.File.Copy(filename, targetfilename, true)
PrjEntities.Includes.Add(CreateFileType(MappingName,"hbm.xml"))
end for
for each CsProject in Project.Solution.Projects where CsProject.ProjectType<>"Web"
FileManager.CreateDirectory(CsProject.Directory)
FileManager.CreateDirectory(CsProject.Directory & "/Properties")
TransformerManager.Transform("Templates/CSharp/CsProject.tpl", "${CsProject.Directory}/${CsProject.Name}.csproj", Environment)
TransformerManager.Transform("Templates/CSharp/AssemblyInfoCs.tpl", "${CsProject.Directory}/Properties/AssemblyInfo.cs", Environment)
end for
TransformerManager.Transform("Templates/Solution.tpl", "${Project.BuildDir}/Src/${Project.Name}.sln", Environment)
This is the generated solution:
Next steps
I should work in these point:
- Generate a more complete solution (with NHibernate infrastructure, Web Presentation, etc…) as in others AjGenesis examples.
- Support more NHibernate mapping options
- Use meta tags
But now, you are able to play with this example. You can change the templates to generate more artifacts, as Visual Basic .NET source files.
Thanks to @fabiomaulo for pointing me to the NHibernate hbm parser capabilities!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez