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.
Follow the Getting ready step in the Save entities to the database recipe in this chapter.
EntityModeMap
to the SessionRecipes
project.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"]); } } } } }
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>
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>
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>
EntityModeMap
recipe.Product
and ActorRole
tables.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.
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.
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
}
}
3.15.34.161