Nhibernate 3.2 Mapping By Code - Version (Concurrency) - nhibernate

I am trying to migrate a hbm.xml based nhibernate project to mapping by code. I am having problem getting the Version section working. In the hbm.xml i have:
< version name="Version" column="Version" type="Int32" unsaved-value="0"/>
I have tried with the following mapping:
Version(x => x.Version, m =>
{
m.Column(c =>
{
c.SqlType("Int32");
c.Name("Version");
});
m.Generated(VersionGeneration.Always);
m.UnsavedValue(0);
m.Insert(true);
m.Type(new NHibernate.Type.Int32Type());
});
But nothing seems to produce the same mapping as the original hbm.xml, they all end up without the type="Int32". Has anyone got any ideas how I can do this, or if its supported in 3.2?
Cheers
Update:
See my answer

Following Toni comment I have changed my mapping to:
Version(x => x.Version, mapper =>
{
mapper.Generated(VersionGeneration.Never);
mapper.UnsavedValue(0);
mapper.Type(new NHibernate.Type.Int32Type());
});
Which prove to work as my original hbm.xml

If the property Version is already type of int32 then that is not inserted into the hbm file. I think the type part is only written into the xml file if the actual types are different. Example (domain entity uses int32 but we want to map it using int64):
// in the domain entity
public int RowVersion{get;set;}
// Mapping
this.Version(x => x.RowVersion, mapper =>
{
mapper.Generated(VersionGeneration.Never);
mapper.UnsavedValue(0);
mapper.Type(new NHibernate.Type.Int64Type());
});
// Xml file
<version name="RowVersion" type="Int64" unsaved-value="0" />

Related

Converting to mapping-by-code causes major performance issues

I have an NHibernate project which uses mapping config files. I'm using an SQL Server database.
I want to switch to mapping by code. My approach is to do this one class at a time, confirming that all of the tests pass with each changeover.
Mixing the two mappings is quite straighforward:
public static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
var configuration = new Configuration();
configuration.Configure();
configuration.AddAssembly(typeof(Entities.Player).Assembly);
var mapper = new NHibernate.Mapping.ByCode.ModelMapper();
// Here are the entities I've switched to mapping-by-code
DATMedia.CMS.EntityLibrary.Mappings.ScheduleMediaItem.Map(mapper);
DATMedia.CMS.EntityLibrary.Mappings.Schedule.Map(mapper);
configuration.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
_sessionFactory = configuration.BuildSessionFactory();
}
return _sessionFactory;
}
}
However, when I changed over the Schedule mapping to being mapping-by-code, I encountered major performance issues. Calls to Session.Flush would take 12 seconds, and this is over a trivial amount of test data.
I switched back to the XML mapping, and the performance problems went away.
Has anyone else experienced the problem?
I'll include the before and after mappings for schedule, just in case there's an obvious defect:
Via the config file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="DATMedia.CMS.EntityLibrary.Entities.Schedule, DATMedia.CMS.EntityLibrary" table="cms_Schedule">
<id name="Id" column="Id" type="Int64">
<generator class="identity" />
</id>
<property name="Date" column="Date" type="Timestamp" />
<many-to-one name="SourceTemplate" column="SourceTemplate_Id" class="DATMedia.CMS.EntityLibrary.Entities.ScheduleTemplate, DATMedia.CMS.EntityLibrary" cascade="none" fetch="join" lazy="proxy"/>
<!--
Note that we are not using cascading deletes here.
This will be handled by SQL Server through ON DELETE CASCADE foreign key constraints
-->
<bag name="MediaItems" inverse="true" cascade="save-update" lazy="true" order-by="PlayIndex">
<key column="Schedule_Id" />
<one-to-many class="DATMedia.CMS.EntityLibrary.Entities.ScheduleMediaItem, DATMedia.CMS.EntityLibrary"/>
</bag>
<bag name="PlayerGroups" table="cms_ManyToMany_PlayerGroupSchedules_PlayerGroup_Schedule" lazy="true" cascade="save-update">
<key column="Schedule_Id" />
<many-to-many column="PlayerGroup_Id"
class="DATMedia.CMS.EntityLibrary.Entities.PlayerGroup, NHibernateManyToMany" />
</bag>
</class>
</hibernate-mapping>
And the mapping by code:
public static void Map(ModelMapper mapper)
{
mapper.Class<DATMedia.CMS.EntityLibrary.Entities.Schedule>(
classMapper =>
{
classMapper.Table("cms_Schedule");
classMapper.Id(x => x.Id, map =>
{
map.Column("Id");
map.Generator(Generators.Identity);
});
classMapper.Property(
s => s.Date,
propertyMapper =>
{
propertyMapper.Column("Date");
propertyMapper.NotNullable(true);
}
);
classMapper.ManyToOne(
s => s.SourceTemplate,
manyToOneMapper =>
{
manyToOneMapper.Column("SourceTemplate_Id");
manyToOneMapper.Cascade(Cascade.None);
manyToOneMapper.Fetch(FetchKind.Join);
manyToOneMapper.Lazy(LazyRelation.Proxy);
}
);
classMapper.Bag(
s => s.MediaItems,
bagPropertyMapper =>
{
bagPropertyMapper.Key(keyMapper =>
{
keyMapper.Column("Schedule_Id");
}
);
bagPropertyMapper.Inverse(true);
bagPropertyMapper.Cascade(Cascade.Persist);
bagPropertyMapper.Lazy(CollectionLazy.Lazy);
bagPropertyMapper.OrderBy(smi => smi.PlayIndex);
}
);
classMapper.Bag(
s => s.PlayerGroups,
bagPropertyMapper =>
{
bagPropertyMapper.Key(keyMapper =>
{
keyMapper.Column("Schedule_Id");
});
bagPropertyMapper.Table("cms_ManyToMany_PlayerGroupSchedules_PlayerGroup_Schedule");
bagPropertyMapper.Lazy(CollectionLazy.Extra);
bagPropertyMapper.Cascade(Cascade.Persist);
},
collectionElementRelation =>
{
collectionElementRelation.ManyToMany(manyToManyMapper =>
{
manyToManyMapper.Column("PlayerGroup_Id");
}
);
}
);
}
);
}
Later edit
I made the problem go away by not calling Flush within a transaction.
I've tried to create some simpler test code to replicate the problem, but have been unsuccessful (all my test code runs really fast, no matter how many times I call Flush). This could be because I converted some entitys' key generation from Identity to HiLo.
So it seems in my case, my code was creating a particular configuration which brought about a problem, and hopefully this won't come back to haunt me.
If I guessed, I would say the configuration that causes problems involves a combination of thoughtless use of long running transactions combined with Identity key generation.
I have used mixed mappings in quite a few projects and have not experienced any problems as you describe. I cannot see why the flush would take 12 seconds.
My technique for mixed mappings is slightly different to yours and I am not 100% sure if the order you configure matters, its worth a shot. See http://puredotnetcoder.blogspot.co.uk/2011/07/mixed-mappings-with-hbm-and-mapping-by.html
I take it you have you exported ALL the mappings and double checked that they ARE the same before and after. See http://puredotnetcoder.blogspot.co.uk/2011/07/view-xml-generated-when-mapping-by-code.html

