Question

I'm not sure where to put my specific queries for business objects.

When we start using multiple table specific queries for the repository pattern, where should these be placed? Service Layer or Repository?

For Example Please see below:

Ex:

class HR_Repository<T> : IRepository<T> where T : class
{
private readonly  LoginDataContext dataContext;
public HR_Repository(LoginDataContext dataContext)
{
this.dataContext = dataContext;
}
public void Commit()
{
    dataContext.SubmitChanges();
}

public IList<T> FindAll()
{
    var table = this.LookupTableFor(typeof(T));
    return table.Cast<T>().ToList();
}

public IQueryable<T> Find()
{
    var table = this.LookupTableFor(typeof(T));
    return table.Cast<T>();
}

public void Add(T item)
{
    var table = this.LookupTableFor(typeof(T));
    table.InsertOnSubmit(item);
}   

public void Delete(T item)
{
    var table = this.LookupTableFor(typeof(T));
    table.DeleteOnSubmit(item);
}

private ITable LookupTableFor(Type entityType)
{
    return dataContext.GetTable(entityType);
}



}

I have this class below in the repository currently. But I plan to place many more of these for other tables. This to me doesn't "feel" right.

Would multiple classes such as these be considered best practice or frowned upon?:

public static class UserQueries
    {
        public static Employee ByUserName(this IQueryable<Employee> employees, string username)
        {
            return employees.Where(u => u.User_Name == username).FirstOrDefault();
        }
    }

Additionally I'm planning to use another method (GetEmployeeProductivity) that essentially applies logic using data in the Employee object and data found in a separate DataRepository. So now I'm using an EmployeeRepository and a DataRepository.

Where would this go? Employee Class, Service or Repository?

Was it helpful?

Solution

Generally, your logic that makes decisions based on business rules goes in the service layer. The code that creates, updates, or deletes rows from tables (the standard CRUD functions) goes into the repository.

So if you need to retrieve data by joining multiple tables together, that's in the repository. The code that says "if this condition is met, then do this to the database" is in the service layer. If you need to add or update a row in several tables, that's still in the repository, and can either be done in one method (if the 2 tables conceptually are one, but are split into two for database efficiency reasons, like a one to many or many to many relationship), or you can use separate methods, one for each table, and just call them from one method in the service layer.

OTHER TIPS

What you can also do is to encapsulate them in each class where they refer. Your second static class has the Method ByUserName who returns an Employee object. You can put this method in Employee class (The Repository: EmployeeRepository). But You have many choices. when working in a team it should be better if everything is well organized.

As there are many thoughts on design pattern compare to repository/UnitOfWork patten, here is the standard implementation of repository & business layer.

***DATA LAYER***

namespace app.data
{
    public interface IGenericDataRepository<T> where T : class
    {
        IList<T> GetAll(params Expression<Func<T, object>>[] navigationProperties);
        IList<T> GetList(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties);
        T GetSingle(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties);
        void Add(params T[] items);
        void Update(params T[] items);
        void Remove(params T[] items);
    }

}

namespace app.data
{
    public class GenericDataRepository<T> : IGenericDataRepository<T> where T : class
    {
        public virtual IList<T> GetAll(params Expression<Func<T, object>>[] navigationProperties)
        {
            List<T> list;
            using (var context = new GatePassEntities())
                {
                IQueryable<T> dbQuery = context.Set<T>();

                //Apply eager loading
                foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
                    dbQuery = dbQuery.Include<T, object>(navigationProperty);

                list = dbQuery
                    .AsNoTracking()
                    .ToList<T>();
            }
            return list;
        }

        public virtual IList<T> GetList(Func<T, bool> where,
             params Expression<Func<T, object>>[] navigationProperties)
        {
            List<T> list;
            using (var context = new GatePassEntities())
            {
                IQueryable<T> dbQuery = context.Set<T>();

                //Apply eager loading
                foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
                    dbQuery = dbQuery.Include<T, object>(navigationProperty);

                list = dbQuery
                    .AsNoTracking()
                    .Where(where)
                    .ToList<T>();
            }
            return list;
        }

