Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Repository Design Question

I am writing an EF4 datalayer for an MVC 2 web application and I need suggestions on choosing inheritance vs. abstract base classes. My repository has worked well following the 'generic repo' structure but now I want to add "Audit" functionality which records everytime a CRUD operation is performed.

This is the contract I've been using so far:

public interface IRepository<T>
{
    void Create(T entity);
    void Update(T entity);
    void Delete(Func<T, bool> predicate);
    T Get(Func<T, bool> predicate);
    IQueryable<T> Query();
}

My repo. implementation looks like this:

sealed class EFRepository<TEntity> : IRepository<TEntity>
    where TEntity : EntityObject
{
    ObjectContext _context;
    ObjectSet<TEntity> _entitySet;

    public EFRepository(ObjectContext context)
    {
        _context = context;
        _entitySet = _context.CreateObjectSet<TEntity>();
    }

    public void Create(TEntity entity)
    {
        _entitySet.AddObject(entity);
        _context.SaveChanges();
    }

    public void Update(TEntity entity)
    {
        _entitySet.UpdateObject(entity);
        _context.SaveChanges();
    }

    public void Delete(Func<TEntity, bool> predicate)
    {
        TEntity entity = _entitySet.Single(predicate);
        _entitySet.DeleteObject(entity);
        _context.SaveChanges();
    }

    public TEntity Get(Func<TEntity, bool> predicate)
    {
        return _entitySet.SingleOrDefault(predicate);
    }

    public IQueryable<TEntity> Query()
    {
        return _entitySet;
    }
}

I want to create the concept of an AuditableRepository<T>. Should I create it like this:

interface IAuditable<T>
interface IRepository<T>
AuditableRepository<T> : IRepository<T>, IAuditable<T>
EFRepository<T> : AuditableRepository<T>

or is it better to have it like this:

interface IAuditable<T>
interface IRepository<T>
EFRepository<T> : IRepository<T>, IAuditable<T>

or even:

interface IAuditable<T>
interface IRepository<T>
AuditableRepository<T> : IRepository<T>, IAuditable<T>
EFRepository<T> : IRepository<T>
AuditableEFRepository<T> : AuditableRepository<T>

Not all of my EFRepositories will need to be audited. How should I proceed?

like image 423
John Avatar asked Sep 10 '26 19:09

John


2 Answers

Here is another possibility (using a Decorator object to add additional functionality to an existing repository):

public sealed class Auditor<T> : IRepository<T>
{
    private readonly IRepository<T> _repository;

    public Auditor(IRepository<T> repository)
    {
        _repository = repository;    
    }

    public void Create(T entity)
    {
        //Auditing here...
        _repository.Create(entity);
    }

    //And so on for other methods...
}

The advantage to using a Decorator to add additional features is that it avoids the combinatorial explosion you began to see when you considered some repositories with auditing, some without, some using EF, some not. This gets progressively worse with every new feature that may or may not apply, often eventually devolving into configuration flags and messy internal branching.

like image 67
Dan Bryant Avatar answered Sep 12 '26 09:09

Dan Bryant


Will it matter whether a repository is auditable or not? Meaning, do you need to know if a repository is an IAuditableRepository or just an IRepository? If not, you could use DI and add a constructor that takes an IAuditor. Then in your repository methods, if an IAuditor is available, you can use it.

sealed class EFRepository<TEntity> : Repository<TEntity>
    where TEntity : EntityObject
{
    ObjectContext _context;
    ObjectSet<TEntity> _entitySet;
    IAuditor _auditor;

    public EFRepository(ObjectContext context) : this(context, null)
    {
    }
    public EFRepository(ObjectContext context, IAuditor auditor)
    {
        _context = context;
        _entitySet = _context.CreateObjectSet<TEntity>();
        _auditor = auditor; 
    }
    public override void Create(TEntity entity)
    {
        _entitySet.AddObject(entity);
        _context.SaveChanges();

        if (_auditor != null)
        {
            // audit
        }
    }

    // etc.
}
like image 26
Jeff Ogata Avatar answered Sep 12 '26 07:09

Jeff Ogata