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

Follow the Getting ready step in the Save entities to the database recipe in this chapter.

How to do it...

  1. Add a new folder named EntityModeMap to the SessionRecipes project.
  2. Add a new class named Recipe to the folder:
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using NH4CookbookHelpers;
    using NHibernate;
    using NHibernate.Cfg;
    
    namespace SessionRecipes.EntityModeMap
    {
      public class Recipe : HbmMappingRecipe
      {
    
        protected override void Configure(Configuration cfg)
        {
          cfg.SetProperty("default_entity_mode", "dynamic-map");
        }
    
        protected override void AddInitialData(ISession session)
        {
          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}
          };
    
          session.Save("Movie", movie);
        }
    
        public override void RunQueries(ISession session)
        {
          var movies = session
            .CreateQuery("from Movie").List<IDictionary>();
          foreach (var movie in movies)
          {
            Console.WriteLine("Movie:{0}", movie["Name"]);
            Console.WriteLine("Actors");
            foreach (var actor in ((IEnumerable)movie["Actors"]).OfType<IDictionary>())
            {
              Console.WriteLine("{0} as {1}", actor["Actor"], actor["Role"]);
            }
          }
        }
      }
    }
  3. Add a new Product.hbm.xml mapping file to the folder:
    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
      <class entity-name="Product"
          discriminator-value="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>
  4. 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="Movie">
        <property name="Director" type="String" />
        <bag name="Actors" cascade="all-delete-orphan">
          <key column="MovieId" />
          <one-to-many class="ActorRole"/>
        </bag>
      </subclass>
    </hibernate-mapping>
  5. 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>
  6. Don't forget to set your mapping files as embedded resources.
  7. Run the application and start the EntityModeMap recipe.
  8. Check the database's Product and ActorRole tables.

How it works…

EntityMode.Map allows us to define 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, The Configuration and Schema that, because of NHibernate's Java roots, NHibernate uses the term map in place of dictionary.

Next, we've made slight changes to our mappings. First, you'll notice that we've set an entity-name instead of a classname. 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 note that 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 declare it. Finally, we specify discriminator values. You'll recollect from Chapter 2, Models and Mappings 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.0, 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 will not set the default_entity_mode property and 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
3.15.34.161