WCF数据契约和引用实体数据是什么?

12

寻求关于在我的服务中使用“最佳”模式来处理参考数据的反馈、选项和评论。

什么是参考数据?

我们以Northwind为例。订单与数据库中的客户相关联。当我实现我的订单服务时,有时我需要从订单中引用“完整”的客户,而其他情况下只需要引用客户的参考(例如键/值对)。

例如,如果我在执行GetAllOrders()方法时,我并不想返回填充完整信息的订单,我只想返回每个订单客户的轻量级版本的参考数据。但如果我执行GetOrder()方法,我可能希望填写客户详细信息,因为调用此方法的消费者很可能需要这些信息。在某些方法调用期间,我可能希望要求填写客户详细信息,而在其他情况下则保留不填写。

这是我的解决方案:

[DataContract]
public OrderDTO
{
    [DataMember(Required)]
    public CustomerDTO;

    //etc..
}

[DataContract]
public CustomerDTO
{
    [DataMember(Required)]
    public ReferenceInfo ReferenceInfo;

    [DataMember(Optional)]
    public CustomerInfo CustomerInfo;
}

[DataContract]
public ReferenceInfo
{
    [DataMember(Required)]
    public string Key;

    [DataMember(Required)]
    public string Value;
}

[DataContract]
public CustomerInfo 
{
    [DataMember(Required)]
    public string CustomerID;

    [DataMember(Required)]
    public string Name;

    //etc....
}
这里的思路是,由于ReferenceInfo(这是一个通用的键/值对)在CustomerDTO中始终是必需的,因此我将始终拥有ReferenceInfo。这为我提供了足够的信息,以后如果需要的话,可以获得客户详细信息。 CustomerDTO要求ReferenceInfo的缺点是,在获取填充了CustomerInfo的完整CustomerDTO时可能有些过度,但至少我可以保证参考信息。
有没有其他模式或框架组件可用于使此场景/实现更“清晰”?
我问的原因是,尽管我们可以在Northwind中简单地说始终返回完整的CustomerDTO,但在我的情况下,我有一个具有25-50个字段的对象,这些字段是引用/查找类型的数据。在不同的情况下,一些比其他一些更重要,但我希望尽可能少地定义这些引用类型(这样我就不会陷入“DTO维护地狱”)。
意见?反馈?评论?
谢谢!

就我所知...我认为Linq to SQL、Entity Framework或ADO.NET数据服务对我并没有帮助,因为我有3个不同的系统,它们都基本上代表着相同的数据集。这是合并这些系统的第一步,或者至少让外部系统能够以更通用和一致的方式消费这三个系统的数据。 - Brian
请问您能回答一下这个问题吗?http://stackoverflow.com/questions/9483286/understanding-data-outside-of-service-soa - LCJ
7个回答

2
我们的项目也面临着同样的决策点。目前,我们已经决定创建三个级别的数据传输对象(DTOs)来处理一个Thing: SimpleThing、ComplexThing和FullThing。然而,我们不知道这对我们是否奏效,因此这还不是根据现实情况得出的答案。
我想知道的一件事是,我们是否可能会发现我们的服务设计在“错误”的级别上。例如,是否有时应该拆分FullThing并只传递SimpleThing?如果是这样,是否意味着我们把某些业务逻辑放在了错误的级别上?

你成功了吗?我更倾向于这种方法。 - Jonn

2
亚马逊产品广告API网络服务是您正在经历的相同问题的一个很好的例子。
他们使用不同的DTO来根据调用者的情况提供更多或更少的细节。例如,有小响应组大响应组中等响应组
如果像您所说,您不想要一个啰嗦的界面,那么拥有不同的DTO是一种好的技术。

只是想知道,在继承层次结构中如何对齐Small、Medium、Large响应组?ORM是否总是持久化大型DTO,然后仅将Small DTO和Medium DTO作为包装器? - Jonn
@RichardOD,你能否回答一下这个问题:http://stackoverflow.com/questions/9483286/understanding-data-outside-of-service-soa? - LCJ

1

我决定放弃原本的方法。我认为我的初步担忧很大程度上是由于缺乏需求而引起的。我有点预料到会出现这种情况,但我很好奇其他人如何解决确定何时加载某些数据以及何时不加载的问题。

我正在将我的数据合同扁平化,以包含最常用的参考数据元素字段。这对大多数消费者应该有效。如果提供的数据对于特定的消费者不足够,他们将有选择查询单独的服务,以获取特定参考实体(例如货币、州等)的完整详细信息。对于真正基本上是键/值对的简单查找,我们将使用通用的键/值对数据合同来处理它们。我甚至可能会为我的更专业的键/值对使用KnownType属性。

[DataContract]
public OrderDTO
{
    [DataMember(Required)]
    public CustomerDTO Customer;

    //in this case, I think consumers will need currency data, 
    //so I pass back a full currency item
    [DataMember(Required)]
    public Currency Currency; 

    //in this case, I think consumers are not likely to need full StateRegion data, 
    //so I pass back a "reference" to it
    //User's can call a separate service method to get full details if needed, or 
    [DataMember(Required)]
    public KeyValuePair ShipToStateRegion;

    //etc..
}


[DataContract]
[KnownType(Currency)]
public KeyValuePair
{
    [DataMember(Required)]
    public string Key;

    [DataMember(Required)]
    public string Value;

    //enum consisting of all possible reference types, 
    //such as "Currency", "StateRegion", "Country", etc.
    [DataMember(Required)]
    public ReferenceType ReferenceType; 
}


