Here I’ll try to explain how we have used it in that application writing a new example.
The IModuleMapper
/// <summary>
/// A template to perform a mapping using ConfOrm
/// </summary>
public interface IModuleMapper
{
/// <summary>
/// Register domain classes, persistent-strategies, relations and so on of a spefic module
/// </summary>
void DefineDomain();
/// <summary>
/// Register patterns of a module
/// </summary>
void RegisterPatterns();
/// <summary>
/// Customize persistence representations
/// </summary>
void Customize();
/// <summary>
/// Get all domain entities of the module.
/// </summary>
/// <returns>domain entities.</returns>
IEnumerable<Type> GetEntities();
}
/// A template to perform a mapping using ConfOrm
/// </summary>
public interface IModuleMapper
{
/// <summary>
/// Register domain classes, persistent-strategies, relations and so on of a spefic module
/// </summary>
void DefineDomain();
/// <summary>
/// Register patterns of a module
/// </summary>
void RegisterPatterns();
/// <summary>
/// Customize persistence representations
/// </summary>
void Customize();
/// <summary>
/// Get all domain entities of the module.
/// </summary>
/// <returns>domain entities.</returns>
IEnumerable<Type> GetEntities();
}
The Domain
In the assembly Acme.ModuleAusing System;
using Acme.Domain.Common;
namespace Acme.ModuleA
{
public class User: Entity
{
public string UserName { get; set; }
public string Text { get; set; }
public DateTime Birthday { get; set; }
}
}
using Acme.Domain.Common;
namespace Acme.ModuleA
{
public class User: Entity
{
public string UserName { get; set; }
public string Text { get; set; }
public DateTime Birthday { get; set; }
}
}
using System;
using Acme.Domain.Common;
using Acme.ModuleA;
namespace Acme.ModuleB
{
public class Article: Entity
{
public DateTime PublishedAt { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public User Author { get; set; }
}
}
using Acme.Domain.Common;
using Acme.ModuleA;
namespace Acme.ModuleB
{
public class Article: Entity
{
public DateTime PublishedAt { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public User Author { get; set; }
}
}
Some extensions
Just to not forget it later…public static class ModuleMappingUtil
{
public static IEnumerable<Type> MapModule(this IModuleMapper mapper)
{
mapper.DefineDomain();
mapper.RegisterPatterns();
mapper.Customize();
return mapper.GetEntities();
}
public static bool IsOfModuleContaining<T>(this Type source)
{
return source.Assembly.Equals(typeof(T).Assembly);
}
}
{
public static IEnumerable<Type> MapModule(this IModuleMapper mapper)
{
mapper.DefineDomain();
mapper.RegisterPatterns();
mapper.Customize();
return mapper.GetEntities();
}
public static bool IsOfModuleContaining<T>(this Type source)
{
return source.Assembly.Equals(typeof(T).Assembly);
}
}
The mapping
You can organize the mapping of your modules in a single assembly, in general the same where you are generating the session factory, or in more than one assembly. In general you need just a little class to map a module so please don’t be exaggerated separating your application.For this example I will show three implementations of ModuleMapper all implemented in the same assembly named Acme.Persistence.Wiring.
public class GeneralsPatternsModuleMapper : IModuleMapper
{
private readonly Mapper mapper;
private readonly ObjectRelationalMapper orm;
public GeneralsPatternsModuleMapper(ObjectRelationalMapper orm, Mapper mapper)
{
this.orm = orm;
this.mapper = mapper;
}
#region IModuleMapper Members
public void DefineDomain()
{
// map .NET4 ISet<T> as a NHibernate's set
orm.Patterns.Sets.Add(mi => mi.GetPropertyOrFieldType().GetGenericIntercafesTypeDefinitions().Contains(typeof (ISet<>)));
}
public void RegisterPatterns()
{
// all strings are length 50 characters
mapper.PatternsAppliers.Property.Add(mi => typeof (string).Equals(mi.GetPropertyOrFieldType()), (mi, map) => map.Length(50));
// when a DateTime seems to be a simple date apply NH's Date type
mapper.PatternsAppliers.Property.Add(mi => typeof (DateTime).Equals(mi.GetPropertyOrFieldType()) && IsDate(mi.Name), (mi, map) => map.Type(NHibernateUtil.Date));
// when a DateTime seems to not be a simple date apply NH's UtcDateTime type
mapper.PatternsAppliers.Property.Add(mi => typeof (DateTime).Equals(mi.GetPropertyOrFieldType()) && !IsDate(mi.Name), (mi, map) => map.Type(NHibernateUtil.UtcDateTime));
}
public void Customize()
{
// Nothing to do
}
public IEnumerable<Type> GetEntities()
{
yield break;
}
#endregion
public static bool IsDate(string name)
{
return name.EndsWith("Date") || name.StartsWith("Date") || name.EndsWith("Day") || name.EndsWith("day") || name.StartsWith("Day");
}
}
{
private readonly Mapper mapper;
private readonly ObjectRelationalMapper orm;
public GeneralsPatternsModuleMapper(ObjectRelationalMapper orm, Mapper mapper)
{
this.orm = orm;
this.mapper = mapper;
}
#region IModuleMapper Members
public void DefineDomain()
{
// map .NET4 ISet<T> as a NHibernate's set
orm.Patterns.Sets.Add(mi => mi.GetPropertyOrFieldType().GetGenericIntercafesTypeDefinitions().Contains(typeof (ISet<>)));
}
public void RegisterPatterns()
{
// all strings are length 50 characters
mapper.PatternsAppliers.Property.Add(mi => typeof (string).Equals(mi.GetPropertyOrFieldType()), (mi, map) => map.Length(50));
// when a DateTime seems to be a simple date apply NH's Date type
mapper.PatternsAppliers.Property.Add(mi => typeof (DateTime).Equals(mi.GetPropertyOrFieldType()) && IsDate(mi.Name), (mi, map) => map.Type(NHibernateUtil.Date));
// when a DateTime seems to not be a simple date apply NH's UtcDateTime type
mapper.PatternsAppliers.Property.Add(mi => typeof (DateTime).Equals(mi.GetPropertyOrFieldType()) && !IsDate(mi.Name), (mi, map) => map.Type(NHibernateUtil.UtcDateTime));
}
public void Customize()
{
// Nothing to do
}
public IEnumerable<Type> GetEntities()
{
yield break;
}
#endregion
public static bool IsDate(string name)
{
return name.EndsWith("Date") || name.StartsWith("Date") || name.EndsWith("Day") || name.EndsWith("day") || name.StartsWith("Day");
}
}
public class ModuleAMapper : IModuleMapper
{
private readonly Mapper mapper;
private readonly ObjectRelationalMapper orm;
public ModuleAMapper(ObjectRelationalMapper orm, Mapper mapper)
{
this.orm = orm;
this.mapper = mapper;
}
public void DefineDomain()
{
// register all classes of my module (ConfORM will use it to discover polymorphic associations)
orm.AddToDomain(typeof(User).Assembly.GetExportedTypes());
// defines the persistence strategy for each root of each hierarchy
orm.TablePerClass<User>();
}
public void RegisterPatterns()
{
// no specific patterns for this module
}
public void Customize()
{
// map a specific size (20) for a specific property of a specific class
mapper.Class<User>(x => x.Property(user => user.UserName, map => map.Length(20)));
}
public IEnumerable<Type> GetEntities()
{
// all entities of this modules
return typeof(User).Assembly.GetExportedTypes().Where(t=> typeof(Entity).IsAssignableFrom(t));
}
}
{
private readonly Mapper mapper;
private readonly ObjectRelationalMapper orm;
public ModuleAMapper(ObjectRelationalMapper orm, Mapper mapper)
{
this.orm = orm;
this.mapper = mapper;
}
public void DefineDomain()
{
// register all classes of my module (ConfORM will use it to discover polymorphic associations)
orm.AddToDomain(typeof(User).Assembly.GetExportedTypes());
// defines the persistence strategy for each root of each hierarchy
orm.TablePerClass<User>();
}
public void RegisterPatterns()
{
// no specific patterns for this module
}
public void Customize()
{
// map a specific size (20) for a specific property of a specific class
mapper.Class<User>(x => x.Property(user => user.UserName, map => map.Length(20)));
}
public IEnumerable<Type> GetEntities()
{
// all entities of this modules
return typeof(User).Assembly.GetExportedTypes().Where(t=> typeof(Entity).IsAssignableFrom(t));
}
}
public class ModuleBMapper : IModuleMapper
{
private readonly Mapper mapper;
private readonly ObjectRelationalMapper orm;
public ModuleBMapper(ObjectRelationalMapper orm, Mapper mapper)
{
this.orm = orm;
this.mapper = mapper;
}
public void DefineDomain()
{
// register all classes of my module (ConfORM will use it to discover polymorphic associations)
orm.AddToDomain(typeof(Article).Assembly.GetExportedTypes());
// defines the persistence strategy for each root of each hierarchy
orm.TablePerClassHierarchy<Article>();
}
public void RegisterPatterns()
{
// patterns for this module
// when a string property is named "Text" then apply StringClob type (note just for Acme.ModuleB)
mapper.PatternsAppliers.Property.Add(
mi => "text".Equals(mi.Name.ToLowerInvariant()) && typeof (string).Equals(mi.GetPropertyOrFieldType()) && mi.DeclaringType.IsOfModuleContaining<Article>(),
(mi, map) => { map.Type(NHibernateUtil.StringClob); map.Length(int.MaxValue); });
}
public void Customize()
{
// nothing to do
}
public IEnumerable<Type> GetEntities()
{
// all entities of this modules
return typeof(Article).Assembly.GetExportedTypes().Where(t => typeof(Entity).IsAssignableFrom(t));
}
}
{
private readonly Mapper mapper;
private readonly ObjectRelationalMapper orm;
public ModuleBMapper(ObjectRelationalMapper orm, Mapper mapper)
{
this.orm = orm;
this.mapper = mapper;
}
public void DefineDomain()
{
// register all classes of my module (ConfORM will use it to discover polymorphic associations)
orm.AddToDomain(typeof(Article).Assembly.GetExportedTypes());
// defines the persistence strategy for each root of each hierarchy
orm.TablePerClassHierarchy<Article>();
}
public void RegisterPatterns()
{
// patterns for this module
// when a string property is named "Text" then apply StringClob type (note just for Acme.ModuleB)
mapper.PatternsAppliers.Property.Add(
mi => "text".Equals(mi.Name.ToLowerInvariant()) && typeof (string).Equals(mi.GetPropertyOrFieldType()) && mi.DeclaringType.IsOfModuleContaining<Article>(),
(mi, map) => { map.Type(NHibernateUtil.StringClob); map.Length(int.MaxValue); });
}
public void Customize()
{
// nothing to do
}
public IEnumerable<Type> GetEntities()
{
// all entities of this modules
return typeof(Article).Assembly.GetExportedTypes().Where(t => typeof(Entity).IsAssignableFrom(t));
}
}
The NHibernate’s initialization
First you need a method to get all modules of your application, for simplicity here is a simple implementation:private static IEnumerable<IModuleMapper> GetAllModulesMappers(ObjectRelationalMapper orm, Mapper mapper)
{
yield return new GeneralsPatternsModuleMapper(orm, mapper);
yield return new ModuleAMapper(orm, mapper);
yield return new ModuleBMapper(orm, mapper);
}
{
yield return new GeneralsPatternsModuleMapper(orm, mapper);
yield return new ModuleAMapper(orm, mapper);
yield return new ModuleBMapper(orm, mapper);
}
- var orm = new ObjectRelationalMapper();
- var patternSet = new CoolPatternsAppliersHolder(orm);
- var mapper = new Mapper(orm, patternSet);
- HbmMapping mappings = mapper.CompileMappingFor(GetAllModulesMappers(orm, mapper).SelectMany(x => x.MapModule()));
- var configure = new Configuration();
- configure.SessionFactoryName("Demo");
- configure.DataBaseIntegration(db =>
- {
- db.Dialect<MsSql2008Dialect>();
- db.ConnectionStringName = "ToAcmeDb";
- });
- configure.AddDeserializedMapping(mappings, "AcmeApplicationDomain");
- ISessionFactory factory = configure.BuildSessionFactory();
Hi Fabio, i have a problem, can you see if you can help me to solve it?
ReplyDeletehttp://stackoverflow.com/questions/6600989/how-to-have-nhibernate-persist-a-null-string-as-property-value-as-string-empty
I actually need to do the same with an nullable enum too (it saves as string in a varchar).
Hi Fabio, great post! I actually made use of your old IModuleMapping example as a base for building a compositional mapping implementation using MEF. As to ConfOrm itself, it took me just a few lines to map the whole domain! Easy to use once you get to know it. I also added a pattern to tell conform how to handle the cascading of a relationship given the Type of the target of the relation. If anyone is interested in any of this, do not hesitate to contact me! :-)
ReplyDeleteNicolas,
ReplyDeletepublish it somewhere and perhaps we can add it in ConfORM.Shop.
Fabio,
ReplyDeleteYou just gave me the perfect excuse to start my own blog... :-)
http://codetothepeople.blogspot.com/
Let me know what you think!
Congratulation, very good.
ReplyDeleteI'll include it to ConfORM.Shop.
Thanks Fabio!
ReplyDeleteHi Fabio,
ReplyDeleteLove the concept behind ConfORM. Thanks for creating it. I was wondering if you could help me a couple of questions I had about using ConfORM... Does it matter what database I use when using ConfORM i.e. can I use Oracle when using ConfORM? Also, I have a need to map spatial database types to types on POCO. Is this possible to do when using ConfORM? I wasn't able to find any info on how to customize what datatypes, the columns get mapped to. I would appreciate your thoughts/suggestions on this issue.
Thank You,
Vish
The first commercial application mapped using ConfORM is using (back-end):
ReplyDeleteNHibernate, ConfORM, NHibernate.Validator, NHibernate.Spatial, NHibernate.Search.
There is a OSS application using NHibernate for multiple DB and ConfORM to map the domain (http://dexterblogengine.codeplex.com/); Ugo Lattanzi (one of dexter guy) have a commercial application using NHibernate, mapped with ConfORM, to work with ORACLE.
Hola, muy bueno el post! muy util!. Te queria hacer una consulta Fabio. Estoy utilizando fluent para un proyecto y me encuentro con el siguiente error: a different object with the same identifier value was already associated with the session. Segun lo que investigue, se debe a que en el primer nivel de cache existe una entidad con el mismo id pero que nhibernate la considera distinta a que la quiero persistir. Sabes cual podria ser la solucion? ya he intentado borrando la entidad de la cache, usando merge en vez de saveOrUpdate y varias cosas mas pero nada resulto. Podrias orientarme un poco? Gracias. Saludos
ReplyDeleteMuy interesante el articulo.
ReplyDeleteLe hago una consulta
¿Con que tipo de licencia esta publicado el código del ejemplo?
¿Quería saber si lo puedo utilizar dentro de en un desarrollo propio?