Jason
Jason

Reputation: 13

Base Class Enum Implemented Differently in Derived Classes

My scenario:

public class EntityBase
{
    public int ID { get; set; }
    [Required()]
    public string Name { get; set; }
    //And this is what is getting me
    //I want a "Type" enum
}

Then derived classes would have different enums that they would assign to Type.

public class AnimalEntity : EntityBase
{
    //Type would have an 'animal type' value: Land, Sea or Air
    //Implementation code would do something like:
    // myAnimal.Type = AnimalType.Land
}

public class PersonEntity : EntityBase
{
    //Type would have a 'person type' value: Doctor, Lawyer or Engineer
    //Implementation code would do something like:
    // myPerson.Type = PersonType.Lawyer
}

public class MonsterEntity : EntityBase
{
    //Type would have a 'monster type' value: Goblinoid, Undead 
}

So, the big question is what am I trying to do, right? I am trying to create a base repository class, which will return entities grouped by type. All my entities will have some kind of "type", and I want to create a generic "group by type".

public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : EntityBase
{
     //Our common GetAsync, GetByIdAsync, and all our other CRUD

     //And then something like this:
    public IEnumerable<GroupedData<string, T>> GetGroupedByType(string searchTerm)
    {
        var entities =
            from s in DbSet
            where (searchTerm == null || s.Name.ToLower().Contains(searchTerm))
            group s by s.Type into g
            select new GroupedData<string, T> { Key = g.Key.ToString(), Data = g };

        return (entities);
    }
}

When T is AnimalEntity, I would get groups Land, Sea and Air with the corresponding entities. For PersonEntity, I would get Doctor, Lawyer, Engineer groups.

If my approach/design is invalid or less than ideal, please let me know.

Upvotes: 1

Views: 174

Answers (2)

Adriano Repetti
Adriano Repetti

Reputation: 67090

Enum (please pardon me) are kind of second class citizens so first thing you may think about will not work:

class EntityBase<T> where T : enum {
    public T Type { get; set; }
}

Unfortunately it doesn't compile, you may then think to replace enum with a base class:

class EntityBase<T> where T : EntityTypeBase {
    public T Type { get; set; }
}

Implementing in EntityTypeBase everything you need to be comfortable with them (== and != operators, IConvertible interface and other boilerplate). It's a lot of code and you'll need also to manage that in EF (otherwise you won't be able to use such property in your queries unless you load everything in memory as objects). You may also force the use of enums (with a run-time check) but this will break SQL code generation in EF.

What's I'd suggest in this case is to use a type EF knows and understand. You may use a string (if you wish so) or an integer (as in this example):

class EntityBase
    public virtual int Type { get; set; }
}

In a derived class:

class AnimalEntity : EntityBase {
    public override int Type { 
        get { return base.Type; }
        set {
            if (!Enum.IsDefined(typeof(AnimalType), value))
                throw new ArgumentException();

            base.Type = (int)value;
        }
    }
}

In this way you still can use PersonType.Layer and AnimalType.Land keeping also a little of type safety. Of course you need to keep your enums in-sync to do not have duplicated values (otherwise group by won't work).

As last please also consider to use...another entity. If you have another table EntityType:

ID    Name          ApplicableTo
0     Laywer        Person
1     Programmer    Person
2     Land          Animal
...

What you have to do in the setter is to check if type is applicable or not and you may have few convenience classes that will group them by type:

public static class PersonType {
    public static EntityType Lawyer { get { ... } }
    public static EntityType Programmer { get { ... } }
}

IMO this is scale better (easier to add new items and you can delegate, in future, some behavior to EntityType items) and it is safer than hard-coded constants (because integrity is granted by DB engine itself). Of course price to pay is extra overhead for the search in the EntityType table (unless you use some caching mechanism).

Upvotes: 1

Patrick Hofman
Patrick Hofman

Reputation: 156978

Two options I can think of:

First, preferably, use a generic type parameter (T in this sample):

public class EntityBase<T>
{
   public T Type {get;set;}
}

Supply that type in the type declaration:

public class AnimalEntity : EntityBase<AnimalEnum>
{ }

Second, if you need more freedom, I usually use a list of string contants:

public class EntityBase
{
   public string Type {get;set;}
}

public static class AnimalTypes
{
    public const string Dog = "dog";
    public const string Cat = "cat";
}

Upvotes: 1

Related Questions