[DataContract]
public Currency : KeyValuePair
{
    [DataMember(Required)]
    public decimal ExchangeRate;

    [DataMember(Required)]
    public DateTime ExchangeRateAsOfDate;
}


[DataContract]
public CustomerDTO 
{
    [DataMember(Required)]
    public string CustomerID;

    [DataMember(Required)]
    public string Name;

    //etc....
}

有什么想法?意见?评论?


这听起来像一个合理的妥协,但是要准备好进行很多调整和微调,直到找出应该是“共同子集”的属性为止。如果这个项目主要是用于报告或分析目的,那么就有可能根本没有共同子集。这将使您重新需要多个串行化。 - dthrasher
@Brian- 尽管这是一个老问题,我已经添加了一个关于亚马逊如何做到的答案,希望你会发现它有用。 - RichardOD

1

我们在对象关系映射中也遇到了这个问题。有时我们想要整个对象,有时只需要引用。

难点在于将序列化嵌入到类本身中,数据合同模式强制实施只有一种正确的对象序列化方法的观念。但实际上有很多场景需要对类及其子对象进行部分序列化。

这通常意味着您需要为每个类都编写多个DTO。例如,FullCustomerDTO和CustomerReferenceDTO。然后您还需要创建方式来将不同的DTO映射回客户领域对象。

可以想象,这是很多工作,其中大部分都非常繁琐。


请回答 http://stackoverflow.com/questions/9483286/understanding-data-outside-of-service-soa 这个问题好吗? - LCJ

1

我通常在复杂的Web服务中构建惰性加载(即发送/接收实体的Web服务)。如果一个人有一个父亲属性(也是一个人),我只发送父亲的标识符而不是嵌套对象,然后我只需确保我的Web服务具有可以接受标识符并响应相应的Person实体的操作。如果客户端想要使用Father属性,则可以调用Web服务。

我还扩展了这个功能,以便可以进行批处理。如果一个操作返回5个人,则如果在这些人中的任何一个上访问Father属性,则会请求所有5个带有它们的标识符的父亲。这有助于减少Web服务的聊天量。


请注意,使用此策略需要对实体标识的敏锐意识。您可能不希望同一实体的多个实例漂浮在周围;因此,您可能希望在客户端上创建一个缓存,以存储到目前为止接收到的实体,并使用已经获得的实体而不是返回另一个副本。 - Travis Heseman

1

对我来说,这似乎是一个复杂的解决方案。为什么不在OrderDTO类中只放置客户ID字段,然后让应用程序在运行时决定是否需要客户数据呢?由于它拥有客户ID,因此可以在需要时拉取下来。


我将提供根据客户ID获取客户数据的功能,但尽量避免使用冗长的接口。 - Brian
详细说明我所说的“啰嗦”的界面。假设我在网格中显示GetAllOrders()的结果。网格中的一列是“客户名称”。我不想为我在网格中显示的每个订单调用GetCustomer()。此外,如果新要求规定除了客户名称之外,我们还需要显示例如客户电话号码等信息,我不想违反我的合同。我只想能够将我的方法调用切换到以下内容:<br> GetAllOrders(expandReference=true);注意:不确定电话号码是否实际上在Northwind中。 - Brian
那么您会有一个逻辑,根据CustomerDTO中的键值对返回部分客户详细信息?我花了一段时间才理解您的机制,这可能意味着它过于复杂或者我太慢了。如果是我,我肯定会尝试只使用两个类:Order和Customer来完成它。在您的接口中添加一个额外的方法,例如GetAllOrdersPartial(),似乎比创建其他有些晦涩的类更好。如果我需要维护您的代码,我更喜欢多调用几个方法,而不是为了达到相同的效果而进行一些棘手的优化。 - sipsorcery
@sipwiz,你能否回答一下这个问题:http://stackoverflow.com/questions/9483286/understanding-data-outside-of-service-soa? - LCJ

1

另一种可能性是将对象视为属性包。在查询时指定所需的属性,然后精确获取所需的属性。

然后更改要在“简短”版本中显示的属性不需要多次往返,您可以一次获取集合的所有属性(避免冗余接口),如果您决定需要“简短”版本的不同属性,则无需修改数据或操作契约。


将属性视为对象本身是我一直在推动的想法。我认为这将允许灵活地提取我想要的任何属性。然而,这也引入了系统复杂性的层次,因为我们不再处理具有本机类型字段的对象,而是处理“字段对象”的集合。这种抽象给您带来了灵活性,但代价是复杂性(可能还包括可维护性)。在我的特定情况中,我发现大多数其他开发人员认为这个解决方案过于复杂,难以理解。 - Brian
1
两者并不一定是互斥的。您公共面向的API可以具有静态类型字段,同时使用属性包作为底层实现。您还可以直接或间接地公开该包以允许动态添加“字段”,同时仍保留已知静态内容的静态属性。 - kyoryu
更具体地说,WCF合同没有必要与对象的消费者使用的API匹配,事实上,我通常建议您从一开始就明确这一点,这样您将在长期内获得更好的效果。 - kyoryu
请问您能回答一下这个问题吗?http://stackoverflow.com/questions/9483286/understanding-data-outside-of-service-soa - LCJ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接