开发者

C# Repository Design Question

开发者 https://www.devze.com 2023-01-25 15:47 出处:网络
I am writing an EF4 datalayer for an MVC 2 web application and I need suggestions on choosing inherita开发者_运维技巧nce vs. abstract base classes. My repository has worked well following the \'generi

I am writing an EF4 datalayer for an MVC 2 web application and I need suggestions on choosing inherita开发者_运维技巧nce 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?


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.


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


Neither feels like a perfect solution, as Dan says you might face a problem with different combinations of the interfaces. Audit features doesn't sound like it really needs to be part of the class either. I think rather than going with Dan's approach though that I'd declare and fire events in general on the repository. That way you could hook up lots of different things to it, much more flexible. So declare events like Created, Deleted etc.

In the future it also allows some really nice functionality through things like reactive extensions.


First of all, if your Repository<T> doesn't have any common functionality (you only have abstract methods and no implementation there), the first thing I would do is get rid of it. It is best that all other parts of your program access the repository through the IRepository<T> interface only, that will give you greater freedom later.

Second thing, having an EFRepository class implies that you want to leave an option to switch to a different ORM one day. My opinion is - don't do it. If you chose EF, stick to EF. If you really think this is an option, at least create a separate assembly with a namespace MyApp.EntityORM or whatever, put all your Repository classes there and get rid of the EF prefix. Then, if it ever comes to that, you can perhaps load the right ORM through dependency injection some day, without changing the rest of your code.

From my experience: there can be subtle differences between ORM's which prevent you from interchanging them in a truly transparent way, and there is little chance you will need to ever change your ORM for a single project.

Second thing, I also prefer wrapping base functionality of a base repository in a different class (the Decorator pattern as Dan Bryant already said), as opposed to overriding an already existing class. The reason is that overriding often leads to having to deal with internals of the base class, and this can sometimes get a bit messy.

From your question, it is not completely clear how an "auditable repository" should behave, but if it is just a plain IRepository<T> (it audits all methods implemented in a IRepository<T>), then you don't need a separate interface:

interface IRepository<T>
class Repository<T> : IRepository<T>
class AuditableRepository<T> : IRepository<T>

Adding to what Dan said, you could have a factory method which would create the appropriate wrapper:

class AuditableRepository<T> : IRepository<T>
{
     public static IRepository<T> CreateFrom(IRepository<T> baseRepo)
     { 
          // wrap base repo in an auditable repository
          // and return it
          return new AuditableRepository<T>(baseRepo);
     }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消