C# 领域模型和相关数据(贫血领域模型)
我目前正在使用EntityFrameworkCore使用ASP.NETCore1.0。我对数据库中的数据进行了一些复杂的计算,我不知道如何使用依赖注入构建一个合适的体系结构,而不构建一个贫乏的域模型() (简化)示例: 我拥有以下实体:C# 领域模型和相关数据(贫血领域模型),c#,architecture,asp.net-core,domain-driven-design,entity-framework-core,C#,Architecture,Asp.net Core,Domain Driven Design,Entity Framework Core,我目前正在使用EntityFrameworkCore使用ASP.NETCore1.0。我对数据库中的数据进行了一些复杂的计算,我不知道如何使用依赖注入构建一个合适的体系结构,而不构建一个贫乏的域模型() (简化)示例: 我拥有以下实体: public class Project { public int Id {get;set;} public string Name {get;set;} } public class TimeEntry { publi
public class Project {
public int Id {get;set;}
public string Name {get;set;}
}
public class TimeEntry
{
public int Id {get;set;}
public DateTime Date {get;set;}
public int DurationMinutes {get;set;}
public int ProjectId {get;set;}
public Project Project {get;set;}
}
public class Employee {
public int Id {get;set;}
public string Name {get;set;}
public List<TimeEntry> TimeEntries {get;set;}
}
如果我想得到时间表,我需要注入EmployeeService并调用GetMonthlyTimeSheet
因此-我的服务中有很多GetThis()和GetThis()方法,尽管这些方法完全适合Employee
类本身
我想要实现的是:
public class Employee {
public int Id {get;set;}
public string Name {get;set;}
public List<TimeEntry> TimeEntries {get;set;}
public List<CalculatedMonth> GetMonthlyTimeSheet() {
var result = new List<CalculatedMonth>();
//complex calculation using TimeEntries etc here
return result;
}
}
public IActionResult GetTimeSheets(int employeeId) {
var employee = _employeeRepository.Get(employeeId);
return employee.GetTimeSheets();
}
公共类员工{
公共int Id{get;set;}
公共字符串名称{get;set;}
公共列表时间项{get;set;}
公共列表GetMonthlyTimeSheet(){
var result=新列表();
//此处使用时间项等进行复杂计算
返回结果;
}
}
公共IActionResult GetTimeSheets(int employeeId){
var employee=_employeeRepository.Get(employeeId);
return employee.GetTimeSheets();
}
…但为此,我需要确保从数据库填充TimeEntries列表(EF Core不支持延迟加载)。我不想在每个请求中都包含(x=>y)所有内容,因为有时我只需要员工的姓名而不需要时间条目,这会影响应用程序的性能
有人能告诉我如何正确地设计这个吗
编辑:
一种可能性(根据第一个答案的评论)是:
public class Employee {
public int Id {get;set;}
public string Name {get;set;}
public List<TimeEntry> TimeEntries {get;set;}
public List<CalculatedMonth> GetMonthlyTimeSheet() {
if (TimeEntries == null)
throw new PleaseIncludePropertyException(nameof(TimeEntries));
var result = new List<CalculatedMonth>();
//complex calculation using TimeEntries etc here
return result;
}
}
public class EmployeeService {
private DbContext _db;
public EmployeeService(DbContext db) {
_db = db;
}
public Employee GetEmployeeWithoutData(int employeeId) {
return _db.Employee.Single();
}
public Employee GetEmployeeWithData(int employeeId) {
return _db.Employee.Include(x=>x.TimeEntry).ThenInclude(x=>x.Project).Single();
}
}
public IActionResult GetTimeSheets(int employeeId) {
var employee = _employeeService.GetEmployeeWithData(employeeId);
return employee.GetTimeSheets();
}
公共类员工{
公共int Id{get;set;}
公共字符串名称{get;set;}
公共列表时间项{get;set;}
公共列表GetMonthlyTimeSheet(){
if(TimeEntries==null)
抛出新的PleaseIncludePropertyException(nameof(TimeEntries));
var result=新列表();
//此处使用时间项等进行复杂计算
返回结果;
}
}
公营雇员服务{
私有DbContext_db;
公共雇员服务(DbContext db){
_db=db;
}
公共雇员GetEmployeeWithout数据(int employeeId){
return _db.Employee.Single();
}
公共雇员GetEmployeeWithData(int employeeId){
return _db.Employee.Include(x=>x.TimeEntry).然后Include(x=>x.Project).Single();
}
}
公共IActionResult GetTimeSheets(int employeeId){
var employee=_employeeService.GetEmployeeWithData(employeeId);
return employee.GetTimeSheets();
}
如果我正确理解了您的问题,您可以使用一个技巧,将服务注入到您的实体中,以帮助它完成工作,例如:
public class Employee()
{
public object GetTimeSheets(ICalculatorHelper helper)
{
}
}
然后在保存员工的服务中,您将在构造函数中获得该服务,并将其传递给employee类进行计算。该服务可以是一个门面,例如获取所有数据并执行初始化或任何您真正需要的操作
对于时间条目,您可以使用如下函数获取它们:
private GetTimeEntries(ICalculationHelper helper)
{
if (_entries == null)
{
_entries = helper.GetTimeEntries();
}
return _entries;
}
如果这种模式适合您,这当然取决于您的缓存策略等等
就我个人而言,我发现使用贫乏的类和服务中的大量业务逻辑相当容易。我确实在对象中添加了一些,例如,根据FirstName和LastName计算FullName。通常是不涉及其他服务的东西。尽管这是一个偏好问题。不要试图解决聚合的查询问题。您的聚合旨在处理命令并保护不变量。它们围绕一组数据形成一致性边界
员工
对象是否负责保护员工时间表的完整性?如果没有,则此数据不属于Employee
类
延迟加载对于CRUD模型来说可能很好,但在我们设计聚合时通常被认为是一种反模式,因为这些聚合应该尽可能小和内聚
您是否根据时间表的计算结果做出业务决策?有什么不变量需要保护吗?如果决策是针对过时的时间表数据做出的,这有关系吗?如果这些问题的答案是否,那么您的计算实际上只是一个查询
在服务对象中放置查询是可以的。这些服务对象甚至可能位于域模型之外(例如,在应用程序层),但没有严格的规则可遵循。此外,您可以选择加载一些聚合以访问处理计算所需的数据,但通常最好直接进入数据库。这允许更好地分离读写(CQR)。避免进入贫血域模型的第一步是使setter
私有化,并通过参数验证创建适当的对象构造函数。此外,当使用EF时,如果您的公共构造函数不是如我所建议的那样无参数的,您将至少需要一个受保护的构造函数。我认为这是考虑如何对您的模型进行单元测试的工作。IMHO认为,使代码可测试通常是解决设计问题及其解决方案的一种很好的方法。例如,鉴于上述设计,您是否高兴能够测试(单元或集成)您的EmployeeService
?他的问题是数据可能加载到对象中,也可能不加载到对象中。@HristoYankov:这有助于他不必担心如何获取数据,这是ICalculatorHelper
(或其他命名服务)的任务,他可以将计算的逻辑保存在模型中(如果它特定于模型)或者在计算器帮助程序中,如果它已退出
private GetTimeEntries(ICalculationHelper helper)
{
if (_entries == null)
{
_entries = helper.GetTimeEntries();
}
return _entries;
}