In Onion Architecture, we have repository layer which conducts Crud Operations (simple insert, delete, update). (Applying Entity Framework with database)
Additionally , we have Service Layer which conducts complex business logic.
Questions
(1) Should DBContext leak into Service layer? What if we wanted to conduct a complex business logic query, which is not possible from simple repository, and then EF Savechanges in database?
(2) Should IQueryable be in Service layer? Again, what if there is a need for a complex query that is best by creating and then running expression tree in the service layer after its built?
1 Answer 1
DbContext should not leak to service layer. Service layer get data by calling repository in some in-memory objects. Then it perform the complex business logic on in-memory objects by calling some domain methods or perform itself. When the data is ready to store in external database then service layer again call repository to save the data.
Please find a sample code below.
Domain model:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Name
{
get
{
return $"{FirstName} {LastName}";
}
}
public int Age { get; set; }
public bool? IsAdult { get; set; }
public void DetermineAdult()
{
if (Age >= 18) IsAdult = true;
else IsAdult = false;
}
}
User interface:
public IActionResult Index()
{
_userService.ProcessUsers();
return View();
}
Service layer:
// 1. Get data from repository in form of in-memory objects.
// 2. Perform some business logic with in-memory objects.
// 3. Send in-memory objects to repository to store in database.
void IUserService.ProcessUsers()
{
IEnumerable<User> users = _userRepository.GetAll();
foreach (User user in users)
{
user.DetermineAdult(); // Calling domain method to perform the business logic.
_userRepository.Update(user.Id, user);
}
}
Repository layer:
IEnumerable<User> IUserRepository.GetAll()
{
List<User> users = _userContext.Users.ToList();
return users;
}
void IUserRepository.Update(int userId, User user)
{
User userFromDb = _userContext.Users.FirstOrDefault(u => u.Id == userId);
if (userFromDb == null) return;
userFromDb.IsAdult = user.IsAdult;
_userContext.SaveChanges();
}
Solution structure
Please find the full code in my github repository https://github.com/Arnab-Developer/OnionArchitecture
Explore related questions
See similar questions with these tags.
IQueryable
as abstraction type. Data access abstraction can have domain specific methods and types, then implementation of data access implementation will be responsible to convert database types into domain types. If you need complex query, you simply will add a another method. But try to keep as much logic in the service layer.