How to persist an enum property on a record?

Topics: Writing modules
Nov 19, 2012 at 5:59 PM

So I have something like:

 

public class MyRecord
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual Status Status { get; set; }
    }

 

where Status is an enum. I've tried:

 

  public int UpdateFrom4()
  {
      SchemaBuilder.AlterTable("MyRecord",
                                      table => table
                                       .AddColumn<Status>("Status")
                );

            return 5;
    }

 

but no Status column was added to my SQL Server DB.

Developer
Nov 19, 2012 at 11:42 PM

You need to store enums as a string:

public int UpdateFrom4()
  {
      SchemaBuilder.AlterTable("MyRecord",
                                      table => table
                                       .AddColumn<string>("Status")
                );

            return 5;
    }

Nov 20, 2012 at 10:09 AM

Thanks Mr Schoorstra.

Feb 12, 2013 at 10:55 AM
Why are anums stored as Strings? Why thay do not use type inside the Enum?
Coordinator
Feb 12, 2013 at 5:33 PM
The database doesn't know what a .NET enum is.
Feb 12, 2013 at 5:44 PM
Edited Feb 12, 2013 at 5:44 PM
Yes, thats true. It is about ORM. I just wonder why Orchard is using the approach of converting Enum's value to a strings instead of using its numeric interpretation.

If you ever change the Enum name, then your data are no longer valid. Thats' why I always use explicit Enum values:
public enum Cms : int {
    Orchard = 1,
    Sitefinity
....
}
Coordinator
Feb 12, 2013 at 9:47 PM
Sure, no particular reason that I know of.
Aug 8, 2013 at 7:43 AM
Orchard uses fluent nHibernate and its default approach to enums is to store them as strings, which I suspect is why Orchard takes this approach.

Unfortunately, if you're using an enum that is adorned with the [Flags] attribute and you wish to have a column represented by this enum that can store multiple bitwise values, you really need to be able to persist this column as an int.

I'm thinking we could look at providing one of two alternate ways of supporting enums persisted as ints...

We could provide an IntegerEnumAttribute and associated IntegerEnumConvention in the core Orchard\Data\Conventions\ location. This convention could ensure that a property value is stored as an integer if the property is adorned with the [IntegerEnum] attribute. The benefit of this approach is that it is zero risk and would not effect any existing entities that have enum based properties.

Alternatively we could provide a FlagsEnumConvention that automatically caused any enum properties to be persisted as ints if the enum is adorned with the [Flags] attribute. I think this is a nice approach, however there may already be modules out there that persist flags enums (although I would expect this to be very unlikely).

Here's an example of what I've tried and seems to work...any thoughts?
namespace Orchard.Data.Conventions
{
    public class FlagsEnumConvention :
        IPropertyConvention,
        IPropertyConventionAcceptance
    {
        #region IPropertyConvention Members

        public void Apply(IPropertyInstance instance)
        {
            instance.CustomType(instance.Property.PropertyType);
        }

        #endregion

        #region IPropertyConventionAcceptance Members

        public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
        {
            criteria.Expect(x => x.Property.PropertyType.IsEnum && (x.Property.PropertyType.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0));
        }

        #endregion
    }
}
Coordinator
Aug 8, 2013 at 7:57 PM
My lo-tech approach has been to join and split string values. It would sure be nice to have something more automatic.
Aug 9, 2013 at 12:56 AM
Should I submit a patch/pull request for the above FlagsEnumConvention class to go into the framework? It seems to work for me.

A couple of things though...
  1. Should we also have some kind of migration that handles automatically converting string columns to int columns for enum properties that have the Flags attribute set? I'm thinking this would be quite difficult to automate, and a quick browse through the source I couldn't find any Flags enums that are being persisted as content part record fields.
  2. When querying the content manager, I was hoping that I could use bitwise comparisons or the .HasFlag extension method that is now exposed on enums that have the [Flags] attribute. However it seems that nHibernate or somewhere down in the Orchard data layer doesn't really support this...
The following call errors:
_contentManager.Query<MyPart, MyPartRecord>().Where(p => (p.FlagsField & checkFlag) == checkFlag).List()
And the call below always seems to return true (from HasFlag)....
_contentManager.Query<MyPart, MyPartRecord>().Where(p => p.FlagsField.HasFlag(checkFlag)).List()
To work around this, I call .List() on the query first then check the flag on the result, but I suspect this causes the full result set to be returned from the DB, rather than passing the bitwise check into the db query.

The exception I get when attempting to pass a bitwise operation into the Where clause on the content query is below...any ideas?
An unhandled exception has occurred and the request was terminated. Please refresh the page. If the error persists, go back
Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object. at NHibernate.Criterion.SubqueryExpression..ctor(String op, String quantifier, DetachedCriteria dc, Boolean prefixOp) at NHibernate.Criterion.Subqueries.Eq(Object value, DetachedCriteria dc) at NHibernate.Linq.Visitors.WhereArgumentsVisitor.VisitBinaryCriterionExpression(BinaryExpression expr) at NHibernate.Linq.Visitors.WhereArgumentsVisitor.VisitBinary(BinaryExpression expr) at NHibernate.Linq.Visitors.ExpressionVisitor.Visit(Expression exp) at NHibernate.Linq.Visitors.ExpressionVisitor.VisitLambda(LambdaExpression lambda) at NHibernate.Linq.Visitors.ExpressionVisitor.Visit(Expression exp) at NHibernate.Linq.Visitors.WhereArgumentsVisitor.VisitUnary(UnaryExpression expr) at NHibernate.Linq.Visitors.ExpressionVisitor.Visit(Expression exp) at NHibernate.Linq.Visitors.RootVisitor.HandleWhereCall(MethodCallExpression call) at NHibernate.Linq.Visitors.RootVisitor.VisitMethodCall(MethodCallExpression expr) at NHibernate.Linq.Visitors.ExpressionVisitor.Visit(Expression exp) at NHibernate.Linq.Visitors.NHibernateQueryTranslator.Translate(Expression expression, QueryOptions queryOptions) at Orchard.ContentManagement.DefaultContentQuery.Where[TRecord](Expression`1 predicate) in c:\Users\Michael\Documents\Visual Studio 2012\Projects\Orchard.Source.1.6\src\Orchard\ContentManagement\DefaultContentQuery.cs:line 89 at Orchard.ContentManagement.DefaultContentQuery.ContentQuery`2.Orchard.ContentManagement.IContentQuery<T,TR>.Where(Expression`1 predicate) in c:\Users\Michael\Documents\Visual Studio 2012\Projects\Orchard.Source.1.6\src\Orchard\ContentManagement\DefaultContentQuery.cs:line 237 at...etc (my code)
Coordinator
Aug 9, 2013 at 12:59 AM
Pull request sounds good. I don't know about the rest.