Mapping RefrencesAny (multiple tables) with fluent nhibernate - fluent-nhibernate

I am trying to follow this post using Fluent NHIbernate: http://blogs.planetcloud.co.uk/mygreatdiscovery/post/Localizing-entities-with-NHibernate.aspx
My test generates the following error:
----> NHibernate.MappingException : An association from the table TranslatedText refers to an unmapped class: .Domain.Localisation.ILocalizedEntity
Any idea how to get NH to honour the interface?
Adding .IncludeBase<ILocalizedEntity>() to my auto model did nothing... (as expected its an interface not an abstract right?)
Mappings: (Question)
mapping.HasMany(m => m.TranslatedTexts)
.AsSet()
.Inverse().Cascade.SaveUpdate()
.KeyColumn("EntityId")
.ForeignKeyConstraintName("none")
.Where("EntityClass = 'Domain.Question'");
TranslatedText (has) public virtual ILocalizedEntity Entity { get; set; }
mapping.ReferencesAny(tt => tt.Entity)
.IdentityType<Guid>()
.EntityIdentifierColumn("EntityId")
.EntityTypeColumn("EntityType");
Interface:
public interface ILocalizedEntity
{
ICollection<TranslatedText> TranslatedTexts { get; set; }
}
I've seen the same in the FNH test suite. I have a feeling it is something to do with the fact I use AutoMapping, but not sure what as yet...
edit
confirmed - using standard ClassMaps instead of automapping, with the same mappings above, works as expected.

I did find a "workaround" which was to remove the classes involved from automapping, and hand-roll them using ClassMaps, really not ideal since I HEART automapping!
sessionFactory = Fluently.Configure()
.Database(sqlConfig)
.Mappings(m => m.AutoMappings.Add(new MyAutoPersistenceModel().GetModel()))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<QuestionMap>())
.BuildSessionFactory();
In MyAutoPersistenceModel:
public override bool ShouldMap(System.Type type)
{
var interfaces = type.GetInterfaces();
return type != typeof(TranslatedText) &&
!interfaces.Any(x => x.GetType() == typeof(ILocalisedEntity)) &&
interfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntityWithTypedId<>));
}

Related

Fluent NHibernate table naming convention not working

I have the following convention which I load into my FNH config
public class TableNameConvention : IClassConvention, IClassConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IClassInspector> criteria)
{
criteria.Expect(x => x.TableName, Is.Not.Set);
}
public void Apply(IClassInstance instance)
{
var tableName = instance.EntityType.Name.Pluralise();
instance.Table(tableName);
}
}
I do not specify table names on any of my mappings, yet this convention is not applied. I'm using Fluent NHibernate 1.4.1.1. Can anyone spot anything I might have done wrong?
UPDATE
The conventions are loaded in the following manner:
public static NHibernate.Cfg.Configuration BuildConfiguration()
{
var connectionStringName = "mydb";
return Fluently.Configure(new NHibernate.Cfg.Configuration())
.Database(MsSqlConfiguration
.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey(connectionStringName))
.Dialect<MsSql2008Dialect>()
.AdoNetBatchSize(50))
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<Profile>();
m.FluentMappings.Conventions.Add(DefaultLazy.Always(), DynamicUpdate.AlwaysTrue(), DynamicInsert.AlwaysTrue());
m.FluentMappings.Conventions.AddFromAssemblyOf<HiLoConvention>();
})
.ExposeConfiguration(config => config.SetProperty(NHibernate.Cfg.Environment.CurrentSessionContextClass, typeof(ManagedWebSessionContext).AssemblyQualifiedName))
.ExposeConfiguration(HiLoConvention.CreateScript)
.ExposeConfiguration(RunSchemaUpdate)
.BuildConfiguration();
}
All conventions sit in the same assembly and namespace as the HiLoConvention referenced above in the .AddFromAssembly() method call.
UPDATE 2:
The problem is in the Accept() method, because if I remove this method (and also the IClassConventionAcceptance interface from the class declaration) then the convention is applied. I have also tried this expectation to no avail
criteria.Expect(x => string.IsNullOrEmpty(x.TableName))
The original code worked with Fluent 1.2.1...
This question is old, but perhaps this can help someone else:
I assume you wanted to set the convention on each entity, unless a table name was specified explicitly in the map. So to achieve that, you can simply do the following:
public class TableNameConvention : IClassConvention
{
public void Apply(IClassInstance instance)
{
var tableName = instance.EntityType.Name.Pluralise();
instance.Table(tableName);
}
}
This will apply the convention on all entities, unless TableName was specified explicitly in the map.
Have you tried
m.FluentMappings.ConventionDiscovery.AddFromAssemblyOf<HiLoConvention>()
in place of
m.FluentMappings.Conventions.AddFromAssemblyOf<HiLoConvention>()

