关于这个主题有数百个类似的问题。但我仍然感到困惑,我希望得到专家的建议。
我们正在使用ASP.NET MVC 4和EF5开发应用程序,我们的数据库是第一种方法。
我们在一个单独的项目中有数据层,该项目是一个类库,并包含在其中定义的所有实体。然后使用所有存储库和域模型定义业务层(是要使用的正确术语)。然后是表示层。
目前我们还没有定义任何视图模型,我们使用BL中的相同域模型作为视图模型。在这种方法中,一个映射就足够了。
ENTITY< =>域名模型
但对我而言,它看起来并不像一个好的设计。我更喜欢在表示层中定义视图模型,并使用域模型在表示层和业务层之间进行通信。在BL,将域对象转换为数据实体并与DAL通信。使用这种方法我必须使用两次映射。
查看模型< =>域模型< => ENTITY
我的域名模型真的有必要吗?我不能使用我的实体与Presentation层进行通信。如果我在表示层中引用实体,是否会产生任何影响?如果有什么样的影响?
答案 0 :(得分:89)
我认为您在定义每个图层的内容以及它在解决方案中扮演的角色时遇到了问题。
您的数据层只是您的数据库/ SharePoint列表/ .csv文件/ excel表...您明白了,它只是存储数据的位置,它可以是任何格式。所以请记住,数据层只不过是数据。
// ----------------------------
// Data tier
// - MySQL
// - MS SQL
// - SharePoint list
// - Excel
// - CSV
// - NoSQL
// ----------------------------
此图层抽象出您的数据源,并提供一个API,其中您的应用程序的其余部分可以与数据源进行交互。
请考虑我们的数据源是MS SQL数据库,并且我们正在使用Entity Framework来访问数据。您将尝试抽象出来的是数据库和实体框架,每个Data Repository
都有Entity
。
我们在MS SQL数据库中有一个Customers
表。 customers表中的每个客户都是Entity
,并在C#代码中表示。
通过使用存储库模式,我们可以抽象出数据访问代码的实现,以便将来,如果我们的数据源发生更改,我们的应用程序的其余部分将不会受到影响。接下来,我们需要CustomersRepository
中的Data Access Layer
,其中包括Add
,Remove
和FindById
等方法。抽象出任何数据访问代码。以下示例是您如何实现这一目标。
public interface IEntity
{
int Id { get; set; }
}
public class Customer : IEntity
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime RegistrationDate { get; set; }
}
public interface IRepository<TEntity> where TEntity : class, IEntity
{
TEntity FindById(int id);
void Add(TEntity entity);
void Remove(TEntity entity);
}
public class CustomerRepository : IRepository<Customer>
{
public Customer FindById(int id)
{
// find the customer using their id
return null;
}
public void Add(Customer customer)
{
// add the specified customer to the db
}
public void Remove(Customer customer)
{
// remove the specified customer from the db
}
}
数据访问层属于数据层和业务逻辑之间。
// ----------------------------
// Business logic
// ----------------------------
// ----------------------------
// Data access layer
// - Repository
// - Domain models / Business models / Entities
// ----------------------------
// ----------------------------
// Data tier
// - MySQL
// - MS SQL
// - SharePoint list
// - Excel
// - CSV
// - NoSQL
// ----------------------------
业务层建立在数据访问层之上,并不处理任何数据访问问题,而是严格的业务逻辑。如果其中一项业务要求是阻止从英国境外发出订单,那么业务逻辑层将处理此问题。
演示层只是简单地显示您的数据,但如果您不小心您提供的数据以及您允许发布的数据,那么您可以自行解决很多问题这条线,这就是使用视图模型很重要的原因,因为视图模型是一个表示层关注点,表示层不需要知道关于域模型的任何信息,它只需要了解视图模型。
那么什么是视图模型......它们只是为每个视图定制的数据模型,例如注册表单将包含RegistrationViewModel
,从而暴露这些典型属性。
public class RegistrationViewModel
{
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
表示层还处理输入验证,因此例如验证键入的电子邮件地址是否具有正确的格式,或者输入的密码是否与表示层相关,而不是业务问题,并且可以使用{{ 1}}。
Data Annotations
使用View Models非常重要的原因是Business Models属于业务层,它们包含应保持私有的数据。例如,如果您要在JSON响应中公开域模型,它会向用户显示整个数据,将其名称显示为地址,因为您没有选择要公开的内容和不是什么,但使用似乎有用的东西。
我还应该在此指出public class RegistrationViewModel
{
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Compare("ConfirmPassword")
public string Password { get; set; }
[Required]
[DataType(DataType.Password)]
public string ConfirmPassword { get; set; }
}
和domain models
之间存在差异。已经有了一个更详细的答案here
我会保持这个简短的说明:
答案 1 :(得分:7)
我不是专家,但我会就这个话题分享我的50美分。
我想分享您对忽略View模型的担忧。
使用视图模型,您可以:
所以,我也认为这是一个糟糕的设计,但其他人可能有不同的意见。
请记住,业务层对视图模型一无所知,因此您应该将其映射到控制器中。
我会简单地开始,使用POCO作为域模型,可以使用ORM或NoRM进行持久化。对于世界上开发的大多数软件,它不会对您的系统造成太大伤害,而且也很简单。
将来,如果由于某种原因开始使用Web服务,则可能需要考虑使用DTO(数据传输对象)进行远程调用。 在那里,您可以做的是拥有另一个图层,负责将您的域模型映射到所需的DTO。该层仅用于远程调用(Web服务),保留演示文稿的视图模型。
答案 2 :(得分:1)
引入其他课程的主要好处是分离关注点:
只有ViewModel和Domain Entities的两个模型类才能实现。不是使用与持久化实体类似的单独模型类对域逻辑进行建模,而是在使用域实体的服务类中实现它。域实体最多应具有处理其自身属性的逻辑(例如,将两个属性的组合值保持在有效状态)。如果多个域实体受用例影响,请在服务类中对此用例进行建模。如果你把逻辑直接管理实体类中的实体之间的关系,代码就会很快变得不可维护(相信我,我试过了)。
我不建议将可持久实体用作ViewModel,因为这会混淆显示问题(例如[DisplayName]
)与持久性问题(例如[ForeignKey]
)。