XML files have been NHibernate's default approach to mapping since its inception. It's a platform neutral, flexible and easily parsed format. The mapping syntax for Java's Hibernate is virtually identical to NHibernate's.
NHibernate also allows you to specify the mappings using nothing but code. This gives us a couple of advantages, since the mapping code gets intricately connected to the classes it should map. There is no risk of misspelled class or property names and you can use runtime logic to customize the mappings.
Complete the Getting ready instructions given at the beginning of this chapter.
The recipe uses the entity classes that we created in Eg.Core
in the preceding recipes of this chapter. However, for convenience, NH4CookbookHelpers
also provide the same class model and we will use that model here. Feel free to modify the code (changing the using
statements) to use Eg.Core
, if that suits you better. You may have to add a version
property (integer) to the Product
class.
MappingByCode
to the MappingRecipes
project.ProductMapping
to the folder:using NH4CookbookHelpers.Mapping.Model; using NHibernate.Mapping.ByCode; using NHibernate.Mapping.ByCode.Conformist; namespace MappingRecipes.MappingByCode { public class ProductMapping : ClassMapping<Product> { public ProductMapping() { Table("Product"); Id(x => x.Id, x => x.Generator(Generators.GuidComb)); Version(p => p.Version, v => v.UnsavedValue(0)); Discriminator(p=>p.Column("ProductType")); Property(p => p.Name); Property(p => p.Description); Property(p => p.UnitPrice); } } }
MovieMapping
to the folder:using NH4CookbookHelpers.Mapping.Model; using NHibernate.Mapping.ByCode; using NHibernate.Mapping.ByCode.Conformist; namespace MappingRecipes.MappingByCode { public class MovieMapping : SubclassMapping<Movie> { public MovieMapping() { DiscriminatorValue("Movie"); Property(x => x.Director); List(x => x.Actors, x => { x.Key(k => k.Column("MovieId")); x.Index(i => i.Column("ActorIndex")); x.Cascade(Cascade.All | Cascade.DeleteOrphans); } , x => x.OneToMany() ); } } }
BookMapping
to the folder:using NH4CookbookHelpers.Mapping.Model; using NHibernate.Mapping.ByCode.Conformist; namespace MappingRecipes.MappingByCode { public class BookMapping : SubclassMapping<Book> { public BookMapping() { DiscriminatorValue("Book"); Property(x => x.Author); Property(x => x.ISBN); } } }
ActorRoleMapping
to the folder:using NH4CookbookHelpers.Mapping.Model; using NHibernate.Mapping.ByCode; using NHibernate.Mapping.ByCode.Conformist; namespace MappingRecipes.MappingByCode { public class ActorRoleMapping : Class-Mapping<ActorRole> { public ActorRoleMapping() { Id(x => x.Id, x => x.Generator(new Generators.GuidComb)); Property(x => x.Actor); Property(x => x.Role); } } }
Recipe
to the folder:using System.Collections.Generic; using NH4CookbookHelpers.Mapping; using NH4CookbookHelpers.Mapping.Model; using NHibernate; using NHibernate.Cfg; using NHibernate.Mapping.ByCode; namespace MappingRecipes.MappingByCode { public class Recipe : BaseMappingRecipe { protected override void Configure(Configuration cfg) { var mapper = new ModelMapper(); mapper.AddMapping<ProductMapping>(); mapper.AddMapping<MovieMapping>(); mapper.AddMapping<BookMapping>(); mapper.AddMapping<ActorRoleMapping>(); var mapping = mapper.CompileMappingForAllExplicitlyAddedEntities(); cfg.AddMapping(mapping); } protected override void AddInitialData(ISession session) { session.Save(new Movie { Name = "Mapping by code - the movie", Description = "An interesting documentary", UnitPrice = 300, Actors = new List<ActorRole> { new ActorRole { Actor = "You", Role = "The mapper" } } }); } } }
MappingByCode
recipe.When NHibernate processes an XML-based mapping, it deserializes the XML into an in-memory structure of all the included mappings, called an HbmMapping
. Using coded mappings, the same structure is produced from code without deserialization.
The ModelMapper
is responsible for translating the ClassMapping
and SubclassMapping
classes into a well-structured HbmMapping
. In this recipe, we added all the mappings explicitly, using the AddMapping<T>
method, and therefore we could use the aptly named CompileMappingForAllExplicitlyAddedEntities
method to generate the HbmMapping
.
As you may have noticed, the naming of methods and properties in the mapping code very closely mimic that of elements and attributes in the HBM files. This was a deliberate design decision, to make it easy to translate HBM concepts and examples into coded mappings.
The following are a couple of examples:
XML mapping |
Code mapping |
---|---|
<property name="Author"> |
Property(x => x.Author) |
<list name="Actors" cascade= "all-delete-orphan" > <key column="MovieId" /> <index column="ActorIndex" /> <one-to-many class="ActorRole"/> </list> |
List(x => x.Actors, x =>{ x.Key(k => k.Column("MovieId")); x.Index(i => i.Column("ActorIndex")); x.Cascade( Cascade.All | Cascade.DeleteOrphans); } , x => x.OneToMany() ) |
<many-to-one name="Publisher" cascade="save-update"> <column name="PublisherId"/> </many-to-one> |
ManyToOne(x => x.Author, x =>{ x.Column( "PublisherId"); x.Cascade( Cascade.Persist); }); |
It's not strictly necessary to use the separate mapping classes (ClassMapping, SubclassMapping
, and so on.). These are just encapsulated ways to add mapping data to the model mapper. Mappings can also be added using the Class<T>
or Subclass<T>
methods:
var mapper = new ModelMapper(); mapper.Class<Product>(m => { m.Table("Product"); m.Id(p => p.Id, p => p.Generator(Generators.GuidComb)); m.Version(p => p.Version, v => v.UnsavedValue(0)); m.Discriminator(p => p.Column("ProductType")); m.Property(p => p.Name); m.Property(p => p.Description); m.Property(p => p.UnitPrice); });
Mapping a large object model like this tends to become unmanageable, but of course you could pass the ModelMapper
instance around to different mapping contributors, if this is a syntax you prefer.
The ModelMapper
exposes a large set of events that are fired when the mappings are generated. We can add handlers to those events, not just to inspect the process, but also to modify the mappings as they are generated. How these events are used is described in the next recipe.
3.142.199.184