In this post I will map the same domain I used in the previous post about EntityFramework but without the overhead of the crazy properties I had to add to fix the deficiencies of EntityFramework 4.1:
For this post I’m using the following nugets:
- ConfORM (depends on NHibernate): This help me to automap the domain to a relational model based on conventions.
- NHibernate.LinFu: This is a proxy generator used by nhibernate for lazy loading. Note that the next version of NHibernate 3.2 (trunk) doesn’t need this. It is inside NHibernate.dll
- NHibernate.SetForNet4: This is because NHibernate doesn’t support System.Collections.Generic.ISet<T> out of the box. ISet<T> is from .Net 4, while nhibernate support set mapping from version 1 with Iesi.Collections. This nuget add support for lazy loading an ISet<T>
This is a console project and I have the following files:
Net4Collections is a class installed by the NHibernate.SetForNet4, so the only I did for this example was:
internal class Program { private static void Main() { HbmMapping mappings = AutomapDomain(typeof (Order), typeof (OrderItem), typeof (Product), typeof (Customization)); Configuration configuration = ConfigureNHibernate(mappings); Console.WriteLine("Generating the schema"); new SchemaExport(configuration).Create(true, true); Console.WriteLine("Persiting some objects"); var sf = configuration.BuildSessionFactory(); using(var s = sf.OpenSession()) using(var tx = s.BeginTransaction()) { s.Save(new Product { Name = "Fideos", Customizations = {new Customization { Name = "Tuco", PossibleValues = {"Pocon", "Medio", "Sopa"} }} }); tx.Commit(); } Console.ReadLine(); } private static HbmMapping AutomapDomain(params Type[] entities) { var orm = new ObjectRelationalMapper(); //We are telling here to conform that ISet<> properties should be mapped as <set> orm.Patterns.Sets.Add( m => m.GetPropertyOrFieldType() .GetGenericIntercafesTypeDefinitions() .Contains(typeof (ISet<>))); orm.TablePerClass(entities); CustomizeMappings(orm); //Some ConfORM.Shop patterns var englishInflector = new EnglishInflector(); var patterns = new SafePropertyAccessorPack() .Merge(new CoolTablesAndColumnsNamingPack(orm)) .Merge(new PluralizedTablesPack(orm, englishInflector) .Merge(new CollectionOfElementsColumnApplier(orm, englishInflector))); //Mapper var mapper = new Mapper(orm, patterns); var mapping = mapper.CompileMappingFor(entities); Console.WriteLine(Serialize(mapping)); return mapping; } private static void CustomizeMappings(ObjectRelationalMapper orm) { orm.PersistentProperty<Order>(o => o.Total); orm.Cascade<Product, Customization>(Cascade.All); } private static Configuration ConfigureNHibernate(HbmMapping mapping) { var configuration = new Configuration(); //nhibernate support for ISet<> configuration.CollectionTypeFactory<Net4CollectionTypeFactory>(); //Use linfu proxy facotory: NOTE: This is going to be deprecated in vNext. configuration.Proxy(p => p.ProxyFactoryFactory<ProxyFactoryFactory>()); //The database connection configuration configuration.DataBaseIntegration(db => { db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote; db.Dialect<MsSql2008Dialect>(); db.Driver<SqlClientDriver>(); db.ConnectionStringName = "NHibernateTest"; db.LogSqlInConsole = true; db.LogFormatedSql = true; }); configuration.AddDeserializedMapping(mapping, "AllMappings"); return configuration; } //This method is only used to show you in the console the nhibernate mappings in XML protected static string Serialize(HbmMapping hbmElement) { var setting = new XmlWriterSettings {Indent = true}; var serializer = new XmlSerializer(typeof (HbmMapping)); using (var memStream = new MemoryStream()) using (XmlWriter xmlWriter = XmlWriter.Create(memStream, setting)) { serializer.Serialize(xmlWriter, hbmElement); memStream.Flush(); memStream.Position = 0; var sr = new StreamReader(memStream); return sr.ReadToEnd(); } } }
There are only one customization in this example to map the readonly property. By default ConfORM doesn’t map readonly properties but as I said yesterday I was trying to do so in EntityFramework…
I am using a set of conventions to map the domain, the power of ConfORM to infer most of the things and also ConfORM.Shop.dll that comes with the ConfORM nuget package to generate this beautiful schema:
Please take your time to examine this schema.
Want to see the output of the console?
Generating the schema if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK3E8588581FEFE3AB]') AND parent_object_id = OBJECT_ID('OrderItems')) alter table OrderItems drop constraint FK3E8588581FEFE3AB if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK3E858858F373BEAE]') AND parent_object_id = OBJECT_ID('OrderItems')) alter table OrderItems drop constraint FK3E858858F373BEAE if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FKEF9298D4286FF938]') AND parent_object_id = OBJECT_ID('OrderItemPreferences')) alter table OrderItemPreferences drop constraint FKEF9298D4286FF938 if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FKC2F496971FEFE3AB]') AND parent_object_id = OBJECT_ID('Customizations')) alter table Customizations drop constraint FKC2F496971FEFE3AB if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK956AE80DAF824711]') AND parent_object_id = OBJECT_ID('CustomizationPossibleValues')) alter table CustomizationPossibleValues drop constraint FK956AE80DAF824711 if exists (select * from dbo.sysobjects where id = object_id(N'Orders') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table Orders if exists (select * from dbo.sysobjects where id = object_id(N'OrderItems') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table OrderItems if exists (select * from dbo.sysobjects where id = object_id(N'OrderItemPreferences') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table OrderItemPreferences if exists (select * from dbo.sysobjects where id = object_id(N'Products') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table Products if exists (select * from dbo.sysobjects where id = object_id(N'Customizations') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table Customizations if exists (select * from dbo.sysobjects where id = object_id(N'CustomizationPossibleValues') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table CustomizationPossibleValues if exists (select * from dbo.sysobjects where id = object_id(N'hibernate_unique_key') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table hibernate_unique_key create table Orders ( Id BIGINT not null, [Date] DATETIME null, Status INT null, Location INT null, primary key (Id) ) create table OrderItems ( Id BIGINT not null, ProductId BIGINT null, Quantity INT null, UnitPrice DECIMAL(19,5) null, OrderId BIGINT null, primary key (Id) ) create table OrderItemPreferences ( OrderItemId BIGINT not null, Preference NVARCHAR(255) null, idx NVARCHAR(255) not null, primary key (OrderItemId, idx) ) create table Products ( Id BIGINT not null, Name NVARCHAR(255) null, Price DECIMAL(19,5) null, primary key (Id) ) create table Customizations ( Id BIGINT not null, Name NVARCHAR(255) null, ProductId BIGINT null, primary key (Id) ) create table CustomizationPossibleValues ( CustomizationId BIGINT not null, PossibleValue NVARCHAR(255) null ) alter table OrderItems add constraint FK3E8588581FEFE3AB foreign key (ProductId) references Products alter table OrderItems add constraint FK3E858858F373BEAE foreign key (OrderId) references Orders alter table OrderItemPreferences add constraint FKEF9298D4286FF938 foreign key (OrderItemId) references OrderItems alter table Customizations add constraint FKC2F496971FEFE3AB foreign key (ProductId) references Products alter table CustomizationPossibleValues add constraint FK956AE80DAF824711 foreign key (CustomizationId) references Customizations create table hibernate_unique_key ( next_hi BIGINT ) insert into hibernate_unique_key values ( 1 ) Persiting some objects NHibernate: select next_hi from hibernate_unique_key with (updlock, rowlock) NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1; @p0 = 2 [Type: Int64 (0)], @p1 = 1 [Type: Int64 (0)] NHibernate: select next_hi from hibernate_unique_key with (updlock, rowlock) NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1; @p0 = 3 [Type: Int64 (0)], @p1 = 2 [Type: Int64 (0)] NHibernate: INSERT INTO Products (Name, Price, Id) VALUES (@p0, @p1, @p2); @p0 = 'Fideos' [Type: String (4000)], @p1 = 0 [Type: Decimal (0)], @p2 = 32768 [Type: Int64 (0)] NHibernate: INSERT INTO Customizations (Name, Id) VALUES (@p0, @p1); @p0 = 'Tuco' [Type: String (4000)], @p1 = 65536 [Type: Int64 (0)] NHibernate: UPDATE Customizations SET ProductId = @p0 WHERE Id = @p1; @p0 = 32768 [Type: Int64 (0)], @p1 = 65536 [Type: Int64 (0)] NHibernate: INSERT INTO CustomizationPossibleValues (CustomizationId, PossibleValue) VALUES (@p0, @p1); @p0 = 65536 [Type: Int64 (0)], @p1 = 'Pocon' [Type: String (4000)] NHibernate: INSERT INTO CustomizationPossibleValues (CustomizationId, PossibleValue) VALUES (@p0, @p1); @p0 = 65536 [Type: Int64 (0)], @p1 = 'Medio' [Type: String (4000)] NHibernate: INSERT INTO CustomizationPossibleValues (CustomizationId, PossibleValue) VALUES (@p0, @p1); @p0 = 65536 [Type: Int64 (0)], @p1 = 'Sopa' [Type: String (4000)]
In this domain we have things like ISet
It is a quite simple example but as you can see ConfORM resolve most of the things *automagicallly although you can customize everything.
There is also an inflector for Spanish and Italian in ConfORM.Shop. You don’t need an inflector to work with ConfORM you can remove all that part and use singular names.
I'll publish this example somewhere soon. But if you need to get into ConfORM I strongly recommend you to see the ConfOrm.UsageExamples and also NHibernate.Mystic.
I did a lot of corrections to this article after published; thank Fabio Maulo very much for doing a deep review.