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
ReplyDelete