A first look at ConfORM - Part 1
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.