Mapping with ConfORM

The ConfORM project brings convention-based mappings to NHibernate. In this recipe, I'll show you how to map your model using ConfORM conventions.

Getting ready

  1. Check out the ConfORM source code from Google Code at http://code.google.com/p/codeconform/source/checkout.
  2. Build the ConfORM project.
  3. Complete the previous Eg.Core model and mapping recipes.

How to do it...

  1. Create a new console project named Eg.ConfORMMappings.
  2. Add references to the Eg.Core model project, ConfORM.dll and ConfORM.Shop.dll.
  3. In Eg.Core.Entity, make the Version property public.
  4. In 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;
  5. Add the following 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);
    }
  6. Add the following WriteXmlMapping function:
    private static void WriteXmlMapping(HbmMapping hbmMapping)
    {
      var document = Serialize(hbmMapping);
      File.WriteAllText("WholeDomain.hbm.xml", document);
    }
  7. Add the following 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();
        }
      }
    }
  8. In the static void Main method, add the following line:
    WriteXmlMapping(GetMapping());
  9. Build and run your application.
  10. Browse to the application's 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>

How it works...

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:

How it works...

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.

  1. We begin by specifying our Product class hierarchy, which includes Books and Movies. We also add our ActorRole entity class individually.
  2. We use the GuidOptimizedPoidPattern to find all Guid properties named Id, and map them as POIDs with the guid.comb generator.
  3. References from 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);
  4. Next, we configure a few conventions. All 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));
  5. All properties of root entities, except those named 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));
  6. Finally, we map our 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 => { }));
  7. The last step in building our 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);
  8. The resulting mapping object is equivalent to XML mapping contained in WholeDomain.hbm.xml.

See also

  • Mapping a class with XML
  • Creating class hierarchy mappings
  • Mapping a one-to-many relationship
  • Setting up a base entity class
  • Bidirectional one-to-many class relationships
  • Handling versioning and concurrency
  • 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.128.226.121