I'm converting a previous project from using normal NHibernate hbm.xml mappings to Fluent NHibernate. Currently, I'm stuck on what should be one of the last steps to getting this working. I've added a derived class for DefaultAutomappingConfiguration to modify my ID naming convention. The string "Id" is appended to the class name:
public override bool IsId(FluentNHibernate.Member member)
{
return member.Name == member.DeclaringType.Name + "Id";
}
This should make "Agency" have an ID in a field named "AgencyId". Instead, I'm getting this error:
The entity 'ClassMap`1' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id).
{Name = "ClassMap`1" FullName = "FluentNHibernate.Mapping.ClassMap`1[[BackendDb.Model.Agency, BackendDb, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"}
I made a breakpoint on the IsId function to see what's going on:
{Property: Cache}
{Name = "ClassMap`1" FullName = "FluentNHibernate.Mapping.ClassMap`1[[BackendDb.Model.Agency, BackendDb, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"}
What is this? The object is not something I've created. Every other object passes through this function fine, and the ones I actually wanted to map are returning the proper value.
My Session factory looks something like this:
var cfg = new MapConfig();
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(m => m.Server(@".\SqlExpress")
.Database("{some dbname}")
.TrustedConnection()))
.Mappings(m =>
m.AutoMappings
.Add(AutoMap.AssemblyOf<Agency>(cfg))
)
.BuildSessionFactory();
Annoyingly, it seems that this somehow caused the three tables I was testing Fluent NHibernate on in my dev database to be emptied. What the hell?
The sessionfactory is trying to automap all classes in the assembly that contains your Agency
class based on this directive: Add(AutoMap.AssemblyOf<Agency>(cfg))
. Since you have an AgencyMap
in the assembly andClassMap<>
does not have an Id
property, FNH is throwing an error.
If you want to use ClassMap<>
configurations, instead of (or in addition to) declaring an automapping configuration, declare a fluent mapping:
m.FluentMappings.AddFromAssemblyOf<Agency>();
If you don't need AutoMappings, remove the `.AutoMappings.Add' directive.
However, if you want to use AutoMappings, you need to tell FNH what classes you want to map. To handle this, I usually define a marker interface:
public abstract class Entity : IPersistable
{
public virtual int Id { get; set; }
}
public interface IPersistable
{
}
Then, in the class that I derive from DefaultAutomappingConfiguration
, I tell FNH to only map the classes that have that interface (you can limit the mapped classes however you see fit):
public class EntityAutoMappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
return type.GetInterfaces().Contains(typeof (IPersistable));
}
}
To handle the primary key mapping, I create a convention class:
public class PrimaryKeyNamePlusId : IIdConvention
{
public void Apply(IIdentityInstance instance)
{
instance.Column(instance.EntityType.Name+"Id");
}
}
Finally, I configure my SessionFactory to use the configuration/convention classes:
m.AutoMappings.AssemblyOf<Entity>(new EntityAutoMappingConfiguration())
.IgnoreBase<Entity>()
.UseOverridesFromAssemblyOf<Entity>()
.Conventions.AddFromAssemblyOf<Entity>();
You can't use ClassMap
in combination with the automapper unless you also configure the automapper to ignore the Entites for which you are using ClassMap and their respective mapping files.
In my case, I happen to use a custom attribute to indicate classes which should be automapped, so I can toss all sorts of garbage I don't want mapped into my .dll without having Fluent try to automap it:
/// <summary>
/// Add this attribute to entity classes which should be automapped by Fluent.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
class AutomapAttribute : Attribute
{
}
And in my DefaultAutomappingConfiguration
override class:
public override bool ShouldMap(Type type)
{
return (type.Namespace == "Data.Entities"
&& type.GetCustomAttributes(typeof(AutomapAttribute), false).Length > 0);
}
Of course, you don't need to check for an attribute if you just keep your automapped entities in a different namespace from other classes.