Choose between Auto Mapping and Fluent mapping with Fluent NHibernate (S#arp Architecture)

I have a application using NHibernate Auto Mapping... All working fine so far...
My Fluent Global.asax config:
private void InitializeNHibernateSession()
{
NHibernateSession.Init(
webSessionStorage,
new string[] { Server.MapPath("~/bin/Proj.Data.dll") },
new AutoPersistenceModelGenerator().Generate(),
Server.MapPath("~/NHibernate.config"));
}
But I need to map a class with Fluent mapping... I created the class :
namespace Proj.Data.NHibernateMaps
{
public class CategoryMap : IAutoMappingOverride<Category>
{
public void Override(AutoMapping<Category> mapping)
{
mapping.Id(x => x.Id)
.GeneratedBy.Identity();
mapping.Map(x => x.Description);
mapping.Map(x => x.UrlName);
mapping.References(x => x.ParentCategory)
.Not.LazyLoad();
}
}
}
The problem is that this mapping is never used by the NHibernate... Instead it uses the Auto Mapping generated Category...
How can I use my Fluent Mapping ?
Thanks
Paul
Wherever you're configuring the AutoPersistenceModel you need to reference the mapping overrides. I find the easiest way to do this is to just point it at the assembly containing the mapping overrides and let it discover all of them. That way you can add new IAutoMappingOverride implementations and it will be automatically picked up. You do this using the UseOverridesFromAssemblyOf extension method.
public class AutoPersistenceModelGenerator {
public AutoPersistenceModel Generate() {
return AutoMap.AssemblyOf<Category>()
.UseOverridesFromAssemblyOf<CategoryMap>();
}
}

Why is Fluent NHibernate ignoring my convention?

I have a convention UserTypeConvention<MyUserType> where MyUserType : IUserType where MyUserType handles an enum type MyEnum. I have configured Fluent NHibernate thusly
sessionFactory = Fluently
.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(
c => c.Is(connectionString))
)
.Mappings(
m => m
.FluentMappings
.AddFromAssemblyOf<A>()
.Conventions
.AddFromAssemblyOf<A>()
)
.BuildSessionFactory();
where A is a type in the same assembly as UserTypeConvention<MyUserType> and MyUserType. However, Fluent NHibernate is not applying MyUserType to properties of type MyEnum on my domain objects. Instead, it is applying FluentNHibernate.Mapping.GenericEnumMapper<MyEnumType> to these properties.
What is going on?
For now I have solved this with:
public class MyEnumUserTypeConvention : UserTypeConvention<MyEnumUserType> {
public override void Accept(IAcceptanceCriteria<IPropertyInspector> criteria) {
// Fluent NHibernate is too eager in applying GenericEnumMapper
// so our criteria is that it is already applied this type
criteria.Expect(x => x.Type == typeof(GenericEnumMapper<MyEnum>));
}
public override void Apply(IPropertyInstance instance) {
// we override Fluent NHibernate's application of GenericEnumMapper
instance.CustomType<MyEnumUserType>();
}
}
I think this should be thoroughly unnecessary. If someone told me this were a bug in Fluent NHibernate, that'd be fine. If someone gave me a good reason why Fluent NHibernate should be so eager in applying GenericEnumMapper that would be acceptable too.
Ok i tried the following and I think it will works for you :
just overriede the Accept method in MyEnumUserTypeConvention class and do nothing inside it:
public class MyEnumUserTypeConvention : UserTypeConvention<MyEnumUserType>
{
public override void Accept(FluentNHibernate.Conventions.AcceptanceCriteria.IAcceptanceCriteria<FluentNHibernate.Conventions.Inspections.IPropertyInspector> criteria)
{
///Do nothing
}
}

Is it possible to use a Fluent NHibernate convention to map all ICollections as sets?

Is it possible to use a Fluent NHibernate convention to map all ICollections as sets? I have an entity like so:
public class NoahsArk
{
public virtual ICollection<Animal> Animals { get; set; }
public NoahsArk()
{
Animals = new HashSet<Animal>();
}
}
With fluent mappings, this property would be mapped as HasMany(x => x.Animals).AsSet(), but how would I do this with a convention that I want to use with the automapper?
I should add that by default, ICollections get persisted as ILists, and I get a cast exception when it tries to cast the HashSet to IList.
This isn't possible in a convention, currently. If you want the automapper to treat your collections as sets by default, use ISet instead of ICollection.
In response to Christina's question, you have to create a new class that implements IAutoMappingOverride<T>:
public class AlbumOverride : IAutoMappingOverride<Album>
{
public void Override(AutoMapping<Album> mapping)
{
mapping.HasMany(x => x.Pictures).AsSet().Inverse();
}
}
Then tell FNH to use it in the configuration:
Fluently.Configure()
.Database(...)
.Mappings(m => m.AutoMappings.Add(AutoMap.Assemblies(...)
.UseOverridesFromAssemblyOf<Album>()))
.BuildConfiguration();
You'll need a new override class for every entity you need an override for, but it's mostly a copy and paste affair.