Fluent NHibernate (1.2.0.712) export mappings to HBM not working / not respecting conventions

The HBM export function in Fluent NHibernate does not seem to work.
If I call FluentMappingsContainer.ExportTo, the generated mappings come out incorrect, and I get the following exception:
FluentNHibernate.Cfg.FluentConfigurationException: An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
My configuration code looks like this:
MsSqlConfiguration database = MsSqlConfiguration.MsSql2008
.ConnectionString(GetConnectionString())
.Cache(c => c
.UseQueryCache()
.UseSecondLevelCache()
.ProviderClass<SysCacheProvider>()
);
database.ShowSql();
FluentConfiguration config = Fluently.Configure()
.Database(database)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Entity>()
.Conventions.AddFromAssemblyOf<Entity>());
config.ExposeConfiguration(x =>
{
x.SetProperty("hbm2ddl.keywords", "auto-quote");
x.SetInterceptor(new ServiceInterceptor());
});
config.ExposeConfiguration(x => { x.SetProperty("current_session_context_class", "thread_static"); });
// Configure HBM export path, if configured:
var path = Service.Config.HbmExportPath;
if (!String.IsNullOrEmpty(path))
config.Mappings(m => m.FluentMappings.ExportTo(path));
// Build session factory:
_sessionFactory = config.BuildSessionFactory();
Setting HbmExportPath in my configuration to null, app launches and runs without problems. As soon as I configure the export path (causing ExportTo to be called), the generated mappings cause an exception as described above.
Looking at the exported mappings, it appears my conventions are not being applied - for example, I have a foreign-key convention in place, using camel-case and "Id" suffix, but when I export the HBM files, primary keys are consistently named with an underscore and lowercase "_id", for example:
<class xmlns="urn:nhibernate-mapping-2.2" name="MyApp.Entities.Contact, MyApp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Contact`">
...
<bag name="Departments" table="ContactDepartment">
<key>
<column name="Contact_id" />
</key>
<many-to-many class="MyApp.Entities.Department, MyApp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<column name="Department_id" />
</many-to-many>
</bag>
...
</class>
I had this problem with the previous version and with the current release of Fluent.
Any ideas?
After drilling through the Fluent source code (latest from Git repository), something looks odd to me.
The ExportTo() methods are defined twice - once by FluentConfiguration itself, and it does appear it's exporting the configuration files "too soon", resulting in an incomplete configuration, both at run-time (resulting in the above exception) and at export-time.
Oddly, the PersistenceModel type does have the capability to export the complete configuration, but this feature is not exposed. Instead, there are at least two other seemingly broken implementations of ExportTo().
To solve the problem, we need access to the PersistenceModel instance, which has the capability to write the complete configuration - fortunately, I found a way to do that:
// create a local instance of the PersistenceModel type:
PersistenceModel model = new PersistenceModel();
FluentConfiguration config = Fluently.Configure()
.Database(database)
.Mappings(m => m.UsePersistenceModel(model) // use the local instance!
.FluentMappings.AddFromAssemblyOf<Entity>()
.Conventions.AddFromAssemblyOf<Entity>());
// ...
var path = Service.Config.HbmExportPath;
_sessionFactory = config.BuildSessionFactory(); // completes the configuration
// now write out the full mappings from the PersistenceModel:
if (!String.IsNullOrEmpty(path))
model.WriteMappingsTo(path);
The HBM files are now being output correctly!

