2
\$\begingroup\$

I'm trying to make a reusable data layer that is mostly generated from a database via T4 templates. On top of this data layer, I'd like to have WebAPI endpoints for each repository, with most of the basic CRUD operations generated/from a base class. The generics are somewhat getting out of hand, and I don't know if I'm trying to build too much of a swiss army knife here.

Starting at the bottom, I have an entity class (some bits truncated):

public class Entity<T>
{
    public T Id { get; set; }
}

A T4 template runs a SQL statement to pull out database schema, and spits out a file with a bunch of classes along the lines of:

public partial class Visit : Entity<int>
{
   //Fields on the table
}

Generated along side these entity classes are corresponding repositories, all implementing this interface:

public interface IRepository<TEntity, in TKey> where TEntity: Entity<TKey>
{
    Task<IList<TEntity>> GetAll();
    // Other Methods
}

These get filled out with proper SQL statements for the action. Specific repositories can have additional methods as needed.

This bit I'm happy with, it's easy to understand, and the code generators for it aren't complicated. Where it starts getting complicated is when I move up into the WebAPI Controllers.

All data access is handled from a Unit of Work, which handles connection and transaction lifetimes. Currently, there are individual properties for each repository, and this is done via a T4 template:

public partial class RulesWorkUnit : ITransactionWorkUnit
{
    private readonly Lazy<VisitRepository> _Visit;
    public RulesWorkUnit(DbConnectionFactory connectionFactory)
    {
        _Visit = new Lazy<VisitRepository>(() => new VisitRepository(_conn, _tran));
    }

    public VisitRepository Visit { get { return _Visit.Value; } }
}

This part I also like, and is what I've been doing for a bit for data access, and works well for the cases where I'm coding by hand.

Now, here's where it starts going off of the rails a bit. I have a base API Controller class. This needs to know which repository to create, so that goes in as a generic parameter. This parameter needs to go down to the Work Unit so it knows which repository to return, that results in this code added to the work unit:

//Member variable
private Dictionary<Type, Func<dynamic>> _repositoryTypes;

//Added to constructor
_repositoryTypes[typeof(VisitRepository)] = () => new VisitRepository(_conn, _tran);

//New Method
public TRepository GetRepository<TRepository, TEntity, TKey>()
        where TRepository : class, IRepository<TEntity, TKey>
        where TEntity : Entity<TKey>
    {
        return _repositoryTypes[typeof (TRepository)] as TRepository;
    }

This is where the generic definitions start exploding. The base API controller is one worse, since it also needs to know the model to map to:

public abstract class RepositoryApiController<TRepository, TModel, TEntity, TKey> : ApiController
    where TRepository : class, IRepository<TEntity, TKey>
    where TEntity : Entity<TKey>

Which starts setting off my code smell alarms. I would like somehow to manage the boilerplate code for most of the basic API methods (they'll all have a Get All, Get By Id, Post, Put, Delete that have the same code, with the exception of the entity type and the return model type).

Is there a better way of designing this?

\$\endgroup\$
  • 4
    \$\begingroup\$ Are you using an ORM? It would be nice to see some implementations, and the truncated bits too (including the class bodies). \$\endgroup\$ – Mathieu Guindon Jun 3 '14 at 16:54
2
\$\begingroup\$

It looks like you're basically writing Entity Framework. If so...why not use Entity Framework?

It also looks like in addition to your rewriting of Entity Framework, you're writing the whole generic repository pattern + unit of work pattern that people like to add on top of it. I would strongly recommend against that. It ends up being a lot of extra code and very little or negative benefit, considering that EF/your ORM pretty much uses the repository pattern anyway (if you have a DataContext structured like EF's).

On top of all of that, you're also writing your own custom dependency injection/IOC container now with your static dictionary. The generic type problems you have are based on your unit of work problem. See my answer on this CR post for details.

Instead of writing everything from scratch, I would recommend using vanilla EF with no repository pattern, along with an already-existing IOC container, and wire up DI the plain old vanilla way the Web API site recommends or the way Mark Seemann recommends if you're opinionated about DI.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.