areller
areller

Reputation: 5238

Abstraction with repository pattern

I am building an application (a web api to be specific) and I want to implement the repository pattern to abstract the data access layer and prepare it for future changes.

My goal is to make the repositories interfaces abstract enough to be able to implement every technology on top of them, starting from Native SQL Client (running sql command) to orm's like EF or dapper.

I have read some articles about repositories and the interface of my generic repository looks something like that:

interface IRepository<T>
{
    IEnumerable<T> FindAll();

    IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);

    T FindById(int id);

    void Add(T entity);

    void Remove(T entity);
}

I want the method FindBy to accept a linq expression because the other option is making it accept native sql and that won't work too well with technologies like linq to entity of EF.

The problem is that i also want to be able to implement a native sql repository on top of this interface and in order to implement a native sql repository, i need to run sql command, strings. In this interface i don't accept any sql command as string, i accept linq expressions, and the native sql client can't handle linq expressions (as far as i know).

So my question is, how can i make this interface be compatible with any technology/orm/library/client/adapter, you get the idea...

Thanks, Arik

Upvotes: 3

Views: 1619

Answers (1)

Backs
Backs

Reputation: 24903

Adbstracting repository is a good idea. I'll try to help you. First of all, your repository must independent of persistance, so you need to remove method FindBy(Expression<Func<T, bool>> predicate), rather replace it with some kind of specification pattern:

interface IRepository<T>
{
    IEnumerable<T> FindAll();
    IEnumerable<T> FindBy(ISpecification<T> specification);
    T FindById(int id);
    void Add(T entity);
    void Remove(T entity);
}

public interface ISpecification<T>
{
    IEnumerable<T> Execute(DbContext context);
}

So, now we have independent repository. And then you can create implementation of specification and repository for linq-supported persistance, it can looks like this:

public class LinqRepository<T> : IRepository<T>
{
    private readonly DbContext _context;

    public LinqRepository(DbContext context)
    {
        _context = context;
    }

    public IEnumerable<T> FindBy(ISpecification<T> specification)
    {
        return specification.Execute(_context);
    }
    //and others...
}

public class LinqSpecification<T> : ISpecification<T>
{
    private readonly Expression<Func<T, bool>> _predicate;

    public LinqSpecification(Expression<Func<T, bool>> predicate)
    {
        this._predicate = predicate;
    }

    public IEnumerable<T> Execute(DbContext context)
    {
        return context.Set<T>().Where(_predicate).ToList();
    }
}

And call like this:

IRepository<Person> repository = new LinqRepository<Person>(dbContext);
var adults = repository.FindBy(new LinqSpecification<Person>(p => p.Age > 18));

Another side, when you need to implement non-linq repository, it can look like this:

public class SqlRepository<T> : IRepository<T>
{
    private readonly DbContext _context;

    public SqlRepository(DbContext context)
    {
        _context = context;
    }

    public IEnumerable<T> FindBy(ISpecification<T> specification)
    {
        return specification.Execute(_context);
    }
}

public class SqlSpecification<T> : ISpecification<T>
{
    private readonly string _query;

    public SqlSpecification(string query)
    {
        _query = query;
    }

    public IEnumerable<T> Execute(DbContext context)
    {
        return context.ExecuteSql<T>(_query);
    }
}

And call:

IRepository<Person> repository = new SqlRepository<Person>(dbContext);
var adults = repository.FindBy(new SqlSpecification<Person>("SELECT * FROM Persons WHERE Age > 18"));

Of couse, it's not ideal variant, but it depends on your system architecture and other components. But it can be used as background.

Upvotes: 1

Related Questions