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.
Set up a new console project for NHibernate by following these steps:
NHibernate.dll
and NHibernate.ByteCode.Castle.dll
.var nhConfig = new Configuration().Configure(); var sessionFactory = nhConfig.BuildSessionFactory();
App.config
file to your project.configuration
element, declare a hibernate-configuration
section, as shown:<configSections> <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate"/> </configSections>
configuration
element, add a connection string named db
as shown:<connectionStrings> <add name="db" connectionString="Server=.SQLExpress; Database=NHCookbook; Trusted_Connection=SSPI"/> </connectionStrings>
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>
hibernate-configuration session-factory
element, add a default_entity_mode
property with the value dynamic-map
.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>
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>
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>
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} };
using (var session = sessionFactory.OpenSession()) { using (var tx = session.BeginTransaction()) { session.Save("Movie", movie); tx.Commit(); } }
Product
and ActorRole
tables.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.
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.
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
}
}