Important note
All that I will explain in the following is based on the assumption that you want to learn some skills that are useful when building large, enterprise-scale applications. In these scenarios, maintainability and extensibility are a main priority. Why? Because if you do not pay attentions to them, accidental complexity can easily blow of proportions and turn your codebase into one big bowl of spaghetti code.
Back to the definition
First, let's revisit the definition of the repository pattern. If you read it closely, it states that its purpose is to contain the querying logic, which in your scenario is done using LINQ-to-SQL (if you were using ADO.NET, your querying logic would be the actual SQL you were going to execute).
The idea behind this pattern is that you don't want the consumers of your repository to know or care what type of storage mechanism and querying/manipulation ways you are using.
All they want and should know is that they have an interface, something like this for example:
public interface IEmployeeRepository
{
IEnumerable<Employee> GetAllEmployees();
Employee GetEmployee(int id);
}
This means that if you introduce IQueryable anywhere in your public interface, this automatically tells them "Hey, although you're talking to an interface, I'll just let you know that it's actually a database you're actually talking to"... which kinda beats the purpose of interfaces and abstractions in general. In this regard, you were right when saying that the Service layer should not have access to the database.
Second, here's a good article to support my opinion that IQueryable is a leaky abstraction - it creates tight-coupling between your application and the storage mechanism.
Back to the interface I defined above, there is a big problem with it - it is very coarse. As per your example, in most some scenarios you do not want to pull all the data related to an employee, nor all the data about all employees, but different subsets of rows and columns each time. But we've just dumped the flexibility given by IQueryable, what is there left?
Bringing only what you neeed
Now since we've established that IQueryable should not be part of your interface's public API, there is only one (that I know of) alternative left to get what you need: a different method for each different subset of employee data you need to fetch.
For example, the scenario you requested in your question would look lie
public interface IEmployeeRepository
{
IEnumerable<BasicEmployeeData> GetBasicEmployeesData(IEnumerable<int> ids);
}
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class EntityFrameworkEmployeeRepository : IEmployeeRepository
{
public IEnumerable<BasicEmployeeData> GetBasicEmployeesData(IEnumerable<int> ids)
{
using (var context = new EmployeeContext())
{
return context.Employees.Where(e => ids.Any(id => id == e)).ToList();
// I haven't tested if .Any works or whether you should instead do .Join
// but this is orthogonal to the main question
}
}
}
If, in the future you will need to pull say, only the id, first name, last name, monthly pay and performance ratings for a couple of employees, all you'll have to do is create a new DTO (EmployeeEvaluation for example), add an interface method returning an IEnumerable of given DTOs and then implement this new method into your EntityFrameworkRepository.
Furthermore, if somewhere even further into the future, you'll want to pull all the data you've got for some employees, your interface might look something like this:
public interface IEmployeeRepository
{
IEnumerable<BasicEmployeeData> GetBasicEmployeesData(IEnumerable<int> ids);
IEnumerable<EmployeeEvaluation> GetEmployeeEvaluations(IEnumerable<int> ids);
IEnumerable<Employee> GetEmployees(IEnumerable<int> ids);
IEnumerable<Employee> GetEmployees();
}
In reply to the comment containing "In my case, I will be building the service layer as well. So, I will be the client of my own repository. So, if that's case, I'm not sure if I should repository pattern at all."
When using the word "client", I mean any piece of code that is calling your repository, be it your own or someone else's. So it can be your service, your controller, your tests OR if you decide to compile and distribute your code as a DLL, anyone else that decides to use it.
But, regardless of who calls that code, they should be isolated from knowing what goes on behind the scenes. This also applies if you own the entire codebase, as is your case. Think about the following scenario:
Sometime in the future, your company decides to buy some fancy software that will help them do their accounting and ditch the relational database. In order to keep your application running, you will also have to abandon any usage of Entity Framework and instead do web service calls to this software. Now, if as you suggested, you've used EF directly in all your services (since you haven't implemented a repository, this is the only remaining option) there will be a lot of places in your Servuce layer where you will have to go, remove all references to EF and convert it to code that uses the new web service. This is called shotgun surgery and the cause is that you are mixing two things that should be separate: data retrieval and data processing.
Had you gone the repository way, the only thing that you would have had to change would have been the repository (data retrieval), whereas all the service logic (processing) would have remained untouched. The beauty of decoupled code :)
With these in mind, I strongly encourage you to actually implement the repository pattern. It really helps you keep things decoupled and in accordance with the S in SOLID.