Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Opinion requested: for static values, is it better to use Enums or Entities?

I'm trying to solve a dilemna that has been nagging me for the last few months.

My colleagues and I completely disagree on a technical problem, and I would like our beloved community's opinion on the matter.

In a nutshell:

Is it preferable to use enumerations (with potentially attributes on them such as "Description"), or use entities (with Name and Description properties)?

In details:

In our domain model, we've got a lot of mini-entities which only contain an Id, a Name, and a Description. 95% of the time, the Description is equal to the Name.

For the sake of the explanation I'm going to take one of the many example: in our Security entity, we have an AssetClass property. An AssetClass has a static list of values ("Equity", "Bond", etc.) and does not change from the interface or anything else.

The problem with that, is when you want to get all securities with an asset class of "Bond" say, NHibernate will have to join on the AssetClass table... and considering AssetClass is not the only property like that, you can imagine the performance impact of all those joins.

Our current solution: (which I disagree with):

We have in our code some hard-coded instances of AssetClass with all their respective values and Ids (i.e. Equity with Id of 1, Bond with Id of 2 etc.), which match what's in the database:

public partial class AssetClass
{
    static public AssetClass Equity = new AssetClass(1, "Equity", "Equity");
    static public AssetClass Bond = new AssetClass(2, "Bond", "Bond");
    static public AssetClass Future = new AssetClass(3, "Future", "Future");
    static public AssetClass SomethingElse = new AssetClass(4, "Something else", "This is something else");
}

We also made a special NHibernate type (code below if you are interested) that allow us to avoid NHibernate doing a join by loading that hard-coded instance instead of going to the database to get it:

using System;
using System.Data;
using System.Data.Common;
using NHibernate.Dialect;
using NHibernate.SqlTypes;
using NHibernate.Type;

namespace MyCompany.Utilities.DomainObjects
{
    public abstract class PrimitiveTypeBase<T> : PrimitiveType where T : class, IUniquelyNamed, IIdentifiable
    {
        private readonly PrimitiveTypeFactory<T> _factory;

        public PrimitiveTypeBase() : base(SqlTypeFactory.Int32)
        {
            _factory = new PrimitiveTypeFactory<T>();
        }

        public override string Name
        {
            get { return typeof(T).Name; }
        }

        public override Type ReturnedClass
        {
            get { return typeof(T); }
        }

        public override Type PrimitiveClass
        {
            get { return typeof (int); }
        }

        public override object DefaultValue
        {
            get { return null; }
        }

        public override void Set(IDbCommand cmd, object value, int index)
        {
            var type = value as T;
            var param = cmd.Parameters[index] as DbParameter;
            param.Value = type.Id;
        }

        public override object Get(IDataReader rs, int index)
        {
            return GetStaticValue(rs[index]);
        }

        public override object Get(IDataReader rs, string name)
        {
            return GetStaticValue(rs[name]);
        }

        private T GetStaticValue(object val)
        {
            if (val == null)
            {
                return (T)DefaultValue;
            }

            int id = int.Parse(val.ToString());
            T entity = _factory.GetById(id); // that returns, by reflection and based on the type T, the static value with the given Id

            if (entity == null)
            {
                throw new InvalidOperationException(string.Format("Could not determine {0} for id {1}", typeof (T).Name, id));
            }
            return entity;
        }

        public override object FromStringValue(string xml)
        {
            return GetStaticValue(xml);
        }

        public override string ObjectToSQLString(object value, Dialect dialect)
        {
            var type = value as T;
            return type.Id.ToString();
        }
    }
}

My solution: (which I agree with :-))

Replacing those entities by enums, and if we ever need a Description field, use an attribute. We could also have a constraint on the database to make sure you can't store random values that don't match an enum.

Their rational against using enumerations:

  • This is not an object, so you can't extend it, it's not object oriented, etc.
  • You can't get the description easily, or have "proper english" names (with spaces or symbols) such as "My Value" (which on an enum would be "MyValue")
  • Enums sucks
  • Attribute sucks

My rational against our current solution:

  • We can have a mismatch between the ids in the code and what's in the database
  • It's a lot harder to maintain (we need to make absolutely sure that every hard-coded values we have are also there in the database)
  • Attributes and enums don't suck if used properly and for static values like these
  • For "proper english" names, we can also use an attribute, with some extension method to consume it.

Now, what do YOU think?

like image 426
Antoine Jaussoin Avatar asked Aug 19 '11 08:08

Antoine Jaussoin


People also ask

Why enums are better than constants?

Enums limit you to the required set of inputs whereas even if you use constant strings you still can use other String not part of your logic. This helps you to not make a mistake, to enter something out of the domain, while entering data and also improves the program readability.

When should you use enums?

You should use enum types any time you need to represent a fixed set of constants. That includes natural enum types such as the planets in our solar system and data sets where you know all possible values at compile time—for example, the choices on a menu, command line flags, and so on.

Should I use enums or strings?

They are different types of data. Enums are way more efficient memory-wise. Use enums rather than strings, in this case. The difference in performance may be hardly noticeable but there is no reason to waste performance when you don't have to.


2 Answers

Personally I prefer the first solution, possibly with an additional property which returns all the values.

It's much more OO - enums are basically "named numbers", that's all. Everywhere else in code, state is stored in properties - so why use attributes instead? As Eric Lippert wrote in a blog post comparing the two, "Attributes are facts about the mechanisms". You're basically using them as a way of providing data about values instead, and that just feels wrong to me.

Your first two objections to using POCOs in terms of the potential mismatch between code and the database doesn't ring true either - because they're exactly the same for enums as well, aren't they? Besides, it's very easy to write a test which validates the data - you could even do so on startup if you wanted.

It's not clear what the rest of your AssetClass does, but if it only has a private constructor, then you get a lot of the benefits of enums in terms of a limited, well-known set of values, but without the problem that enums are basically just numbers.

In fact, the POCO solution is better than the enum in terms of value limitation - because the only "invalid" POCO value is null, whereas it's easy to come up with an invalid enum value:

FooEnum invalid = (FooEnum) 12345;

Are you going to check for that everywhere? Generally null references go bang considerably earlier than invalid enum values, and are also easier to check for.

One downside I can think of in the POCO approach is that you can't switch on it. There are ways around that, but they're not terribly pleasant - you'd basically have to also have a set of well-known numbers (which could be an enum, of course) so that some property would return that and you could switch on it.

like image 62
Jon Skeet Avatar answered Sep 21 '22 14:09

Jon Skeet


I don't really like either options because of potential mismatch of ID's between the code and database. You actually pointed this out, but for some reason seem to think this issue will disappear if you used enums, when in actual fact you'll have the exact same thing?

If I had to choose between the two I'd pick the current implementation for the reasons Jon Skeet has mentioned.

However, I think the best approach is just create a normal class and fetch the items from the database, and program against attributes. I wrote this a while ago to explain what I mean: Program against attributes

like image 45
David Masters Avatar answered Sep 21 '22 14:09

David Masters