2011-11-04 43 views
1

我想挑选您的大脑,了解如何摆脱SQL Server超时方案。为了演示目的,我有两个(自我描述的)WCF服务,UserServiceAuthorizationService在事务性WCF操作中摆脱SQL Server数据库超时

我有一个操作CREATEUSER()UserServiceAssignRole()AuthorizationService。如果两个操作都不存在,那么这两个操作或者启动一个事务,或者如果一个事务由调用者启动,则加入事务

服务合同

[ServiceContract(Namespace="http://tempuri.org/")] 
public interface IUserService 
{ 
    [OperationContract] 
    void CreateUser(UserData user); 
} 

[ServiceContract(Namespace="http://tempuri.org/")] 
public interface IAuthorizationService 
{ 
    [OperationContract] 
    void AssignRole(UserData user, RoleData role); 
} 

代理

public sealed class UserProxy : ClientBase<IUserService>, IUserService 
{ 
    void IUserService.CreateUser(UserData user) 
    { 
     Channel.CreateUser(user); 
    } 
} 

public sealed class AuthorizationProxy : ClientBase<IAuthorizationService>, IAuthorizationService 
{ 
    void IAuthorizationService.AssignRole(UserData user, RoleData role) 
    { 
     Channel.AssignRole(user, role); 
    } 
} 

实施

public sealed class UserService : IUserService 
{ 
    [OperationBehavior(TransactionScopeRequired=true)] 
    void IUserService.CreateUser(UserData user) 
    { 
     // code to create user in database 

     IAuthorizationService authProxy = new AuthorizationProxy(); 
     // call AuthorizationService.AssignRole to assign user a default role 
     authProxy.AssignRole(user, /* default role */); 
    } 
} 

public sealed class AuthorizationService: IAuthorizationService 
{ 
    [OperationBehavior(TransactionScopeRequired=true)] 
    void IAuthorizationService.AssignRole(UserData user, RoleData role) 
    { 
     // code to confirm that user exists (database read) => YOU GET A TIMEOUT HERE, since user created in UserService.CreateUser() has not been committed 
    } 
} 

说明

UserService.CreateUser()操作需要调用AuthorizationService.AssignRole()为正在创建一个用户分配一个缺省的作用。这个想法是,UserService.CreateUser()操作的成功与其成功执行和AuthorizationService.AssignRole()的绑定以分配默认角色,因此它们都是在单个事务中发生的非常重要的。

注:AuthorizationService.AssignRole()不仅从UserService.CreateUser(叫)所以它总是要执行自己的检查以确认该用户是否存在。

麻烦:当UserService.CreateUser()操作被调用时,它会启动一个事务,当它调用AuthorizationService.AssignRole()分配默认角色,操作将加入现有的交易和检查用户是否存在会导致超时,因为事务未完成并且用户未提交。

我知道一个快速和肮脏的(可能?)的方式来解决这个问题是一个布尔参数添加到AuthorizationService.AssignRole(),这样我可以用它来控制用户存在检查是否应该做的(即绕过检查,如果用户正在创建过程中),但我想听听是否有更多优雅的方法来绕过此超时方案。

回答

1

令人尴尬的部分是,“超时”并不完全是因为无法读取事务中未提交的数据。我上面画的示范场景只是故事的一半。在真实环境中,我发现绑定配置设置为参与交易的服务之一没有交易流程属性设置为真实 - 一个昂贵的疏忽。为了演示这一点,请看看WCF 主机中配置文件的system.serviceModel部分。有两个绑定,一个配置为处理大量消息,另一个具有典型/默认设置。由于缺少了transactionFlow =“true”,即使所有参与的ServiceContracts,OperationContracts和实现都使用正确的属性进行修饰,由于不一致,也会出现超时。

