Mapping by code

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.

Getting ready

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.

How to do it…

  1. Add a new folder named MappingByCode to the MappingRecipes project.
  2. Add a new class named 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);
      }
     }
    }
  3. Add a new class named 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()
       );
      }
     }
    }
  4. Add a new class named 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);
      }
     }
    }
  5. Add a new class named 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);
      }
     }
    }
  6. Add a new class named 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" 
         } 
      }
       });
      }
     }
    }
  7. Run the application and start the MappingByCode recipe.
  8. Investigate the query log.

How it works…

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);
});

There's more…

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.

See also

  • Mapping a class with XML
  • Mapping by convention
  • Creating mappings fluently
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.184.102