        public virtual T GetSingle(Func<T, bool> where,
             params Expression<Func<T, object>>[] navigationProperties)
        {
            T item = null;
            using (var context = new GatePassEntities())
            {
                IQueryable<T> dbQuery = context.Set<T>();

                //Apply eager loading
                foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
                    dbQuery = dbQuery.Include<T, object>(navigationProperty);

                item = dbQuery
                    .AsNoTracking() //Don't track any changes for the selected item
                    .FirstOrDefault(where); //Apply where clause
            }
            return item;
        }

        public virtual void Add(params T[] items)
        {
            using (var context = new GatePassEntities())
            {
                foreach (T item in items)
                {
                    context.Entry(item).State = EntityState.Added;
                }
                context.SaveChanges();
            }
        }

        public virtual void Update(params T[] items)
        {
            using (var context = new GatePassEntities())
            {
                foreach (T item in items)
                {
                    context.Entry(item).State = EntityState.Modified;
                }
                context.SaveChanges();
            }
        }

        public virtual void Remove(params T[] items)
        {
            using (var context = new GatePassEntities())
            {
                foreach (T item in items)
                {
                    context.Entry(item).State = EntityState.Deleted;
                }
                context.SaveChanges();
            }
        }

    }
}

//Domain Models like Employee, Department can be your objectcontext, dbcontext (can be generated by EF or other ORM Tools like T4)
namespace app.data
{
    public interface IEmployeeRepository : IGenericDataRepository<Employee>
    {
    }

    public interface IDepartmentRepository : IGenericDataRepository<Department>
    {
    }
}

***BUSINESS LAYER***

namespace app.business
{
    public interface IBusinessLayer
    {
        IList<Employee> GetAllEmployees();
        IList<Employee> GetEmployeesByCountryName(string countryName);
        Employee GetEmployeeByName(string EmployeeName);
        Employee GetEmployeeByIdentityId(int identityId, string EmployeeUniqueIdentityNumber);
        void AddEmployee(params Employee[] Employees);
        void UpdateEmployee(params Employee[] Employees);
        void RemoveEmployee(params Employee[] Employees);
    }
}

public class BuinessLayer : IBusinessLayer
{
 private readonly IEmployeeRepository _EmployeeRepository;


        public BuinessLayer()
        {
            _EmployeeRepository = new EmployeeRepository();

        }

        public BuinessLayer(IEmployeeRepository EmployeeRepository)
        {
            _EmployeeRepository = EmployeeRepository;

        }

        public IList<Employee> GetAllEmployees()
        {
            return _EmployeeRepository.GetAll();
        }

        public IList<Employee> GetEmployeesByCountryName(string countryName)
        {
            return _EmployeeRepository.GetList(e => e.Country.Employees.Equals(countryName));
        }

        public Employee GetEmployeeByName(string EmployeeName)
        {
            return _EmployeeRepository.GetSingle(
                d => d.Name.Equals(EmployeeName),
                d => d.Country); //include related employees
        }

        public Employee GetEmployeeByIdentityId(int identityId, string EmployeeUniqueIdentityNumber)
        {
            var EmployeeIdentity = _EmployeeIdentityRepository
                .GetSingle
                (
                q => q.IdentityId == identityId && q.UniqueIdentityNumber == EmployeeUniqueIdentityNumber);
            Employee Employee = new Employee();
            if (EmployeeIdentity != null)
            {
                Employee = _EmployeeRepository.GetSingle(o => o.EmployeeId == EmployeeIdentity.EmployeeId);
            }
            else
                Employee = null;
            return Employee;
        }

        public void AddEmployee(params Employee[] Employees)
        {
            try
            {
                _EmployeeRepository.Add(Employees);
            }
            catch (DbEntityValidationException dbEx)
            {
                foreach (var validationErrors in dbEx.EntityValidationErrors)
                {
                    foreach (var validationError in validationErrors.ValidationErrors)
                    {
                        Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
                    }
                }
            }

        }

        public void UpdateEmployee(params Employee[] Employees)
        {
            /* Validation and error handling omitted */
            _EmployeeRepository.Update(Employees);
        }

        public void RemoveEmployee(params Employee[] Employees)
        {
            /* Validation and error handling omitted */
            _EmployeeRepository.Remove(Employees);
        }
}

Now you can call business layer from the front-end

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top