21

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

2
  • What happens if you just map it without CustomType? NH should figure things out itself no? Commented Aug 20, 2010 at 15:48
  • @ShaneC - NH would, but FNH behaviour defaults to trying to persist the enum as string. Commented Aug 20, 2010 at 19:43

6 Answers 6

23
+100

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Can I then override this convention for the majority of enums which I wish to be mapped as string?
You certainly can. It's all in the acceptance part: stackoverflow.com/questions/439003/… for more info
Excellent, I've just tried this and it's behaving exactly as I require. Thanks for your help @CatZ !
7

I know I'm late to the party - it's been two years since the question. But since I've stumbled upon this, I might just add solved the problem for me:

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

It did the trick for me: it stopped updating every time.

Source: https://groups.google.com/forum/#!searchin/fluent-nhibernate/enum/fluent-nhibernate/bBXlDRvphDw/AFnYs9ei7O0J

Comments

4

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.

8 Comments

+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...
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).
Thanks guys, tempted to accept this, it seems like a safe and straightforward workaround.
@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.
@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.
|
1

Simple way that worked for me was to change mapping's custom type from int to PersistentEnumType. Make sure to declare a generic version to make your life easier:

public class PersistentEnumType<T> : PersistentEnumType {
    public PersistentEnumType() : base(typeof(T)) {}
}

Then use

Map(x => x.DayOfWeek)
    .CustomType<PersistentEnumType<System.DayOfWeek>>();

This does not require changes to your entities, only mappings, and can be applied on a per-property basis.

See more here.

1 Comment

Nice solution that also works with the Orchard extensibility point ISessionConfigurationEvents. Just add a class that implements ISessionConfigurationEvents.Created() and put defaultModel.Override<MyPartRecord>(mapping => mapping.Map(x => x.DayOfWeek).CustomType<System.DayOfWeek>()) in there and be sure to delete mappings.bin before restarting Orchard.
0

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.

Comments

0

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.)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.