0

I have the following code and would like to know if there is a way to refactor in order to remove duplicated logic.

This results current user with eager loading.

  var currentEmployee = RosterContext.Employees
                    .Where(e => e.User.Id == id)
                    .Include(e => e.Job.Department).FirstOrDefault();

.

var job = RosterContext.Employees.Where(e=>e.Job.Department.Id == currentEmployee.Job.DepartmentId).ToList();

I created another same context which compares the first line of code to result all employee names who work in same department. My question is, as I am using two linq expression that uses the same context (Employees) am i able to combine both linq queries into one? It may become a long linq expression but it should serve on getting the current user object followed by comparing user object to get all employees that share the same department id?

hello world
  • 385
  • 5
  • 24

3 Answers3

0

It makes sense to try an ORM framework, such as Entity Framework or NHibernate. ORM framewok will model database FK relationship as a scalar property on one side and vector property (collection) on the other side of the relationship. For instance Department would have a collection property Jobs, and a Job entity would have a scalar Department property. DB queries with joins on FK become just dependency property navigation, for example - to access the list of employees in current department you would just return something like employee.Department.Employees - that is, assuming your entities are all loaded (which is rather simple to achieve in EF, using include statement)

ironstone13
  • 3,325
  • 18
  • 24
  • I think he is using EF already as he said he's using Code First fluent API – trousyt Mar 06 '16 at 21:15
  • Hi, thanks for your response. I am using EF as Code First with Fluent API. I have tried calling the foreign key property within my variable as you have mentioned i.e. employee.Department.Employees however this returns null for some reason. – hello world Mar 06 '16 at 21:25
  • Navigation properties are not initialized by default, to load them you have to specify this explicitly by using the **include** statement, see this link https://msdn.microsoft.com/en-us/data/jj574232.aspx – ironstone13 Mar 07 '16 at 13:53
0

EF Code First supports relationships out of the box. You can either use the conventions or explicitly specify the relationship (for example, if the foreign key property is named something weird). See here for example: https://msdn.microsoft.com/en-us/data/hh134698.aspx

When you've configured your models right, you should be able to access department like so:

var currentUser = _unitOfWork.Employee.GetEmployeeByID(loggedInUser.GetUser(user).Id);
var job = currentUser.Job;
var department = job.Department;

// or

var department = _unitOfWork.Employee.GetEmployeeByID(loggedInUser.GetUser(user).Id).Job.Department;

To show all employees that work in the same department:

var coworkers = department.Jobs.SelectMany(j => j.Employees);

Update

To use eager loading with a single repository class (you shouldn't need multiple repository classes in this instance, and therefore don't need to use Unit of Work):

public class EmployeeRepository {
    private readonly MyContext _context = new MyContext(); // Or whatever...

    public IList<Employee> GetCoworkers(int userId) {
        var currentEmployee = _context.Employees
            .Where(e => e.UserId == userId)
            .Include(e => e.Job.Department)     // Use eager loading; this will fetch Job and Department rows for this user
            .FirstOrDefault();

        var department = currentEmployee.Job.Department;
        var coworkers = department.Jobs.SelectMany(j => j.Employees);
        return coworkers;
    }
}

And call it like so...

var repo = new EmployeeRepository();
var coworkers = repo.GetCoworkers(loggedInUser.GetUser(user).Id);

