Using dictionaries as entities

A little-known feature of NHibernate is EntityMode.Map. In this recipe, I'll show you how we can use this feature to persist entities without classes.

Getting ready

Set up a new console project for NHibernate by following these steps:

  1. Create a new console project.
  2. Add references to NHibernate.dll and NHibernate.ByteCode.Castle.dll.
  3. Add the following code to your main function:
    var nhConfig = new Configuration().Configure();
    var sessionFactory = nhConfig.BuildSessionFactory();
  4. Add an App.config file to your project.
  5. Inside the configuration element, declare a hibernate-configuration section, as shown:
    <configSections>
      <section name="hibernate-configuration"
               type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate"/>
    </configSections>
  6. Inside the configuration element, add a connection string named db as shown:
    <connectionStrings>
        <add name="db" connectionString="Server=.SQLExpress; Database=NHCookbook; Trusted_Connection=SSPI"/>
      </connectionStrings>
  7. Add the hibernate-configuration section with the following configuration:
    <hibernate-configuration
    xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
    
      <property name="proxyfactory.factory_class">
      NHibernate.ByteCode.Castle.ProxyFactoryFactory,
      NHibernate.ByteCode.Castle
      </property>
    
      <property name="dialect">
      NHibernate.Dialect.MsSql2008Dialect,
      NHibernate
      </property>
    
      <property name="connection.connection_string_name">
      db
      </property>
    
      <property name="adonet.batch_size">
      100
      </property>
    
      <mapping assembly="yourAssemblyHere"/>
    
    </session-factory>
    </hibernate-configuration>
  8. Set the mapping assembly value to the name of your project.

How to do it...

  1. In your hibernate-configuration session-factory element, add a default_entity_mode property with the value dynamic-map.
  2. Create a new Product.hbm.xml mapping file with this mapping:
    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
      <class entity-name="Product" 
             discriminator-value="Eg.Core.Product">
        <id name="Id" type="Guid">
          <generator class="guid.comb" />
        </id>
        <discriminator column="ProductType" type="String" />
        <natural-id mutable="true">
          <property name="Name" not-null="true" 
                    type="String" />
        </natural-id>
        <version name="Version" type="Int32"/>
        <property name="Description" type="String" />
        <property name="UnitPrice" not-null="true" 
                  type="Currency" />
      </class>
    </hibernate-mapping>
  3. Again, create a mapping file named Movie.hbm.xml with the following mapping:
    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
      <subclass entity-name="Movie" extends="Product" 
                discriminator-value="Eg.Core.Movie">
        <property name="Director" type="String" />
        <list name="Actors" cascade="all-delete-orphan">
          <key column="MovieId" />
          <index column="ActorIndex" />
          <one-to-many entity-name="ActorRole"/>
        </list>
      </subclass>
    </hibernate-mapping>
  4. Finally, create a mapping file named ActorRole.hbm.xml with the following mapping:
    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
      <class entity-name="ActorRole">
        <id name="Id" type="Guid">
          <generator class="guid.comb" />
        </id>
        <version name="Version" type="Int32" />
        <property name="Actor" type="String" 
                  not-null="true" />
        <property name="Role" type="String" 
                  not-null="true" />
      </class>
    </hibernate-mapping>
  5. Don't forget to set your mapping files as embedded resources.
  6. In main, after building the session factory, create a movie object as a dictionary using the following code:
    var movieActors = new List<Dictionary<string, object>>()
    {
      new Dictionary<string, object>() {
      {"Actor","Keanu Reeves"},
      {"Role","Neo"}
      },
      new Dictionary<string, object>() {
      {"Actor", "Carrie-Ann Moss"},
      {"Role", "Trinity"}
      }
    };
    
    var movie = new Dictionary<string, object>()
    {
      {"Name", "The Matrix"},
      {"Description", "Sci-Fi Action film"},
      {"UnitPrice", 18.99M},
      {"Director", "Wachowski Brothers"},
      {"Actors", movieActors}
    };
  7. After building the movie dictionary, save it using the following code:
    using (var session = sessionFactory.OpenSession())
    {
      using (var tx = session.BeginTransaction())
      {
      session.Save("Movie", movie);
      tx.Commit();
      }
    }
  8. Build and run your application.
  9. Check your database's Product and ActorRole tables.

How it works...

EntityMode.Map allows us to define our entities as dictionaries instead of statically typed objects. There are three key pieces to this approach.

First, instead of creating sessions using the default EntityMode.Poco where NHibernate expects us to interact with it using plain old class objects, we've told NHibernate to use EntityMode.Map by setting default_entity_mode to dynamic-map. Remember from Chapter 1 that, because of NHibernate's Java roots, NHibernate uses the term map in place of dictionary.

Next, we've made some slight changes to our mappings. First, you'll notice that we've set an entity-name instead of a class name. This allows us to specify an entity by name, instead of allowing NHibernate to decide based on the type of object we pass in. Next, you'll notice we specify types for all of our properties. We don't have classes that NHibernate can reflect to guess our data types. We have to tell it. Finally, we specify discriminator values. You'll remember from Chapter 2 that the default discriminator value is the type's FullName. The default discriminator is actually the entity-name, which defaults to the type's FullName. In this case, we don't have a type, and if we used our entity-names, the data wouldn't match our normal mappings. We override the values simply so the data will match perfectly with the data from our other recipes.

Finally, we interact with the session using dictionaries (maps) and entity-name strings instead of objects with types.

There's more...

While this example may seem a bit academic, with the release of the Dynamic Language Runtime and the new dynamic feature of C# 4, this type of scenario will undoubtedly prove useful in bridging the gap between NHibernate and the dynamic language world.

Partially dynamic

It's rarely desirable to use EntityMode.Map throughout your application, as shown in this recipe. Instead, you may want to use it only in a specific case, where you would rather not create matching classes. In this scenario, we would not set the default_entity_mode property, and would instead open a child session in map mode. The code to accomplish this is as follows:

using (var pocoSession = sessionFactory.OpenSession())
{
  using (var childSession =
  pocoSession.GetSession(EntityMode.Map))
  {
    // Do something here
  }
}
..................Content has been hidden....................

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