Showing changes from revision #2 to #3: Added | Removed
Now lets look at how you can do some deeper customisations of the Auto Mapping tool.
We're going to use the same domain as before, but with a few extensions.
public class Product { public int Id { get; private set; } public virtual string Name { get; set; } public virtual string Description { get; set; } public virtual decimal Price { get; set; } public virtual ReplenishmentDay ReplenishmentDay { get; set; } } public class Shelf { public virtual int Id { get; private set; } public virtual IList<Product> Products { get; private set; } public virtual string Description { get; set; } public Shelf() { Products = new List<Product>(); } } public class ReplenishmentDay { public static readonly ReplenishmentDay Monday = new ReplenishmentDay("mon"); /* ... */ public static readonly ReplenishmentDay Sunday = new ReplenishmentDay("sun"); private string day; private ReplenishmentDay(string day) { this.day = day; } }
We've extended our domain with a Description
and a ReplenishmentDay
for the Product
; the replenishment day is represented by a type-safe enum (using the type-safe enum pattern). Also there's a Description
against the Shelf
too (not sure why you'd have a description of a shelf, but hey, that's customers for you). These changes are mapped against the following schema:
table Product ( ProductId int identity primary key, Name varchar(250), Description varchar(2000), Price decimal, RepOn int, Shelf_FK int foreign key ) table Shelf ( ShelfId int identity primary key, Description varchar(2000) )
Now, if you've been following along you'll remember that we made all strings default to 250; and yet the new description columns are 2000 characters long. The customer has stipulated that all descriptions of anything in the domain will always be 2000 or less characters, so lets map that without affecting our other rule for strings.
.WithConvention(convention.ConventionDiscovery.Setup(s => {convention.AddTypeConvention(new DescriptionTypeConvention());s.Add<DescriptionTypeConvention>();convention.DefaultStringLength = 250;s.Add<DefaultStringLengthConvention>(); // other conventions });
We're using the Fluent NHibernate's
Conventions ITypeConventionsupport now,again, which allowsallow you to override anything in the mappingmappings ofbefore propertiesthey're that have a specific type. The AddTypeConventionmethod takes a ITypeConventioninstance and applies thatgiven to every property that gets mapped.NHibernate. Baring in mind that our convention in this case is for a stringproperty, and only for onesstring properties that are called "Description", lets see how the DescriptionTypeConvention
is declared.
public class DescriptionTypeConvention:ITypeConventionIPropertyConvention { public boolCanHandle(TypeAccept(IPropertytype)propertyMapping) { returntypepropertyMapping.PropertyType ==typeof(string);typeof(string) && propertyMapping.Property.Name == "Description"; } public voidAlterMap(IPropertyApply(IProperty propertyMapping) {if (propertyMapping.Property.Name != "Description") return;propertyMapping.WithLengthOf(2000); } }
It's fairly expressive of what it does, but I'll cover it for completeness. The
specifies two methods: ITypeConventionIPropertyConventionbool
and CanHandle(Type)Accept(IProperty)void
. AlterMap(IProperty)Apply(IProperty)
should be implemented to return CanHandleAccepttrue
for typesproperties that you want this convention to deal with; this can be handled in any way you want, you could check the name, or it's ancestry, but in our case we just check whether it's a string.string and if it has the correct name.
is where the bulk of the work happens; this method gets called for every property mapping that has a type that AlterMapApply
returns CanHandleAccepttrue
for. We've implemented
to AlterMapApplyfirstly check if the property is called "Description" (if it isn't, we do nothing) and then alter the length of the property. Simple really. To learn more about conventions, see the Conventions page.
With a simple implementation like this, we're able to map every Description property (that's a string
) so that it has a length of 2000, all with an addition of only one line to our auto mapping configuration.
The other alteration to our domain was the addition of the ReplenishmentDay
. There were two interesting things to consider for this change. Firstly, it's stored in an int
column, which obviously doesn't match our type; and secondly the column is called RepOn
, which we mustn't change. We're going to utilise NHibernate's IUserType
to handle this column.
For the sake of this example we're going to assume you've got an
IUserType
calledReplenishmentDayUserType
, but as it's beyond the scope of this post I won't actually show the implementation as it can be quite lengthy. It's best to just assume that theIUserType
reads anint
from the database and can convert it to aReplenishmentDay
instance. There's a nice example of implementingIUserType
on Jakob Andersen's blog.
So how do we tell Fluent NHibernate to use an IUserType
instead of the specified type? Easy, with another [[Conventions|convention], this time we'll use an ITypeConventionIUserTypeConvention., or more specifically we'll derive from the connivence base-class UserTypeConvention
that Fluent NHibernate supplies.
.WithConvention(convention.ConventionDiscovery.Setup(s => {convention.AddTypeConvention(new DescriptionTypeConvention());s.Add<DescriptionTypeConvention>();convention.AddTypeConvention(new ReplenishmentDayTypeConvention());s.Add<ReplenishmentDayTypeConvention>();convention.DefaultStringLength = 250;s.Add<DefaultStringLength>(); // other conventions });
Here's how our new ReplenishmentDayTypeConvention
looks:
public class ReplenishmentDayTypeConvention:ITypeConventionUserTypeConvention<ReplenishmentDayUserType> { publicbool CanHandle(Type type) { return type == typeof(ReplenishmentDay); } publicoverride voidAlterMap(IPropertyApply(IProperty propertyMapping) {propertyMapping .CustomTypeIs<ReplenishmentDayUserType>() .ColumnName("RepOn");base.Apply(propertyMapping); propertyMapping.ColumnName("RepOn"); } }
AsThe youactual cansetting see,of the user type is handled by the base Apply
implementation, and if we handleweren't anyalso ReplenishmentDaytypes, and then supply a IUserTypeusing the CustomTypeIs<T>()method, and overridesetting the column name withthen ColumnName(string).we Again,wouldn't easy!need to override that member at all.
So that's it, with those conventions we're able to keep our standard rule that all strings should be 250 characters or less, unless they're a Description, then they can be 2000 or less. Replenishment days use our type-safe enum, but are persisted to an int
in the database, which also has a custom column name.
What next? Auto Mapping Altering Entities