The ConfORM project brings convention-based mappings to NHibernate. In this recipe, I'll show you how to map your model using ConfORM conventions.
Eg.Core
model and mapping recipes.Eg.ConfORMMappings
.Eg.Core
model project, ConfORM.dll
and ConfORM.Shop.dll
.Eg.Core.Entity
, make the Version
property public.Program.cs
, add the following using statements to the beginning of the file:using System; using System.IO; using System.Linq; using System.Xml; using System.Xml.Serialization; using ConfOrm; using ConfOrm.NH; using ConfOrm.Patterns; using ConfOrm.Shop.CoolNaming; using Eg.Core; using NHibernate; using NHibernate.Cfg.MappingSchema;
GetMapping
function to the Program
class:private static HbmMapping GetMapping() { var orm = new ObjectRelationalMapper(); var mapper = new Mapper(orm, new CoolPatternsAppliersHolder(orm)); orm.TablePerClassHierarchy<Product>(); orm.TablePerClass<ActorRole>(); orm.Patterns.PoidStrategies.Add( new GuidOptimizedPoidPattern()); orm.VersionProperty<Entity>(x => x.Version); orm.NaturalId<Product>(p => p.Name); orm.Cascade<Movie, ActorRole>( Cascade.All | Cascade.DeleteOrphans); mapper.AddPropertyPattern(mi => mi.GetPropertyOrFieldType() == typeof(Decimal) && mi.Name.Contains("Price"), pm => pm.Type(NHibernateUtil.Currency)); mapper.AddPropertyPattern(mi => orm.IsRootEntity(mi.DeclaringType) && !"Description".Equals(mi.Name), pm => pm.NotNullable(true)); mapper.Subclass<Movie>(cm => cm.List(movie => movie.Actors, colm => colm.Index( lim => lim.Column("ActorIndex")), m => { })); var domainClasses = typeof(Entity).Assembly.GetTypes() .Where(t => typeof(Entity).IsAssignableFrom(t)); return mapper.CompileMappingFor(domainClasses); }
WriteXmlMapping
function:private static void WriteXmlMapping(HbmMapping hbmMapping) { var document = Serialize(hbmMapping); File.WriteAllText("WholeDomain.hbm.xml", document); }
Serialize
function:private static string Serialize(HbmMapping hbmElement) { var setting = new XmlWriterSettings { Indent = true }; var serializer = new XmlSerializer(typeof(HbmMapping)); using (var memStream = new MemoryStream(2048)) using (var xmlWriter = XmlWriter.Create(memStream, setting)) { serializer.Serialize(xmlWriter, hbmElement); memStream.Flush(); memStream.Position = 0; using (var sr = new StreamReader(memStream)) { return sr.ReadToEnd(); } } }
static void Main
method, add the following line:WriteXmlMapping(GetMapping());
binDebug
folder and examine the WholeDomain.hbm.xml
file. You should find the following familiar mapping:<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="Eg.Core" assembly="Eg.Core" xmlns="urn:nhibernate-mapping-2.2"> <class name="Product"> <id name="Id" type="Guid"> <generator class="guid.comb" /> </id> <discriminator /> <natural-id> <property name="Name" not-null="true" /> </natural-id> <version name="Version" /> <property name="Description" /> <property name="UnitPrice" type="Currency" not-null="true" /> </class> <class name="ActorRole"> <id name="Id" type="Guid"> <generator class="guid.comb" /> </id> <version name="Version" /> <property name="Actor" not-null="true" /> <property name="Role" not-null="true" /> </class> <subclass name="Book" extends="Product"> <property name="ISBN" /> <property name="Author" /> </subclass> <subclass name="Movie" extends="Product"> <property name="Director" /> <list name="Actors" cascade="all,delete-orphan"> <key column="MovieId" /> <list-index column="ActorIndex" /> <one-to-many class="ActorRole" /> </list> </subclass> </hibernate-mapping>
With a standard NHibernate application, NHibernate takes each XML mapping and deserializes it into an HbmMapping
object, then adds the HbmMapping
object to the NHibernate configuration, as shown in the next diagram:
With ConfORM, we skip this deserialization step. The ConfORM mapper outputs an HbmMapping
object built from our conventions, ready to be added to the configuration.
ConfORM uses conventions and patterns to build a mapping directly from the model. In addition to ConfORM's default patterns, we use a few extra conventions in our model.
Product
class hierarchy, which includes Books
and Movies
. We also add our ActorRole
entity class individually.GuidOptimizedPoidPattern
to find all Guid
properties named Id
, and map them as POIDs with the guid.comb
generator.Movie
to ActorRole
, such as our Actors
collection, should use cascade="all-delete-orphan"
. We set this up with the following bit of code:orm.Cascade<Movie, ActorRole>( Cascade.All | Cascade.DeleteOrphans);
decimal
properties with the word Price
in the property name should be mapped as type="currency"
. We use the following code:mapper.AddPropertyPattern(mi => mi.GetPropertyOrFieldType() == typeof(Decimal) && mi.Name.Contains("Price"), pm => pm.Type(NHibernateUtil.Currency));
Description
, are mapped with not-null="true"
. Remember, we're using the table-per-class hierarchy strategy, so our subclasses shouldn't have not-null
properties. The code we use is as follows:mapper.AddPropertyPattern(mi => orm.IsRootEntity(mi.DeclaringType) && !"Description".Equals(mi.Name), pm => pm.NotNullable(true));
Actors
list in Movies
, setting the list index column name to ActorIndex
. We use the following code:mapper.Subclass<Movie>(cm => cm.List(movie => movie.Actors, colm => colm.Index( lim => lim.Column("ActorIndex")), m => { }));
HbmMapping
object is to call CompileMappingFor
, passing in every Entity
type, as shown in the following code:var domainClasses = typeof(Entity).Assembly.GetTypes() .Where(t => typeof(Entity).IsAssignableFrom(t)); return mapper.CompileMappingFor(domainClasses);
WholeDomain.hbm.xml
.