假设我有这样的表:
Users
------
UserId
Email
...
People
------
PersonId
UserId
Name
Country
...
使用相应的型号:
public class User{
...
}
public class Person{
...
}
我希望在我的MVC应用程序中有一个显示所有人的视图,包括他们的电子邮件地址。
我的解决方案是在不同的项目中构建的,例如:
Comp.App.Services
Comp.App.Dal
Comp.App.Web
理想情况下,我会在我的控制器中创建一个viewmodel,以便稍后填充我的视图:
public class PersonListItemViewModel{
public string Name { get; set; }
public string Country { get; set; }
public string Email { get; set; }
}
现在问题。我想在我的服务层中使用1个查询获取数据,但服务层必须不知道viewmodel,并且查询不应返回任何额外字段。只有我在viewmodel中指定的字段。
我为N层应用程序提供了许多最佳实践,但我似乎无法弄清楚如何在不让我的服务层了解视图模型的情况下实现这一点。这似乎不正确。我总是假设我的服务层只能知道域模型,但在现实世界的应用程序中,我总是会遇到我想限制查询的问题,例如只有2个字段。如果我的服务仅与域模型进行对话,则无法完成此操作。
我误解了这种方法吗?
PS。我在我的Dal项目中使用Dapper。
编辑: 它不是Converting DTOs to View Models的副本 。我知道automapper。让我问这个问题有点不同。当我有一个名为GetPeopleWithEmail()???
的方法时,我的服务应该返回什么根据现在正在阅读的东西,它应该返回一个DTO,并将DTO转换为我的控制器中的ViewModel。正确的吗?
答案 0 :(得分:2)
如果您希望确保您的ViewModel保留在演示文稿端且Domain模型保留在Repository端,那么我将使用逻辑层。
网络层:
App Layer:
我强烈建议使用一些依赖注入,例如Unity来帮助管理依赖项。
例如,在我的上述结构中,SomeController将接受SomeWebLogic,而SomeWebLogic将接受ISomeService。 SomeWebLogic会调用ISomeService将SomeDomainModel转换为SomeViewModel,最终将由SomeController使用。
在App层一侧,SomeService会接受SomeBusinessLogic,它将接收ISomeRepository。
在您的示例中 - 使用我建议的结构:
- 网络层 -
PersonView.cshtml:
@model List<PersonListItemViewModel>;
@foreach(var person in model)
{
//some html binding
}
PersonController.cs:
public ActionResult PersonView()
{
var personWebLogic = new PersonWebLogic(); //would suggest DI instead
var personsModelList = personWebLogic.GetPersons();
return View(personsModelList );
}
PersonWebLogic.cs
public List<PersonListItemViewModel> GetPersons()
{
var personService = new PersonService(); //suggest DI here again
var people = personService.GetPeople(); //returns a list of domain models
var personsViewModelList = new List<PersonListItemViewModel>();
foreach(var person in people)
{
//use some custom function to convert PersonDomainModel to PersonListItemViewModel
personalsViewModel.Add(MapPersonDomainToPersonView(person));
}
return personsViewModelList;
}
- 应用层 -
PersonService
public List<Person> GetPeople()
{
var personLogic = new PersonLogic(); //use DI for this
return personLogic.GetPeople(); //return type will be dependent on your service architecture
}
PersonLogic
public List<Person> GetPeople()
{
var personRepostitory = new PersonRepository(); //DI...
var personDataTable = personRepository.GetAllPeople(); //return type will vary on your repository structure
//Custom function to map to person list from data table, this could be in repo, all depends on your desired structure
return MapPersonDataTableToPersonList(personDataTable);
}
PersonRepository
public DataTable GetAllPeople()
{
var database = GetDatabase();
var dataTable = ...//call db to get person datatable
return dataTable;
}
答案 1 :(得分:1)
[...]并且查询不应返回任何额外字段。只有字段 我在我的viewmodel中指定。
实际上,在域层中返回某个模型,域对象或其他具有10,20或100个属性的实例,并不意味着必须设置所有属性。
另一方面,相同的规则可以应用于数据传输对象(DTO)。
通常使用JSON序列化格式,默认的ASP.NET Web API JSON序列化与JSON.NET库一起使用,后者支持DataContractAttribute
和DataMemberAttribute
属性。
在一天结束时,您可以使用具有20个属性的DTO类从API通过线路发送对象,但只有具有非默认值的对象将被序列化:
[DataContract]
public class Dto
{
[DataMember(EmitDefaultValue = false)]
public string PropertyA { get; set; }
}
如果您实例化Dto
并且未设置任何属性,则序列化结果将只是{}
(空白文字对象)。现在,您可以将此规则推断为大型DTO,并确保您不会通过网络传输不需要的属性。 或者换句话说,您可以使用序列化属性从同一个类中生成许多不同的DTO!。
您应该关心正在设置哪些属性,而不是在n层方案中工作时有多少属性公开某些类。用例A可以设置3个属性,用例B可以设置与用例A不同的其他2个属性。也可以使用用例C设置其他属性加上用例A和B设置的属性。
有时事情变得更加困难,你不能使用一个类来统治它们,然后你实现了许多数据传输对象来覆盖不同的用例,在这些用例中你实现了具体所需的属性子集客户观点。
这里重点是ViewModel != DTO
。 视图模型为视图提供行为和数据,而DTO只是传输某些服务提供的数据子集的对象,以优化网络性能和使用,并避免发送不相关的数据到其他层。
答案 2 :(得分:0)
根据DDD(域驱动设计)的原则,域实体可以是 root 和 child 。根实体是独立的,可以包含一些依赖的子节点。
根实体存储库应该从包含所有必需子节点的数据库加载整个实体。您可以在存储库的代码中优化查询。
public class PersonRepository
{
public Person GetById(int id)
{
// reads Person data and related User data
// builds Person object and returns it
}
}
然后您可以使用根对象来构建视图/模型:
PersonListItemViewModel CreateViewModel(Person person)
{
return new PersonListItemViewModel
{
Name = person.Name,
Country = person.Country,
Email = person.User.Email,
};
}
答案 3 :(得分:0)
该服务可以从All()返回IQueryable的方法返回所有人。然后你可以用它来选择像
这样的自定义投影db.People.All().Select(p => new PersonListItemViewModel()
{
Name = p.Name,
Country = p.Country,
Email = db.Users.FirstOrDefault(u => u.UserId == p.UserId).Email
});