How do you map an enum as an int value with fluent NHibernate?

Question says it all really, the default is for it to map as a string but I need it to map as an int.
I'm currently using PersistenceModel for setting my conventions if that makes any difference.
Update
Found that getting onto the latest version of the code from the trunk resolved my woes.
The way to define this convention changed sometimes ago, it's now :
public class EnumConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType.IsEnum);
}
public void Apply(IPropertyInstance target)
{
target.CustomType(target.Property.PropertyType);
}
}
So, as mentioned, getting the latest version of Fluent NHibernate off the trunk got me to where I needed to be. An example mapping for an enum with the latest code is:
Map(quote => quote.Status).CustomTypeIs(typeof(QuoteStatus));
The custom type forces it to be handled as an instance of the enum rather than using the GenericEnumMapper<TEnum>.
I'm actually considering submitting a patch to be able to change between a enum mapper that persists a string and one that persists an int as that seems like something you should be able to set as a convention.
This popped up on my recent activity and things have changed in the newer versions of Fluent NHibernate to make this easier.
To make all enums be mapped as integers you can now create a convention like so:
public class EnumConvention : IUserTypeConvention
{
public bool Accept(IProperty target)
{
return target.PropertyType.IsEnum;
}
public void Apply(IProperty target)
{
target.CustomTypeIs(target.PropertyType);
}
public bool Accept(Type type)
{
return type.IsEnum;
}
}
Then your mapping only has to be:
Map(quote => quote.Status);
You add the convention to your Fluent NHibernate mapping like so;
Fluently.Configure(nHibConfig)
.Mappings(mappingConfiguration =>
{
mappingConfiguration.FluentMappings
.ConventionDiscovery.AddFromAssemblyOf<EnumConvention>();
})
./* other configuration */
Don't forget about nullable enums (like ExampleEnum? ExampleProperty)! They need to be checked separately. This is how it's done with the new FNH style configuration:
public class EnumConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType.IsEnum ||
(x.Property.PropertyType.IsGenericType &&
x.Property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
x.Property.PropertyType.GetGenericArguments()[0].IsEnum)
);
}
public void Apply(IPropertyInstance target)
{
target.CustomType(target.Property.PropertyType);
}
}
this is how I've mapped a enum property with an int value:
Map(x => x.Status).CustomType(typeof(Int32));
works for me!
For those using Fluent NHibernate with Automapping (and potentially an IoC container):
The IUserTypeConvention is as #Julien's answer above: https://stackoverflow.com/a/1706462/878612
public class EnumConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType.IsEnum);
}
public void Apply(IPropertyInstance target)
{
target.CustomType(target.Property.PropertyType);
}
}
The Fluent NHibernate Automapping configuration could be configured like this:
protected virtual ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(SetupDatabase)
.Mappings(mappingConfiguration =>
{
mappingConfiguration.AutoMappings
.Add(CreateAutomappings);
}
).BuildSessionFactory();
}
protected virtual IPersistenceConfigurer SetupDatabase()
{
return MsSqlConfiguration.MsSql2008.UseOuterJoin()
.ConnectionString(x =>
x.FromConnectionStringWithKey("AppDatabase")) // In Web.config
.ShowSql();
}
protected static AutoPersistenceModel CreateAutomappings()
{
return AutoMap.AssemblyOf<ClassInAnAssemblyToBeMapped>(
new EntityAutomapConfiguration())
.Conventions.Setup(c =>
{
// Other IUserTypeConvention classes here
c.Add<EnumConvention>();
});
}
*Then the CreateSessionFactory can be utilized in an IoC such as Castle Windsor (using a PersistenceFacility and installer) easily. *
Kernel.Register(
Component.For<ISessionFactory>()
.UsingFactoryMethod(() => CreateSessionFactory()),
Component.For<ISession>()
.UsingFactoryMethod(k => k.Resolve<ISessionFactory>().OpenSession())
.LifestylePerWebRequest()
);
You could create an NHibernate IUserType, and specify it using CustomTypeIs<T>() on the property map.
You should keep the values as int / tinyint in your DB Table. For mapping your enum you need to specify mapping correctly. Please see below mapping and enum sample,
Mapping Class
public class TransactionMap : ClassMap Transaction
{
public TransactionMap()
{
//Other mappings
.....
//Mapping for enum
Map(x => x.Status, "Status").CustomType();
Table("Transaction");
}
}
Enum
public enum TransactionStatus
{
Waiting = 1,
Processed = 2,
RolledBack = 3,
Blocked = 4,
Refunded = 5,
AlreadyProcessed = 6,
}