5
\$\begingroup\$

I have been Googling for a few days and am trying to find the best practices when it comes to using the Repository pattern. But what I found that there are no standards and everyone claims that their approach is the best approach. However, I have come up with this design based on what I have read online.

It it good practice to pass DataContext? Please feel free to correct me and explain.

I'm using MVC 5 with EF 6.

 public interface IRepository<T> where TEntity  : class
 {
        IEnumerable<TEntity> GetUsers();
        IQueryable<TEntity> SearchFor(Expression<Func<TEntity, bool>> predicate);
        TEntity GetById(int id);
        void Save(TEntity model);
        void Delete(int id);
 }

public class HostRepository : IRepository<HostViewModel>
{
    private EntityDataModelContext db = new EntityDataModelContext();

    //
    public IEnumerable<HostViewModel> GetUsers()
    {
        var userList = from u in db.Hosts select u;
        var users = new List<HostViewModel>();

        if (userList.Any())
        {
            foreach (var user in userList)
            {
                users.Add(new HostViewModel() { Id = user.Id,  FirstName = user.FirstName });
            }

        }
        return users;
    }
    //implement rest here......
 }

//I use an IoC framework to inject the dependencies

public class HostController : Controller
{
    public IRepository<HostViewModel> hostRepository { get; set; }

    public HostController(IRepository<HostViewModel> hostRepo)
    {
        hostRepository = hostRepo;
    } 

    public ActionResult Index()
    {
        var users = hostRepository.GetUsers();
        return View(users);
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Please note, SE sites are not forums. Please avoid in-post discussions; feel free to use Code Review Chat instead! :) \$\endgroup\$ Jul 18, 2014 at 20:24

1 Answer 1

7
\$\begingroup\$

The Interface

  • The idea of a generic repository interface is that it should be applicable to all entity types. So GetUsers shouldn't be there, because it's only relevant for a particular type of entity. If you want members specific to an entity type, you should have a second interface which extends the generic one:

    IUserRepository : IRepository<User>
    {
        IEnumerable<User> GetUsers();
    }
    
  • One of the debates you may well have seen is whether it's good practice to have IQueryable members, or to take Expression parameters.

    The pro is that it's much more flexible. Rather than having to have, for example, FilterByStartDate(DateTime date), FilterByEndDate(DateTime date), FilterByUserGroup(int groupId), etc., I can just have FilterBy<T>(Predicate<T> condition). Likewise, by exposing IQueryable, you can call additional LINQ methods and it will all get translated into SQL, which can reduce the amount of work your database has to do, and the amount of data you have to transfer.

    The con is that what you have is a leaky abstraction. It looks like I should be able to feed in any reasonable predicate, but in fact, many of them will throw exceptions at runtime because EF doesn't know how to translate them into SQL. So now the details of how exactly EF- which is supposed to be an abstraction over your database- can turn LINQ queries into SQL leak out of your repository, and every consumer of the repository has to worry about them.

    On balance, my suggestion would be: at first, do NOT expose IQueryable or allow arbitrary Expressions to be passed in. Add specific methods for specific queries. If you ultimately find this isn't adequate, you can refactor by adding methods in.

  • Save is a confusing method. Does it mean insert or update? Generally with Entity Framework, you want an Add(TEntity entity) method and a Save() method. Add should be what inserts a record, and Save should commit any updates you've made to existing items. So, as a toy example, I could do:

    var user = userRepository.GetById(userId);
    DoThing(user);
    user.ThingLastDoneTime = DateTime.Now;
    userRepository.Save();
    

    Note that I don't have to pass the user to Save, because it's actually just calling the DbContext's Save() method.

HostViewModel

This is where I start to get a bit more confused. Why is something called ViewModel being treated as an entity, or stored in a database?

Without knowing more it's hard to comment on what's going on here exactly, but EF is supposed to let you handle Entities- these are essentially domain objects. So a User is a good candidate for an entity. A Host might be, depending on what "host" actually means in this context. But a ViewModel? That seems like extreme mixing of concerns between your UI and your core domain.

Or, possibly what's going on is that your actual entity is a Host, and the repository is not only doing its usual repository business, but also in charge of mapping from an entity to a view model. This is also a problematic mixing of concerns. You may, for example, have a view model which does not have sufficient information to create a new entity from. So how would you handle the Add method for that type? Transformation between entities and view models is a UI concern and should be kept away from the repository pattern, which is a data access/persistence pattern.

HostRepository

As already mentioned, it's very weird that GetUsers should be returning a collection of HostViewModels.

The bulk of that method can be simplified with LINQ, too:

return db.Hosts.Select(user => new HostViewModel() { Id = user.Id,  FirstName = user.FirstName });

The Controller

  • I assume your comment about IoC containers is for us and not really in the code. If it's in the code, it shouldn't be!
  • hostRepository should also be private. Other classes don't need to get it, and shouldn't be allowed to set it except through the constructor.
  • You've shortened hostRepository to hostRepo in the constructor. Generally you shouldn't shorten variable names, especially if- as in this case- they're going to be exposed publically to consumers. To deal with class-level vs method level variables, all of the following are popular:
    • For public class members, use PascalCase (first letter capital) rather than camelCase
    • For private class fields and properties use _camelCase, with a leading underscore.
    • As an alternative to the previous, for private class fields and properties, use camelCase. Then inside methods, prefix them with this.
\$\endgroup\$
2
  • \$\begingroup\$ Ben, will you be able to make a template of Repository/UOF work pattern and upload to github and it will be great help not only to me but others who are looking to follow the best practices. thanks for your help. \$\endgroup\$
    – Nick Kahn
    Jul 18, 2014 at 20:07
  • \$\begingroup\$ @AbuHamzah Regarding a template, I wouldn't consider myself enough of an expert to do that. In answer to your update, other than the comments I made, the design seems fine to me. And for HostViewModel, yes, the repository should be for the Host entity. View models, DTOs and value objects are all different things, so you need to make sure you're clear on what each means. \$\endgroup\$ Jul 18, 2014 at 20:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.