2

I've implemented table per hierarchy Entity Data Model with an abstract Document entity and multiple derived entities (Blog, Page, ...). I have repository interface with method signatures using Document entity like this

public Document Load(Guid firmId, int prettyId)
{
    // the OfType<> can be OfType<Page>, OfType<Blog>, ...
    var instance = (from c in _ctx.Documents.OfType<X>() where c.firm_id == firmId && c.PrettyId == prettyId select c).FirstOrDefault();
    ...
}

I only have one class that implements the repository and it is using Document as the type to return from methods. I don't need custom implementations for different types that derive from Document because the implementation specifics for loading, inserting and updating are the same for all. I just need to identify/provide the type to the methods that we want to work with.

Hopefully you will understand what I mean. Please do not reply with references on how to model the TPH because I've already done that and it is modeled fine.

mare
  • 13,033
  • 24
  • 102
  • 191

2 Answers2

1

I've actually discovered that I don't need runtime detection because I can provide type at compile time in my MVC controllers, which unlike the repository (which is only one) are type specific (I have PageController, BlogController, etc.), like this:

public virtual ActionResult Print(int prettyId)
{
    //Document invoice = _repository.Load(prettyId, _docType);
    Document invoice = _repository.Load<Invoice>(prettyId);
    ...
}

In my repository interface I now have this:

// also, please comment, which one is better, this one?
T Load<T>(int prettyId) where T : Document;
T Load<T>(Guid firmId, int prettyId) where T : Document;

// or this one?
//T Load<T1>(int prettyId) where T1 : Document;
//T Load<T1>(Guid firmId, int prettyId) where T1 : Document;

and in repository implementation I have this:

public T Load<T>(int prettyId) where T : Document
{
    return Load<T>(AppState.FirmId, prettyId);
}

public T Load<T>(Guid firmId, int prettyId) where T : Document
{
    var instance =
        (from c in _ctx.Documents.OfType<T>()
         where c.firm_id == firmId && c.PrettyId == prettyId
         select c).FirstOrDefault();
    instance.FirmReference.Load();
    instance.ClientReference.Load();
    instance.DocumentItems.Load();
    instance.TaxStatementReference.Load();
    return instance;
}

This works and looks kinda nice.

mare
  • 13,033
  • 24
  • 102
  • 191
  • Yup, this is what i do. Generics are a good case when using any type of polymorphism (e.g abstract classes). As for your interface, there is no difference between the first or second (commented out) one. One has a type parameter/constraint of T, other has T1. These are just aliases for the type parameter - they can be anything, makes no difference. In your case, i would name them `TDocument` to be more explicit. As for your implementation, i wouldn't do the `.Load()` for all the types there, as the method signature doesn't dictate it is going to do that. I would do that via extension methods. – RPM1984 Nov 28 '10 at 22:54
  • can you provide a sample on extension methods for reference loading? – mare Nov 29 '10 at 00:24
0

Simple way is to create separate method for each special Document type you want to load or to use switch / case statements with some discriminator as parameter in Load method. I understand you want to avoid such solution. In that case you should try to use reflection because providing generic type parameter at runtime is not possible. Check this answer for calling generic method with reflection. You will invoke the base query with OfType and get the instance of IQueryalbe which will be used in for second part of query with where conditions.

Community
  • 1
  • 1
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670