Created
January 7, 2015 11:45
-
-
Save grofit/94e4d9d2abf518574bc9 to your computer and use it in GitHub Desktop.
A GOOD Generic Repository Pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Account | |
{ | |
Guid Id {get;set;} | |
string Name {get;set;} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DatabaseRepository<T> : IRepository<T> | |
{ | |
private IDbConnection _connection; | |
public DatabaseRepository(IDbConnection connection) | |
{ _connection = connection; } | |
// Crud is a given | |
public IEnumerable<T> Find(IFindQuery<T> query) | |
{ return query.Find(_connection); } | |
public void Execute(IExecuteQuery<T> query) | |
{ query.Execute(_connection); } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This allows you to not have to fudge all your logic into your repository classes. | |
// So this stops individual Repositories like PlayerRepository : SomeRepository<Player> with custom GetAllPlayers methods | |
public GetAllPlayersInAccountQuery : IFindQuery<Player> | |
{ | |
public Guid AccountId {get; private set;} | |
public GetAllPlayersInAccountQuery(Guid accountId) | |
{ AccountId = accountId; } | |
public IEnumerable<Player> Find(IDbConnection connection) | |
{ | |
using(var playerQuery = connection.GetCollection<Player>()) | |
{ return playerQuery.Where(x => x.AccountId == AccountId); } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IExecuteQuery<T> | |
{ | |
void Execute(IDbConnection connection); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IFindQuery<T> | |
{ | |
IEnumerable<T> Find(IDbConnection connection); // this can be abstracted further to reduce IDbConnection | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IRepository<T> // Possibly add K for key | |
{ | |
T Get(int id); // you may want to use K as a generic for the key | |
void Update(T entity); | |
void Save(T entity); | |
void Delete(T entity); | |
IEnumerable<T> Find(IFindQuery<T> query); | |
void Execute(IExecuteQuery<T> query); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Player | |
{ | |
Guid AccountId {get;set;} | |
Guid Id {get;set;} | |
string Name {get;set;} | |
PlayerType Type {get;set;} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SomeClass | |
{ | |
private IRepository<Player> _playerRepository; | |
public SomeClass(IRepository<Player> playerRepository) | |
{ | |
_playerRepository = playerRepository; // or if you want to ignore IoC new DatabaseRepository(new SomeConnection()); | |
} | |
public void GetPlayers() | |
{ | |
var currentAccount = // Get Account Somehow | |
var getAllPlayersInAccountQuery = new GetAllPlayersInAccountQuery(currentAccount.Id); | |
return _playerRepository.Find(getAllPlayersInAccountQuery); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Overview
As I end up showing this to a lot of people I thought I should put some comments on as to why this is better than a conventional
IRepository
approach.Common Generic Repository Pattern
So most Generic Repository Pattern approaches tell you to make an
IRepository
then implement a specific repository for each model or logical need you have, i.eUserRepository
,SkillsRepository
,GroupsRepository
etc. Then within each of these repositories you end up with a method for each set of data you want back, i.e:So in this scenario you can have loads of methods all doing different things and everyone is happy, i.e
userRepository.GetUsersWhoAreAdmins()
anduserRepostory.GetAllUsers()
etc however this slowly becomes a dumping ground and there is also one HUGE downside to this approach.Downsides Of This Pattern
As all the logic for getting data from the underlying data source (be it a database, in memory list, xml file etc) is contained at the repository level, if you want to have a method in another repository access the logic you need to have one repository access another, i.e.
So this in best case causes interdependencies between repositories, and at worst case can cause circular dependencies which are going to cause you a big headace. You may be luck and you never need to share logic between repositories, or you may just copy the query logic over which isnt very DRY of you, but it will at least get the job done.
So the problems with the original design that is often touted is:
So Why This Pattern?
This pattern solves the issues above as we only really need an implementation per data source and then each specific bit of query logic becomes an
IFindQuery
orIExecuteQuery
, which makes them easier to re-use across other repositories if needed as you can just new them up and use them within other queries.If you had lots of logic in your repositories in the old world, you would end up with lots of queries and it now becomes a namespace separation issue rather than a repository separation issue.
Improvements?
This was thrown together 7 years ago on the fly to just show a possible approach, I have omitted a very useful feature like
ISpecificQuery<T>
or whatever you want to call it where it can return data separate to the repository generic, i.e:This allows you to basically return anything from any repository, which can be useful for more specific queries which need to do very specific stuff.
You can also potentially split into
IReadRepository
andIWriteRepository
too, which would allow separation between the logic of reading and writing.Ultimately the only downside of this approach is potentially you have a new file per query, vs one file (the old repository) containing all logic, but your DI configuration becomes:
As you can see its always the same repository just a different generic, which literally is only there to enforce the
Find
generic type.