For example the 'ICanDelete<T, in T>' trait exposes a delete-by-key method, hence (as it is stated in a comment) it can't be used in 'IRepositoryBase<T>'. Then we got a 'ICrudRepository<T, T>' which is basically 'IRepository<T, T>' except without query methods.
My suggestions to overcome this design issue:
# Splitting up traits
Technically, the traits could be split up to a even more fine-granulared level in order to re-use them in the desired places. E.g.
public interface ICanDelete<T> {
void Delete(T entity);
}
public interface ICanDelete<T, TKey> :
ICanDelete<T>
{
void Delete(TKey key);
}
This would also be applyable for the compound key repositories. A major drawback might be that we end up with like 40 different, often meaning-less traits, which can be a pain for maintenance.
# Dropping traits
While I'm absolutely in for the SRP, it can cause sometimes a real PITA. As the repository pattern becoming more and more just an abstraction over the top of a ORM framework, it should be considered dropping the traits completely in favor of simplicity. I know there's still a difference between the repository and a ORM object context, however this is only valid if we use the repository pattern completely by exposing meaningfull interfaces that follow the ubiquitous language, which is not possible with stuff like 'Generic Repositories' that also support batching, caching, mapping and what not. So what's left? As I said before: Just a clean approach to abstract away the quirks of a ORM with the possibility to swap out an entire framework.
One should be able to make a 'All-In-One' repository which can then be used along with the adapter pattern and DI like this:
public UserRepository : IUserRepository {
private readonly IGenericRepository<User> _genericRepo;
protected UserRepository(IGenericRepository<User> genericRepo) {
_genericRepo = genericRepo;
}
// intention-telling interface!
public User FindOldestUser() {
return _genericRepo.Find.... //
}
}
What do you guys think?