Error: fluent NHibernate mapping which references mapping in different assembly

My company has multiple sites which reference the same DB and Core library of code.
Then we have a CMS which manages the data.
In the Core library I have a Site class which holds a bunch of basic info about each site. This is the fluent mapping I have for it.
using Core.Model; // Where Site class exists
namespace Core.Repository.NHibernate.Mappings
{
public class SiteMapping : ClassMap<Site>
{
public SiteMapping()
{
Table("f_site");
Id(x => x.Id, "f_site_id").GeneratedBy.Identity();
Map(x => x.Domain, "domain_name").Not.Nullable();
}
}
}
As part of the CMS, we keep a log of who edited what and when. But I want to only have a reference to the Log class and mapping in the CMS, rather than in my core code, as people can only edit info via the CMS.
Here is my current fluent mapping to the Log class, which referneces the Site class.
using Core.Model; // where Site class exists
using Cms.Model; // where Log and CmsUser classes exists
namespace Cms.Repository.NHibernate.Mappings
{
public class LogMapping : ClassMap<Log>
{
public LogMapping()
{
Table("f_log");
Id(x => x.Id, "f_log_id").GeneratedBy.Identity();
Map(x => x.Message, "message");
Map(x => x.LogType, "d_log_type_id").CustomType<LogType>();
Map(x => x.LogOperation, "d_log_operation_id").CustomType<LogOperation>();
Map(x => x.Date, "log_date");
References<Site>(x => x.Site, "f_site_id")
.ForeignKey("f_site_id")
.Cascade.None();
References<CmsUser>(x => x.User, "userId")
.ForeignKey("userId")
.Cascade.None();
}
}
}
In theory this works great, but the Log mapping errors with the following
Cms.Tests.Repository.NHibernate.Repository.TestLogRepository.TestLogMappings:
StructureMap.StructureMapException : StructureMap Exception Code: 207
Internal exception while creating Instance 'e46153a3-2bfe-4279-8749-a42d7a6dd10c' of PluginType Core.Repository.NHibernate.SessionStorage.ISessionContainer`1[[HbmCms.Repository.NHibernate.Mappings.Config.LogMapping, Cms.Repository.NHibernate, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Core.Repository.NHibernate, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null. Check the inner exception for more details.
----> StructureMap.StructureMapException : StructureMap Exception Code: 207
Internal exception while creating Instance '9e72c2ff-e3f4-4b54-9f34-3422a7b982a7' of PluginType Core.Repository.NHibernate.SessionStorage.ISessionFactoryContainer`1[[Cms.Repository.NHibernate.Mappings.Config.LogMapping, Cms.Repository.NHibernate, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]. Check the inner exception for more details.
----> FluentNHibernate.Cfg.FluentConfigurationException : An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
----> NHibernate.MappingException : An association from the table f_log refers to an unmapped class: Core.Model.Site
Does anyone have any idea how to get my CMS mapping to reference the Core Site mapping? This is the first bit of code that is getting a mapping across two projects, but its something that we'll be doing a fair bit of, as lots of stuff you only look at and do in the CMS. I don't really want to put the CMS only code into the Core library if i can avoid it.
Thanks for the help
Sandra
An 'unmapped class' error usually stems from the Configuration not having that ClassMap recorded.
In your FluentConfiguration, you probably have something similar to:
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<T>())
It appears that your two ClassMaps (at least those mentioned in the post) are in different assemblies. You can specify multiple assemblies with:
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<T1>()
.AddFromAssemblyOf<T2>())
To see exactly what is being mapped, you can add
.Diagnostics(d => d.Enable().OutputToConsole())
to your FluentConfiguration and it will return the class maps, the conventions applied to them and why/why not they were valid.