You probably would be able to make the repository query more efficient by selecting the job and department of the current user (like I've done) and then the related jobs and employees when coming back the other way. I'll leave that up to you.

trousyt
  • 360
  • 2
  • 12
  • Hi, thanks for your helpful suggestion. I'm not sure if I done something wrong but I used fluent API to configure the Foreign key but when i attempt to call a property that holds reference to the other table it returns null unless I instantiate the other class i.e. var job = currentUser.Job; when debugging I see all the fields are fine for currentUser apart from Job field which is null. This also applied to job.Department where job property has null value for Department. Only way i can achieve not being null is newing up the object i.e. Department = new Department and then pass this variable. – hello world Mar 06 '16 at 21:45
  • Can you post more code to show us your fluent API configuration for the relationships and a snippet of the classes you've made showing the relationship and foreign key properties? – trousyt Mar 06 '16 at 22:18
  • modelBuilder.Entity() .HasMany(e => e.Jobs) .WithRequired(e => e.Department) .HasForeignKey(e => e.DepartmentId).WillCascadeOnDelete(false); This is how i configured with Fluent API. I have done a seed which does output data with FK id to the table. – hello world Mar 07 '16 at 18:44
  • Do your POCO entity classes have both the relationship (navigational) property AND the foreign key property? Also make sure that your navigation property is defined as `virtual` so that EF can inject its lazy load code through its proxy class. See here under "Type Discovery" for an example of what this should look like: https://msdn.microsoft.com/en-us/data/jj679962.aspx. It would also be a good idea to consider using eager loading (like what @Steve-Greene mentioned) after you have this working as you know in advance that you need those relationships available to you. – trousyt Mar 07 '16 at 19:01
  • My POCO are defined as you suggested. I am able to populate the DB with seed and able to see all tables are defined with its PK and FK and have checked data within the DB and its all fine. I got feeling I need to consider using eager loading to ensure related entites are setup. However how do i conduct this if i am using Unit of work rather than using context directly to create my expression. i.e. var t = context.Object.Include() however my unit of work is access via var t = unitOfWork.Object.MethodNameFromRepo(). Im unable to find .include when using unit of work that holds my repository – hello world Mar 07 '16 at 20:14
  • I would advise that you update your main question in order to clue people in to new developments (they probably won't look down here). As for your question about UoW, your UoW class should contain a single DbContext and construct your repositories with that context. Then in your repository, you would make direct use of that context. But as far as I can tell, you only need to have a method on `EmployeeRepository` that will return a list of related `Employee` entities. I'll update my answer to show what I'm talking about. – trousyt Mar 07 '16 at 21:51
  • Ive attempted to do what you mentioned but this only results one data which is the current user. var department holds one data which of course is the current user department info but with var coworkers which uses selectMany this only returns the same current user. The only way around this was adding another context. I used your first line of code var current user with the expression then done the following var job = RosterContext.Employees.Where(e=>e.Job.Department.Id == currentEmployee.Job.DepartmentId).ToList(); This results all employee names that are from the same department. – hello world Mar 11 '16 at 21:05
  • However this is using the same context i.e. Employees, is there a way to combine my linq expression to the var currentUser linq expression, i.e. currentEmployee = _context.Employees .Where(e => e.UserId == userId) .Include(e => e.Job.Department) and then add my previous linq query as it uses the same context ? – hello world Mar 11 '16 at 21:07
  • I'm not positive if this would work, but perhaps you could try something like `var coworkers = _context.Employees.Where(e => e.UserId == userId).Include(x => x.Job.Department.Jobs.SelectMany(y => y.Employees)).Select(x => x.Job.Department.Jobs.SelectMany(y => y.Employees)).ToList();` – trousyt Mar 11 '16 at 23:21
  • I managed to get it working with the following lines of code: var currentEmployee = RosterContext.Employees.Include(e=>e.Job.Department).Where(e=>e.User.Id == id).SelectMany(y=>y.Job.Employee).ToList(); thank you for your help – hello world Mar 12 '16 at 11:57
0

In Entity Framework you have the using clause to attach children. So for example in pure EF you could do:

var department = context.Department.Include(d => d.Jobs).First(d => d.DepartmentId == departmentId);

https://msdn.microsoft.com/en-us/library/gg671236%28v=vs.103%29.aspx#Anchor_1

With a repository, you may need to do something like this:

EF Including Other Entities (Generic Repository pattern)

Community
  • 1
  • 1
Steve Greene
  • 12,029
  • 1
  • 33
  • 54
  • Hi, there i wouldn't able to use Eagerly Loading as you suggested because I am conducting unit of work in order to access my db set rather than directly using context. i.e. var t = unitofwork.Department.MethodsFromRepository(e->e.propertyName) – hello world Mar 07 '16 at 18:55