我们正在使用
AngularJS
、C#
、ASP.NET Web API
和 Fluent NHibernate
构建 Web 应用程序。我们决定使用 DTO 将数据传输到表示层(角度视图)。
我对 DTO 的一般结构和命名有一些疑问。这是一个例子来说明我的情况。
假设我有一个名为
Customer
的域实体,它看起来像这样:
public class Customer
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Address Address { get; set; }
public virtual ICollection<Account> Accounts { get; set; }
}
现在,在我的视图/表示层中,我需要检索不同风格的
Customer
,例如:
Id
,Name
Id
、Name
、Address
Id
、Name
、Address
、Accounts
我创建了一组 DTO 来完成此任务:
public class CustomerEntry
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CustomerWithAddress : CustomerEntry
{
public AddressDetails Address { get; set; }
}
public class CustomerWithAddressAndAccounts : CustomerWithAddress
{
public ICollection<AccountDetails> Accounts { get; set; }
}
AddressDetails
和 AccountDetails
是具有其对应域实体的所有属性的 DTO。它们非常适合查询和数据检索。
创建新的
Customer
时,字段 Name
和 Address
是必填字段,而 Accounts
是可选字段。换句话说,我需要一个具有所有客户属性的对象。因此造成混乱:
CustomerWithAddressAndAccounts
DTO 拥有一切,但它的名字
对于插入/更新来说似乎有点尴尬。CustomerWithAddressAndAccounts
?我已经查看了有关此主题的其他帖子,但没有取得太大进展。我学到的一件事是避免在类名称中使用后缀“DTO”。我觉得感觉有点多余。
很想听听您的想法。
谢谢。
建议您应该为每个实体添加一个 DTO 类,并以 DTO 为后缀,例如
CustomerEntryDTO
Customer
entity
(但您当然可以根据选择和要求使用继承层次结构)。
此外,添加一个抽象
DTOBase
类型的基类或接口;并且不要对要包含在子 DTO 中的每个地址、帐户和其他属性使用如此深的继承层次结构。相反,请将这些属性包含在同一个 CustomerEntryDTO
类中(如果可能),如下所示:
[Serializable]
public class CustomerEntryDTO : DTOBase, IAddressDetails, IAccountDetails
{
public int Id { get; set; }
public string Name { get; set; }
public AddressDetails Address { get; set; } //Can remain null for some Customers
public ICollection<AccountDetails> Accounts { get; set; } //Can remain null for some Customemer
}
此外,您的 DTO 应该可序列化,以便跨进程边界传递。
有关 DTO 模式的更多,请参阅以下文章:
编辑: 如果您不想通过网络发送某些属性(我知道您需要有条件地发送某些属性,因此需要对此进行更多探索),您可以使用诸如
NonSerialized
之类的属性将它们从序列化机制中排除(但它仅适用于字段,不适用于属性,请参阅与属性一起使用的解决方法文章:属性上的非序列化)。
您还可以创建自己的自定义属性(例如 ExcludeFromSerializationAttribute
),并将其应用于您不想根据某些规则/条件每次都通过网络发送的属性。 另请参阅: 条件 xml 序列化
编辑2: 使用接口来分隔一个
CustomerEntryDTO
类中的不同属性。请参阅 Google 或 MSDN 上的接口隔离原则。稍后我会尝试提供示例解释。
我用什么来插入和更新?
服务操作的定义通常与业务操作非常密切。商业语言不会讲“插入”和“更新”,服务也不会。
客户管理服务可能有一些
Register
操作,需要客户名称以及其他一些可选参数。我要创建另一个 DTO 吗?
是的,您应该创建另一个 DTO。
有时服务操作契约可能就足够了,不需要为特定操作定义单独的 DTO:
function Register(UserName as String, Address as Maybe(of String)) as Response
但大多数时候最好定义一个单独的 DTO 类,即使只针对单个服务操作:
class RegisterCommand
public UserName as String
public Address as Maybe(of String)
end class
function Register(Command as RegisterCommand) as Response
RegisterCommand
DTO 可能看起来与 CustomerWithAddress
DTO 非常相似,因为它们具有相同的字段,但实际上这 2 个 DTO 具有非常不同的含义,并且不能相互替代。
例如,
CustomerWithAddress
包含AddressDetails
,而简单的String
地址表示可能足以注册客户。
为每个服务操作使用单独的 DTO 需要更多时间编写,但更易于维护。