In an application we are going to use ConfORM as way to map our domain for NHibernate. The application is composed by various modules and the first module where we want substitute “the other way to map” with ConfORM, is our implementation of Asp.NET Membership Provider (the application Expo Blox).
This is a good proof of ConfORM because we can’t take decisions about Table/Column naming nor anything else strictly related with the DB. In addition I have asked to Michele don’t send me the source but only assemblies. The result is that I can’t touch neither the DB nor the entities implementation.
Introduction
ConfORM does not have the limitation of define anything concerning a class in a single place and you can organize the mapping by concern. As basic template, for each module, we will use this interface :public interface IModuleMapping { void DomainDefinition(ObjectRelationalMapper orm); void RegisterPatterns(Mapper mapper); void Customize(Mapper mapper); IEnumerable<Type> GetEntities(); }
Initial Questions
- Which are the Root entities of this module and which is the strategy to represent the hierarchy ?
- Which are many-to-many, one-to-one ?
- Cascades… aggregate-root ?
- Something to say about the POID ?
- Something to say about the collection ? I mean… are you specifying only ICollection<T>, for property and field, even when the collection is an ISet<T> ?
public class MembershipModuleMapping: IModuleMapping { private readonly Type[] tablePerClassRootEntities = new[] { typeof(User), typeof(Role), typeof(Application) }; #region Implementation of IModuleMapping public void DomainDefinition(ObjectRelationalMapper orm) { orm.TablePerClass(tablePerClassRootEntities); orm.ManyToMany<User, Role>(); orm.Patterns.PoidStrategies.Add(new GuidPoidPattern()); orm.Patterns.Sets.Add(new SetWhenGenericCollectionPattern()); } public void RegisterPatterns(Mapper mapper) { } public void Customize(Mapper mapper) { } public IEnumerable<Type> GetEntities() { return typeof(User).Assembly.GetTypes().Where(t => typeof(BaseEntity).IsAssignableFrom(t)); } #endregion }The Michele’s surprise is that with four lines we have a correct mapping but… “Fabio, We can’t follow the NHibernate’s convention for columns-naming , nor tables-naming, nor columns size, and nor for the type of DateTime.”
Follow Questions
- Which is the matter with DateTime ?
- Which are columns/table naming convention and specifications ?
Answers
- For all DateTime of this module we are using a custom type (SqlServerDateTimeUserType).
- Have a look to the DB defined by Microsoft, there you can find what you need.
To solve the answer-1
public void RegisterPatterns(Mapper mapper) { PersistentTypesPatterns(mapper); } private void PersistentTypesPatterns(Mapper mapper) { mapper.AddPropertyPattern( mi => IsMemberOfClassOfMembershipProvider(mi) && mi.GetPropertyOrFieldType() == typeof(DateTime), pm => pm.Type<SqlServerDateTimeUserType>()); } private bool IsMemberOfClassOfMembershipProvider(MemberInfo mi) { return mi.ReflectedType.Assembly == typeof(User).Assembly; }
For the answer-2
First of all some methods to group mappings by concernpublic void RegisterPatterns(Mapper mapper) { PersistentTypesPatterns(mapper); ColumnsNamingPatterns(mapper); DDLPatterns(mapper); } public void Customize(Mapper mapper) { CustomizeTables(mapper); CustomizeColumns(mapper); }I saw that most of columns follows the NHibernate’s convention where the mapped property-name is the name of the column. The columns where the NHibernate’s convention does not work are few and alls follows another convention that will be valid only for this module (in the others we will use the clear NHibernate’s convention):
private void ColumnsNamingPatterns(Mapper mapper) { mapper.AddPoidPattern(mi => IsMemberOfClassOfMembershipProvider(mi) && mi.Name == "Id", (mi, idm) => idm.Column(mi.ReflectedType.Name + "Id")); mapper.AddCollectionPattern(IsMemberOfClassOfMembershipProvider, (mi, cm) => cm.Key(km => km.Column(mi.DeclaringType.Name + "Id"))); mapper.AddManyToOnePattern(IsMemberOfClassOfMembershipProvider, (mi, idm) => idm.Column(mi.GetPropertyOrFieldType().Name + "Id")); }
Another fact is that most of string-properties are represented with VARCHAR(256) (old school string sizing):
private void DDLPatterns(Mapper mapper) { mapper.AddPropertyPattern( mi => mi.GetPropertyOrFieldType() == typeof(string) && IsMemberOfClassOfMembershipProvider(mi), pm => pm.Length(256)); }
I can’t see any other pattern in the DB.
Now I can implements the method to customize the name of all tables involved as required by Asp.NET Membership:
private void CustomizeTables(Mapper mapper)
{ mapper.Class<User>(cm => cm.Table("aspnet_Users")); mapper.Class<Role>(cm => cm.Table("aspnet_Roles")); mapper.Class<Application>(cm => cm.Table("aspnet_Applications")); mapper.JoinedSubclass<Entities.Membership>(cm => cm.Table("aspnet_Membership")); mapper.Customize<User>(pcc => pcc.Collection(role => role.Roles, cpm => cpm.Table("aspnet_UsersInRoles"))); mapper.Customize<Role>(pcc => pcc.Collection(role => role.Users, cpm => cpm.Table("aspnet_UsersInRoles"))); }As last I can map the other columns stuff :
private void CustomizeColumns(Mapper mapper) { mapper.Customize<User>(pcc => { pcc.Property(role => role.UserName, pm => pm.NotNullable(true)); pcc.Property(role => role.LoweredUserName, pm => pm.NotNullable(true)); pcc.Property(role => role.MobileAlias, pm => pm.Length(16)); pcc.ManyToOne(role => role.Application, mtom => mtom.NotNullable(true)); }); mapper.Customize<Role>(pcc => { pcc.Property(role => role.RoleName, pm => pm.NotNullable(true)); pcc.Property(role => role.LoweredRoleName, pm => pm.NotNullable(true)); pcc.ManyToOne(role => role.Application, mtom => mtom.NotNullable(true)); }); mapper.Customize<Application>(pcc => { pcc.Property(app => app.ApplicationName, pm => pm.NotNullable(true)); pcc.Property(app => app.LoweredApplicationName, pm => pm.NotNullable(true)); }); mapper.Customize<Payoff.Membership.Entities.Membership>(pcc => { pcc.Property(membership => membership.Password, pm => { pm.Length(128); pm.NotNullable(true); }); pcc.Property(membership => membership.PasswordSalt, pm => { pm.Length(128); pm.NotNullable(true); }); pcc.Property(membership => membership.MobilePIN, pm => pm.Length(16)); pcc.Property(membership => membership.PasswordAnswer, pm => pm.Length(128)); pcc.Property(membership => membership.Comment, pm => pm.Length(3000)); }); mapper.JoinedSubclass<Payoff.Membership.Entities.Membership>(cm => cm.Key(km => km.Column("UserId"))); }