WCF - 多个服务契约使用相似的数据契约

7

我有一个新的问题需要向WCF专家请教。

我的类User与我用于数据库操作的“User”表示非常接近。现在,我想要有两个不同的服务契约来使用这个类作为数据契约,但是每个服务都以自己的方式使用...我的意思是,

public class DBLayer
{
    void InsertUsers(List<User> userList)
    {
        // both 'PropertyVisibleForService1' and 'PropertyVisibleForService2'
        // are used HERE to be inserted into their columns 
    }
}

[DataContract]
public class User
{
  [DataMember] public string PropertyVisibleOnlyForService1{...}
  [DataMember] public string PropertyVisibleOnlyForService2{...}
}

[ServiceContract]
public interface IService1  
{   
   List<User> GetUsers();  // user with 'PropertyVisibleOnlyForService1' inside
}

[ServiceContract]
public interface IService2  
{   
    List<User> GetUsers(); // user with 'PropertyVisibleOnlyForService2' inside 
}

因此,每个服务将获得不同类型的用户,即'User'的子集。请注意,我想在DB操作中使用'User',那么我有什么选择来实现这一点?我真的需要创建不同的数据协议吗,还是有其他更聪明的方法?
最好不仅给我解决方案,还要解释一些最佳实践和替代方案。
谢谢你的帮助。
编辑1: 我在这里添加了一个虚拟的DBLayer类,以便更好地了解为什么我认为继承可能在这种情况下不好。 解决方案是有另一个'UserForService1'和'UserForService2'作为数据协议,最终会从/到一个'User'进行映射,但我想要其他的观点。
编辑2:非常好的文章,在这种情况下对我有所帮助:http://bloggingabout.net/blogs/vagif/archive/2009/03/29/iextensibledataobject-is-not-only-for-backward-compatibility.aspx

2
我不是WCF专家,所以建议通过评论提出:听起来你应该有两个类派生自User;一个具有PropertyVisibleOblyForService1属性,另一个具有其他属性。 - phoog
谢谢您的回复...所以,您建议是将这些属性取出并放入新的派生类中。问题是我希望'User'类保持不变。 - Learner
是的,我认为你理解了我的建议。但是由于我不是WCF专家,也不了解你的项目、要求等等,我不知道是否可能在不改变User类的情况下实现你想要的功能。如果你发布更多详细信息,我或许可以帮忙,或者有更多WCF经验的人会参与讨论。无论如何,祝你好运! - phoog
感谢您的参与,欢迎提出任何意见。我知道一种解决方案是创建两个不同的数据契约,然后将“用户”映射到适当的数据契约,但也许还有其他更好的做法。我不认为继承在我的情况下是一个好选择。谢谢。 - Learner
好的,所以每个人都建议同样的事情。也许我在我的请求中没有表达清楚。我不想从“用户”中取出属性并放入其他派生类中,因为这个“用户”(及其属性)是按原样用于数据库操作的。因此,“PropertyVisibleOnlyForService1”和“PropertyVisibleOnlyForService2”都是用户表中的列。 - Learner
...或者只有当我在派生类中隐藏这些属性时才能实现吗? - Learner
4个回答

4

您可以为每个服务创建单独的DTO,但实际上您的情况非常适合装饰器模式

[DataContract]
public class UserForService1 : User
{
     private User mUser;
     public UserForService1(User u)
     {
         mUser = u;
     }

     //expose only properties you'd like the user of this data contract to see
     [DataMember]
     public string SomeProperty
     {
         get
         {
            //always call into the 'wrapped' object
            return mUser.SomeProperty;
         }
         set
         {
            mUser.SomeProperty = value;
         }
     }
     // etc...
}

对于Service2,类似的代码,只暴露了你所关心的内容...


这正是我在寻找的答案。非常好!干杯! - Learner

1
如果它们被设计成代表不同类型的用户,那么它们应该是不同的类。我同意评论中phoog的观点,你应该从共享的User类中派生出你想要的类型,并将特定的服务属性添加到派生类中。
为什么您认为继承在这种情况下不好?如果您提供更多详细信息,我们可以尝试修改建议以适应您实际的问题。

1

考虑到“PropertyVisibleOnlyForService1”和“PropertyVisibleOnlyForService2”被“User”用于数据库操作,因为它们是数据库表中的列...这意味着我不能将这些属性从“User”中取出并放入派生类中,除非我以某种方式隐藏它们?也许我应该试一试。 - Learner

1
如果您不想使用继承,可以尝试以下方式:
[DataContract]
public class User
{
}

[DataContract]
public class Service1User : User
{
  [DataMember] public string PropertyVisibleOnlyForService1{...}
}

[DataContract]
public class Service2User : User
{
  [DataMember] public string PropertyVisibleOnlyForService2{...}
}

[ServiceContract]
public interface IService1  
{   
   List<Service1User> GetUsers();  // user with 'PropertyVisibleOnlyForService1' inside
}

[ServiceContract]
public interface IService2  
{   
    List<Service2User> GetUsers(); // user with 'PropertyVisibleOnlyForService2' inside 
}

那我不确定你会怎么做。在那一点上,您打破了类型声明的原则。以正常的.NET方式考虑;如果您在应用程序中定义“用户”,则它在任何地方都是相同的类型。某些属性无法从某些其他类或方法中隐藏。

WCF还将将此类型信息打包到生成的WSDL中,并且仅将定义User类型一次,因此它需要知道哪些属性存在。

现在,如果您只关心构造的实际SOAP消息,而不关心WSDL或任何客户端从WSDL生成的内容,则在其为null时可以通过执行以下操作将其不发出该属性到SOAP消息中:

    [DataMember(EmitDefaultValue=false)]

当该属性为空时,它不会被包含在序列化中。但是,如果客户端是从WSDL生成的,这将没有什么实际区别,因为其用户类型仍必须包含两个属性。这只是改变了序列化方式,以便发送给客户端类似于:
<User>
  <PropertyVisibleOnlyForService1 nil="true" />
  <PropertyVisibleOnlyForService2>something</PropertyVisibleOnlyForService2>
</User>

它反而会发送:

<User>
  <PropertyVisibleOnlyForService2>something</PropertyVisibleOnlyForService2>
</User>

谢谢您的观点和如何使用'Emit'的建议。我认为将其记在心中是很好的,也许将来我会用到它。 - Learner

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