views:

141

answers:

4

I am trying to map an enum property (instance of System.DayOfWeek) in my model to an integer database field. Other enum properties in the model should be mapped to strings, so I don't wish to define a convention.

I understand that this should be possible using a fluent mapping like:

Map(x => x.DayOfWeek).CustomType<int>();

and indeed, at first glance this appears to be working.

However, I noticed that instances of entities with properties mapped in this way are being updated every time the session was flushed, even though no amendments have been made to them.

To find out what is causing this flush, I set up an IPreUpdateEventListener, and inspected the OldState and State of the entity. See the attached image. In the OldState, the relevant object is an int, whereas in State it is a DayOfWeek.

If I use an HBM XML mapping with no type attribute specified, this issue doesn't arise.

So...

Is this a bug or shortcoming in the GenericEnumMapper? Is there any way of telling the FNH mapping not to specify any type attribute on the generated HBM? If not, can I specify the default type that NH uses for enums (and what is that)?

alt text

+1  A: 

One workaround that I use is to have an int backing field and letting NHibernate use that for mapping.

Whenever NHibernate has to do a cast to compare the new value with the old one - it is always marked as dirty - causing the flush.

Goblin
+1: Just have a public facing property that represents the enum value, then cast to/from int on your backing field - that's how I'd do this...
DanP
I don't like this idea. Then you have knowledge of the persistence mechanism within the object. And I thought the CustomType<> method was for mappings in which you provide an type inheriting from IUserType? I might be wrong. I would try removing the CustomType mapping and let the convention based GenericEnumMapper do it's job. The only thing I don't like about GenericEnumMapper is that it's susceptible to reordering of the entries in the enum (assuming you don't set values, which I don't).
Rich
Thanks guys, tempted to accept this, it seems like a safe and straightforward workaround.
Ian Nelson
@RichC the convention based GenericEnumMapper fails in this case, as the convention is to persist as string but I want to override and persist as integer for this field.
Ian Nelson
@Rich: You are right - it is a workaround and persistence is leaking into the object. In an ideal world I wouldn't want this, but I can live with it, as it at least doesn't break encapsulation. Same reason I accept having DataBase-Id's inside the object.
Goblin
Another viable option would be to create an IUserType that did the conversion transparently; that would meet the goal of PI
DanP
I would use a IUserType class. I had to in my case because I am nHibernating an existing schema and each enum value is being stored as a string (but not necessarily the same as the enum value I want to use). So I developed a base generic class to handle simple one-to-one translations between POCO value and DB value. Then I inherit it for each enum type and provide the mappings and configure the nHibernate mapping. This also also me to keep the DI.
Rich
A: 

It depends on if you need to have DayOfWeek specifically as an integer.

If you're casting as part of the mapping, equality will always fail, and the property will be marked as dirty.

I'd probably map:

Map(x => x.DayOfWeek).CustomType();

and create a read only property that presents the DayOfWeek value as an integer if it's really required. Regardless, mapping as the actual type should work and prevent false-dirty.

DavidWhitney
A: 

You might consider an alternate approach; I have found the usage of Fabio Maulo's well known instance types to be invaluable for such uses. The benefit of these is immediately apparent any time you find yourself trying to extend what a basic enum can do (eg. providing a localized description, etc.)

DanP
+5  A: 

If you use my enum convention you don't have that problem.

public class EnumConvention : IPropertyConvention, IPropertyConventionAcceptance
{
    public void Apply(IPropertyInstance instance)
    {
        instance.CustomType(instance.Property.PropertyType);
    }

    public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
    {
        criteria.Expect(x => x.Property.PropertyType == typeof(AddressType)  ||
            x.Property.PropertyType == typeof(Status) ||
            x.Property.PropertyType == typeof(DayOfWeek));
    }
}

You can then map your property like regular:

Map(x => x.DayOfWeek);

EDIT : Updated the convention to pick specific enums to use for the int conversion. All enums that are not checked here would be mapped as string. You might have to experiment a little with what to actually test against. I am unsure if the propertytype will do it directly.

mhenrixon
Can I then override this convention for the majority of enums which I wish to be mapped as string?
Ian Nelson
You certainly can. It's all in the acceptance part: http://stackoverflow.com/questions/439003/how-do-you-map-an-enum-as-an-int-value-with-fluent-nhibernate/2716236#2716236 for more info
mhenrixon
Excellent, I've just tried this and it's behaving exactly as I require. Thanks for your help @CatZ !
Ian Nelson