A first look at ConfORM - Part 1
- by thangchung
All source codes for this post can be found at here.Have you ever heard of ConfORM is not? I have read it three months ago when I wrote an post about NHibernate and Autofac. At that time, this project really has just started and still in beta version, so I still do not really care much. But recently when reading a book by Jason Dentler NHibernate 3.0 Cookbook, I started to pay attention to it. Author have mentioned quite a lot of OSS in his book. And now again I have reviewed ConfORM once again. I have been involved in ConfORM development group on google and read some articles about it. Fabio Maulo spent a lot of work for the OSS, and I hope it will adapt a great way for NHibernate (because he contributed to NHibernate that).
So what is ConfORM? It is stand for Configuration ORM, and it was trying to use a lot of heuristic model for identifying entities from C# code. Today, it's mostly Model First Driven development, so the first thing is to build the entity model. This is really important and we can see it is the heart of business software. Then we have to tell DB about the entity of this model. We often will use Inversion Engineering here, Database Schema is will create based on recently Entity Model. From now we will absolutely not interested in the DB again, only focus on the Entity Model.Fluent NHibenate really good, I liked this OSS. Sharp Architecture and has done so well in Fluent NHibernate integration with applications. A Multiple Database technical in Sharp Architecture is truly awesome. It can receive configuration, a connection string and a dll containing entity model, which would then create a SessionFactory, finally caching inside the computer memory. As the number of SessionFactory can be very large and will full of the memory, it has also devised a way of caching SessionFactory in the file. This post I hope this will not completely explain about and building a model of multiple databases. I just tried to mount a number of posts from the community and apply some of my knowledge to build a management model Session for ConfORM.As well as Fluent NHibernate, ConfORM also supported on the interface mapping, see this to understand it. So the first thing we will build the Entity Model for it, and here is what I will use the model for this article. A simple model for managing news and polls, it will be too easy for a number of people, but I hope not to bring complexity to this post.I will then have some code to build super type for the Entity Model.
public interface IEntity<TId> { TId Id { get; set; } }
public abstract class EntityBase<TId> : IEntity<TId> { public virtual TId Id { get; set; } public override bool Equals(object obj) { return Equals(obj as EntityBase<TId>); } private static bool IsTransient(EntityBase<TId> obj) { return obj != null && Equals(obj.Id, default(TId)); } private Type GetUnproxiedType() { return GetType(); } public virtual bool Equals(EntityBase<TId> other) { if (other == null) return false; if (ReferenceEquals(this, other)) return true; if (!IsTransient(this) && !IsTransient(other) && Equals(Id, other.Id)) { var otherType = other.GetUnproxiedType(); var thisType = GetUnproxiedType(); return thisType.IsAssignableFrom(otherType) || otherType.IsAssignableFrom(thisType); } return false; } public override int GetHashCode() { if (Equals(Id, default(TId))) return base.GetHashCode(); return Id.GetHashCode(); } }
Database schema will be created as:The next step is to build the ConORM builder to create a NHibernate Configuration. Patrick have a excellent article about it at here. Contract of it below:
public interface IConfigBuilder { Configuration BuildConfiguration(string connectionString, string sessionFactoryName); }
The idea here is that I will pass in a connection string and a set of the DLL containing the Entity Model and it makes me a NHibernate Configuration (shame that I stole this ideas of Sharp Architecture). And here is its code:
public abstract class ConfORMConfigBuilder : RootObject, IConfigBuilder { private static IConfigurator _configurator; protected IEnumerable<Type> DomainTypes; private readonly IEnumerable<string> _assemblies; protected ConfORMConfigBuilder(IEnumerable<string> assemblies) : this(new Configurator(), assemblies) { _assemblies = assemblies; } protected ConfORMConfigBuilder(IConfigurator configurator, IEnumerable<string> assemblies) { _configurator = configurator; _assemblies = assemblies; } public abstract void GetDatabaseIntegration(IDbIntegrationConfigurationProperties dBIntegration, string connectionString); protected abstract HbmMapping GetMapping(); public Configuration BuildConfiguration(string connectionString, string sessionFactoryName) { Contract.Requires(!string.IsNullOrEmpty(connectionString), "ConnectionString is null or empty"); Contract.Requires(!string.IsNullOrEmpty(sessionFactoryName), "SessionFactory name is null or empty"); Contract.Requires(_configurator != null, "Configurator is null"); return CatchExceptionHelper.TryCatchFunction( () => { DomainTypes = GetTypeOfEntities(_assemblies); if (DomainTypes == null) throw new Exception("Type of domains is null"); var configure = new Configuration(); configure.SessionFactoryName(sessionFactoryName); configure.Proxy(p => p.ProxyFactoryFactory<ProxyFactoryFactory>()); configure.DataBaseIntegration(db => GetDatabaseIntegration(db, connectionString)); if (_configurator.GetAppSettingString("IsCreateNewDatabase").ConvertToBoolean()) { configure.SetProperty("hbm2ddl.auto", "create-drop"); } configure.Properties.Add("default_schema", _configurator.GetAppSettingString("DefaultSchema")); configure.AddDeserializedMapping(GetMapping(), _configurator.GetAppSettingString("DocumentFileName")); SchemaMetadataUpdater.QuoteTableAndColumns(configure); return configure; }, Logger); } protected IEnumerable<Type> GetTypeOfEntities(IEnumerable<string> assemblies) { var type = typeof(EntityBase<Guid>); var domainTypes = new List<Type>(); foreach (var assembly in assemblies) { var realAssembly = Assembly.LoadFrom(assembly); if (realAssembly == null) throw new NullReferenceException(); domainTypes.AddRange(realAssembly.GetTypes().Where( t => { if (t.BaseType != null) return string.Compare(t.BaseType.FullName, type.FullName) == 0; return false; })); } return domainTypes; } }
I do not want to dependency on any RDBMS, so I made a builder as an abstract class, and so I will create a concrete instance for SQL Server 2008 as follows:
public class SqlServerConfORMConfigBuilder : ConfORMConfigBuilder { public SqlServerConfORMConfigBuilder(IEnumerable<string> assemblies) : base(assemblies) { } public override void GetDatabaseIntegration(IDbIntegrationConfigurationProperties dBIntegration, string connectionString) { dBIntegration.Dialect<MsSql2008Dialect>(); dBIntegration.Driver<SqlClientDriver>(); dBIntegration.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote; dBIntegration.IsolationLevel = IsolationLevel.ReadCommitted; dBIntegration.ConnectionString = connectionString; dBIntegration.LogSqlInConsole = true; dBIntegration.Timeout = 10; dBIntegration.LogFormatedSql = true; dBIntegration.HqlToSqlSubstitutions = "true 1, false 0, yes 'Y', no 'N'"; } protected override HbmMapping GetMapping() { var orm = new ObjectRelationalMapper(); orm.Patterns.PoidStrategies.Add(new GuidPoidPattern()); var patternsAppliers = new CoolPatternsAppliersHolder(orm); //patternsAppliers.Merge(new DatePropertyByNameApplier()).Merge(new MsSQL2008DateTimeApplier()); patternsAppliers.Merge(new ManyToOneColumnNamingApplier()); patternsAppliers.Merge(new OneToManyKeyColumnNamingApplier(orm)); var mapper = new Mapper(orm, patternsAppliers); var entities = new List<Type>(); DomainDefinition(orm); Customize(mapper); entities.AddRange(DomainTypes); return mapper.CompileMappingFor(entities); } private void DomainDefinition(IObjectRelationalMapper orm) { orm.TablePerClassHierarchy(new[] { typeof(EntityBase<Guid>) }); orm.TablePerClass(DomainTypes); orm.OneToOne<News, Poll>(); orm.ManyToOne<Category, News>(); orm.Cascade<Category, News>(Cascade.All); orm.Cascade<News, Poll>(Cascade.All); orm.Cascade<User, Poll>(Cascade.All); } private static void Customize(Mapper mapper) { CustomizeRelations(mapper); CustomizeTables(mapper); CustomizeColumns(mapper); } private static void CustomizeRelations(Mapper mapper) { } private static void CustomizeTables(Mapper mapper) { } private static void CustomizeColumns(Mapper mapper) { mapper.Class<Category>( cm => { cm.Property(x => x.Name, m => m.NotNullable(true)); cm.Property(x => x.CreatedDate, m => m.NotNullable(true)); }); mapper.Class<News>( cm => { cm.Property(x => x.Title, m => m.NotNullable(true)); cm.Property(x => x.ShortDescription, m => m.NotNullable(true)); cm.Property(x => x.Content, m => m.NotNullable(true)); }); mapper.Class<Poll>( cm => { cm.Property(x => x.Value, m => m.NotNullable(true)); cm.Property(x => x.VoteDate, m => m.NotNullable(true)); cm.Property(x => x.WhoVote, m => m.NotNullable(true)); }); mapper.Class<User>( cm => { cm.Property(x => x.UserName, m => m.NotNullable(true)); cm.Property(x => x.Password, m => m.NotNullable(true)); }); } }
As you can see that we can do so many things in this class, such as custom entity relationships, custom binding on the columns, custom table name, ... Here I only made two so-Appliers for OneToMany and ManyToOne relationships, you can refer to it here
public class ManyToOneColumnNamingApplier : IPatternApplier<PropertyPath, IManyToOneMapper> { #region IPatternApplier<PropertyPath,IManyToOneMapper> Members public void Apply(PropertyPath subject, IManyToOneMapper applyTo) { applyTo.Column(subject.ToColumnName() + "Id"); } #endregion #region IPattern<PropertyPath> Members public bool Match(PropertyPath subject) { return subject != null; } #endregion }
public class OneToManyKeyColumnNamingApplier : OneToManyPattern, IPatternApplier<PropertyPath, ICollectionPropertiesMapper> { public OneToManyKeyColumnNamingApplier(IDomainInspector domainInspector) : base(domainInspector) { } #region Implementation of IPattern<PropertyPath> public bool Match(PropertyPath subject) { return Match(subject.LocalMember); } #endregion Implementation of IPattern<PropertyPath> #region Implementation of IPatternApplier<PropertyPath,ICollectionPropertiesMapper> public void Apply(PropertyPath subject, ICollectionPropertiesMapper applyTo) { applyTo.Key(km => km.Column(GetKeyColumnName(subject))); } #endregion Implementation of IPatternApplier<PropertyPath,ICollectionPropertiesMapper> protected virtual string GetKeyColumnName(PropertyPath subject) { Type propertyType = subject.LocalMember.GetPropertyOrFieldType(); Type childType = propertyType.DetermineCollectionElementType(); var entity = subject.GetContainerEntity(DomainInspector); var parentPropertyInChild = childType.GetFirstPropertyOfType(entity); var baseName = parentPropertyInChild == null ? subject.PreviousPath == null ? entity.Name : entity.Name + subject.PreviousPath : parentPropertyInChild.Name; return GetKeyColumnName(baseName); } protected virtual string GetKeyColumnName(string baseName) { return string.Format("{0}Id", baseName); } }
Everyone also can download the ConfORM source at google code and see example inside it. Next part I will write about multiple database factory. Hope you enjoy about it. happy coding and see you next part.