As part of my “Crafting Wicked Domain Models” talk, I walk through the concept of enumeration classes, yanked from Java and on Jon Skeet’s list of biggest C# mistakes (or missing features). In my talk, I leave out how to bridge the gap from your domain model to an ORM, simply because it’s just out of scope for that talk to address persistence concerns. Besides, the ORM I use these days to persist relational domain models (NHibernate) handles all the crazy cases an more, so I don’t feel like looking at anything else.
But what I leave out of a talk, I can certainly blog about! Suppose we have one of our enumeration classes (available here on NuGet):
When it comes time to persisting this enumeration class, we want to make sure that the database schema uses the integer value as what gets persisted. When it writes, we want the value to persist, and when it reads, we want the correct enumeration value (Red/Blue) to be hydrated.
To do this in NHibernate, we’ll first need a custom type:
This class is the bridge between our ORM (NHibernate) and our enumeration class. NHibernate is fantastic in its ability to provide easy ways to bridge to value objects. Value objects help avoid primitive obsession, but is only useful if you can actually use them when you’re mapping to your persistence layer.
To instruct NHibernate to use our custom types when reading/writing, the easiest way to do so is with a Fluent NHibernate convention:
This convention walks the type hierarchy for each property type given, and checks to see if the property type’s type hierarchy is a generic type that closes the open generic type of our enumeration class. Finally, we just need to hook our convention up to Fluent NHibernate, but that really depends on how we have Fluent NHibernate hooked up. We don’t have to use Fluent NHibernate to hook up our custom user type, but it’s much easier this way.
With NHibernate, we get the benefits of using Java-style enumeration classes, and have it seamlessly plug in to our persistence layer, which is the whole point of ORMs, right?
I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
Cool. Why just not to create table for enumeration class?
Anonymous
We do sometimes – but that has its drawbacks. It requires extra queries and sometimes joins. We limit those cases to ones where we have user-supplied information, rather than static information.
We have pushed out DB tables from our enumeration classes, too. Making our C# code the reference and updating DB tables as needed (for SQL-centric reports etc).
Daniel Marbach
Because it’s NOT an entity
Anonymous
Hi Jimmy, (have just watched your video)
if you’re into this kind of thing you should take a look at my Harden library ( https://github.com/mcintyre321/Harden ) – it lets you add validation rules and guard rules to objects cleanly, very much keeping logic out of the Views and Services, and keeping it in the domain objects.
You write can rules like this:
public virtual string Email { get; set; }
public bool AllowEmail()
{
return Context.CurrentUser.IsAdmin || Context.CurrentUser == this;
//only admins can see or change other peoples emails
}
and when someone tries to set the property, the HardenInterceptor checks the rule.
Normally have to put this rule into the setter, but this way you can also interrogate the rule form other code, for example when deciding whether or not to show the email field in the form.
It has a totally pluggable extension system, so you can define rules in standalone classes, or in attributes or anything really.
I like to use the following simple solution to having enums mapped as integers, using only a small FluentNHibernate mapping convention:
public class EnumConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria criteria)
{
criteria.Expect(n => n.Property.PropertyType.IsEnum);
}
public void Apply(IPropertyInstance instance)
{
// Map using the integral representation of the enum value.
instance.CustomType(instance.Property.PropertyType);
}
}
Further, it’s important to assign explicit integral values to our enum, to ensure that the integral representation of the enum values stays the same when a new value is added for example:
enum MyEnum
{
FirstValue = 1,
SecondValue = 2
}
Ofcourse, this is no more safe than enums are in C# but it suits me well.
http://hammerproject.com/ matt kocaj
And the HBM mapping example? (as much as I’d love the freedom to be using Fluent on this project)
Anonymous
Ugly, no?
Why no Fluent NHibernate? It’s really easy to migrate/have both in one project.
Никита Говоров
Probably better to use ‘typedef’ to reduce count of full qualified type name strings:
…
http://hammerproject.com/ matt kocaj
And this does or does not create a lookup table for the
TEnumeration items? Or is it the same as my SO example (http://stackoverflow.com/a/10426960/56145), where it simply tells NHibernate how to link the index/value field?
cbp
Just a question – why did you use PrimitiveType instead of IUserType?
Anonymous
Copy-Paste from other projects, really. It wasn’t an intentional decision one way or the other, other than it seemed that PrimitiveType had less to implement out of the box than IUserType.
Carlos Alberto Costa Beppler
Hello, I have to map soma classes that are almost the same of this.
The difference is that in my case I have some prefedined values, but the user can add more values to the database (the enumeration is an entity).
There are any way to do this using NH?
Anonymous
Yep – but the easiest way is to have all of the values in the DB, and just model it such that some are “built in” and can’t be deleted/modified through your app, and some are custom. We usually use a “System” or “BuiltIn” bit flag to indicate this on that table.
http://twitter.com/jbasilio Jose Basilio
How do I use Fluent NHibernate to map this enumeration as a string in SQL? In other words, using your example, how do I get it to persist as “Red” or “Blue” instead of 1 or 2?
Mattias Jöraas
I also need a way to map my custom enumeration classas as string, how do i do this. In some cases we have the data mapped as int and some as string.
Antonio Castro Jr
Jimmy,
Does it work with Entity Framework 4.1 (Code First) ?
Thiago Saadeh
I straight copy pasted your samples and hooked it up to FH the conventions and ran it to see where we would go from there, this is what I got:
The entity ‘Color’ doesn’t have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id).
Jamie Wijaya
It’s kind for mapping a “descriptor pattern” class?
I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1097