<system.serviceModel> 
    <bindings> 
     <netNamedPipeBinding> 
      <binding name="defaultNNPBinding" maxReceivedMessageSize="65536" maxBufferSize="65536" maxBufferPoolSize="524288" 
       openTimeout="00:01:00" receiveTimeout="00:01:00" sendTimeout="00:01:00" closeTimeout="00:01:00" > 
        <!-- ... --> 
      </binding> 
      <binding name="largeMessageNNPBinding" maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" maxBufferPoolSize="524288" 
       openTimeout="00:15:00" receiveTimeout="00:15:00" sendTimeout="00:15:00" closeTimeout="00:15:00" transactionFlow="true"> 
        <!-- ... --> 
      </binding> 
     </netNamedPipeBinding> 
    </bindings> 
    <services> 
     <service name="ServiceLib.UserService" behaviorConfiguration="LargeMessageServiceBehavior"> 
      <endpoint address="net.pipe://localhost/wcf/samples/UserService" binding="netNamedPipeBinding" 
       bindingConfiguration="largeMessageNNPBinding" contract="ServiceLib.Contracts.IUserService"/> 
     </service> 
     <service name="ServiceLib.AuthorizationService" behaviorConfiguration="DefaultServiceBehavior"> 
      <endpoint address="net.pipe://localhost/wcf/samples/AuthorizationService" binding="netNamedPipeBinding" 
       bindingConfiguration="defaultNNPBinding" contract="ServiceLib.Contracts.IAuthorizationService"/> 
     </service> 
    </services> 
    <client> 
     <endpoint address="net.pipe://localhost/wcf/samples/AuthorizationService" binding="netNamedPipeBinding" 
      bindingConfiguration="defaultNNPBinding" contract="ServiceLib.Contracts.IAuthorizationService" name="authorizationservice" /> 
    </client> 
    <behaviors> 
     <serviceBehaviors> 
      <behavior name="LargeMessageServiceBehavior"> 
       <dataContractSerializer maxItemsInObjectGraph="2147483647" /> 
      </behavior> 
      <behavior name="DefaultServiceBehavior"> 
      </behavior> 
     </serviceBehaviors> 
    </behaviors> 
</system.serviceModel> 

好的部分是,有一些重要的经验教训可以从中学到。感谢@Bob和所有其他人的贡献。现在,我会投票给关闭这篇文章

+0

对你感到羞耻!不,真的,搞清楚了。 +1 –

0

AssignRole作业不应该只是分配角色,而不是检查是否有有效的用户。

另一种方法可以检查是否有一个有效的用户之前AssignRole被调用,在CreateUser的情况下,你可能不需要做这个检查。

+0

@Bob:嗯......你在说单一责任原则。这将意味着改变一些东西,方法明智。这不是一个坏主意,但这意味着AuthorizationService的消费者会调用​​AssignRole()来确保他们在调用AssignRole()之前调用某个“CheckIfUserExists”操作,或者沿着这些行调用某些其他实现变体。 –

+0

@John,有很多SRP,但如果它意味着很多改变,那么你需要权衡这个简单的使用布尔值。 –

1

您的示例场景似乎意味着相互矛盾的期望。您写道:

AuthorizationService.AssignRole()不仅从UserService.CreateUser()中调用,所以它总是必须执行自己的检查以确认用户是否存在。

显然,这不可能是真实的,如果它也必须UserService.CreateUser是真实的:

... UserService.CreateUser()操作是依赖于它的两个成功执行和成功的AuthorizationService.AssignRole()来分配一个默认角色,所以它们非常重要,它们都发生在单个事务中。

AuthorizationService.AssignRole不能同时要求用户和要求的用户。

有两种基本的解决方案:

  1. 变化AuthorizationService.AssignRole,以便用户是不需要。两个不同的代码分支将处理你提到的两个场景:(a)正在创建一个新用户; (b)用户已经存在。
  2. 排除交易UserService.CreateUser致电AuthorizationService.AssignRole
+0

我的意思是,可以为已经存在的用户调用AssignRole(),即不仅在创建用户时。我希望澄清我的意思 –

+0

你的建议是有益的,值得考虑。我猜这篇文章的使命是集思广益,看看是否存在允许“脏”读取的调整?即READ UNCOMMITTED。使用LINQ会有这样的事情吗? 。 –

+1

如果CreateUser和AssignRole确实在同一事务中,则不应该需要脏读。 WCF交易的规则不包括我,但你不需要允许TransactionFlow(或类似的)。 TransactionInformation会给你一个LocalIdentifier来确保它是同一个事务。 (这里的类/方法名称可能是错的!) –