Mixing DB configuration from file with Fluent NHibernate mapping

I stumbled upon the following problem: I wanted to configure the DB from config file but the mappings fluently (love it!) The configuration code looks like this:
var cfg = new Configuration();
cfg.Configure();
var fluentCfg = Fluently.Configure(cfg)
.Mappings(
m => m
.FluentMapping
.AddFromAssembly(Assembly.GetExecutingAssembly));
However the config file has a property:
<property name="proxyfactory.factory_class">
NHibernate.ByteCode.LinFu.ProxyFactoryFactory,
NHibernate.ByteCode.LinFu
</property>
and after the cfg.Configure(); all looks good the configuration points to the LinFu bytecode provider BUT after the third line I see the configuration changed to using Castle. I looked in the Fluent's code and I might be wrong but it looks like they are overriding this property in PersistenceConfiguration.cs(line 50) in the constructor of PersistenceConfiguration:
values[ProxyFactoryFactoryClassKey] = DefaultProxyFactoryFactoryClassName;
Does Fluent require Castle? Or may be I am doing something wrong or maybe this is just a bug?
Thank you.
I don't know if this is what you're looking for, but it might help you out. You can expose the configuration and make any changes that you need to, in code.
var cfg = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("ConnectionStringName")).ShowSql())
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<MapMarker>();
m.FluentMappings.Conventions.AddFromAssemblyOf<ConventionMarker>();
})
.ExposeConfiguration(x => x.SetProperty("proxyfactory.factory_class", "NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu"));
There's a ProxyFactoryFactory method chained off Configure.
Fluently.Configure()
.ProxyFactoryFactory(name);
If you're not on 1.2, I believe it's under the Database call (see RexM's answer).

Registering fluent nhibernate mappings for all assemblies in an application

Starting with some code:
sessionFactory = Fluently.Configure(cfg)
.Mappings(m =>
{
List<Assembly> allAssemblies = new List<Assembly>();
string path = Assembly.GetExecutingAssembly().Location;
foreach (string dll in Directory.GetFiles(path, "*.dll"))
{
m.FluentMappings.AddFromAssembly(Assembly.LoadFile(dll));
}
})
.BuildSessionFactory();
I'm very new to both nhibernate and fluent nhibernate. The bit of code above looks like it should work, but to me it looks really ugly. Is there a neater way?
One problem I have is that the code that calls the above code is in a core assembly and is unable to make reference to some of the assemblies that need mapping as their assemblies reference the core assembly. So I can't just use a few calls to AddFromAssemblyOf<T>.
Is there a cleaner way to do this?
You should be managing your SessionFactory initialization from the application itself, so your original code would work just fine.
I handle this by creating an NH config base class that does what you were originally attempting to do. I then sublcass that from within my app and do all the bootstrapping there.
You could create a custom configuration node to put in your config files.
You would have something like the following:
<configSections>
<section name="fluentConfigurationsSection" type="MyCoreAssembly.FluentConfigurationsSection, MyCoreAssembly"/>
</configSections>
<fluentConfigurationsSection>
<fluentConfigurations>
<clear />
<add name="Assembly1" assembly="MyAssemblyNotReferencedByCoreAssembly.Mapping.Fluent"
<add name="Assembly2" assembly="AnotherAssemblyNotReferencedByCoreAssembly.Mapping.Fluent"
<add name="Assembly3" assembly="OneMoreAssemblyNotReferencedByCoreAssembly.Mapping.Fluent"
</fluentConfigurations>
</fluentConfigurationsSection>
Then your code could be changed to something like:
sessionFactory = Fluently.Configure(cfg)
.Mappings(m =>
{
foreach(var config in MethodToGetFluentConfigSectionItems())
{
//load each assembly in config file
m.FluentMappings.AddFromAssembly(Assembly.Load(config.Assembly);
}
})
.BuildSessionFactory();
To create the custom config section you could see here how to do it.
Hope this helps.