1

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.

eddie_cat
  • 2,527
  • 4
  • 25
  • 43
Jason
  • 13
  • 2
  • 1
    What's wrong with specifying the enum in the generic? `EntityBase` etc. I'm not sure I'd choose to do that myself to be honest, but it should still be possible. That said, I've no idea how you'd integrate that into the grouping. I personally would not style a design based on trying to feed generic code. I would design stuff simply then identify where common logic resides. For example, instead of making the enum change, why not have each derived repository implement the grouping function? – Adam Houldsworth Oct 27 '14 at 15:53
  • @AdamHouldsworth Unless I'm mistaken it looks like the goal is to have the typed enum of the derived class as a key. Jason, can you confirm this? – samy Oct 27 '14 at 15:57
  • @AdamHouldsworth - I am beginning to come around to your line of thinking. Trying to abstract the group by type may not be enough payoff for all the other hoops. Thanks. – Jason Oct 27 '14 at 16:33

2 Answers2

1

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";
}
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Thanks for the feedback. I had thought of those, but was hoping for something even cleaner. – Jason Oct 27 '14 at 16:32
1

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

Community
  • 1
  • 1
Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • @PatrickHofman yes, it's not always useful but sometimes I'd like to see it! – Adriano Repetti Oct 27 '14 at 16:11
  • Yes, +1 anyway. It isn't your fault ;) – Patrick Hofman Oct 27 '14 at 16:12
  • 1
    @PatrickHofman LOL we should blame Eric for that (actually I wonder if he ever wrote something about _"how we would use generics and why we can't"_). – Adriano Repetti Oct 27 '14 at 16:15
  • where T : enum - doesn't work. Yep. ...and then, but this will break SQL code generation in EF. You covered all the bases and pain points I have been banging my head against. Thanks for the feedback. – Jason Oct 27 '14 at 16:29
  • @Jason yes, unfortunately in `where T : U` (where `U` isn't a _special case_ like `default`, `class`, `new` or `struct`) it must be a base class or an interface. Value types (then `enum`) are out of the game. Actually you may use a base class and write some code to instruct EF to use it but...well does it worth enough? BTW note that with strings as suggested by Patrick you have an easier life (of course price to pay is DB overhead and harder run-time checks) so you may also consider to use them. – Adriano Repetti Oct 27 '14 at 16:33
  • @Jason see update answer, there is also a more DB-oriented solution (in case requirements will grow-up IMO it's better than compile time constants). – Adriano Repetti Oct 27 '14 at 17:03