1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Data ;
4+ using System . Data . Common ;
5+ using System . Linq ;
6+ using System . Threading ;
7+ using System . Threading . Tasks ;
8+ using Dapper ;
9+ using Dibix . Hosting . Abstractions . Data ;
10+ using Dibix . Worker . Abstractions ;
11+ using Microsoft . Extensions . Logging ;
12+ using static System . Formats . Asn1 . AsnWriter ;
13+
14+ namespace Dibix . Worker . Host
15+ {
16+ internal sealed class ServiceBrokerDapperDatabaseAccessorFactory : IDatabaseAccessorFactory
17+ {
18+ private readonly IDatabaseConnectionResolver _connectionResolver ;
19+ private readonly ILogger _logger ;
20+
21+ public ServiceBrokerDapperDatabaseAccessorFactory ( IDatabaseConnectionResolver connectionResolver , CreateDatabaseLogger loggerFactory )
22+ {
23+ _connectionResolver = connectionResolver ;
24+ _logger = loggerFactory ( ) ;
25+ }
26+
27+ public IDatabaseAccessor Create ( )
28+ {
29+ DbConnection connection = _connectionResolver . Resolve ( ) ;
30+ return new ServiceBrokerDapperDatabaseAccessor ( connection , _logger ) ;
31+ }
32+ }
33+
34+ internal sealed class ServiceBrokerDapperDatabaseAccessor : DatabaseAccessor
35+ {
36+ private readonly ILogger _logger ;
37+
38+ public ServiceBrokerDapperDatabaseAccessor ( DbConnection connection , ILogger logger ) : base ( connection )
39+ {
40+ _logger = logger ;
41+ }
42+
43+ protected override int Execute ( string commandText , CommandType commandType , ParametersVisitor parameters , int ? commandTimeout ) => throw new NotImplementedException ( ) ;
44+
45+ protected override Task < int > ExecuteAsync ( string commandText , CommandType commandType , ParametersVisitor parameters , int ? commandTimeout , CancellationToken cancellationToken ) => throw new NotImplementedException ( ) ;
46+
47+ protected override IEnumerable < T > QueryMany < T > ( string commandText , CommandType commandType , ParametersVisitor parameters ) => throw new NotImplementedException ( ) ;
48+
49+ protected override IEnumerable < T > QueryMany < T > ( string commandText , CommandType commandType , ParametersVisitor parameters , bool buffered ) => throw new NotImplementedException ( ) ;
50+
51+ protected override async Task < IEnumerable < T > > QueryManyAsync < T > ( string commandText , CommandType commandType , ParametersVisitor parameters , bool buffered , CancellationToken cancellationToken )
52+ {
53+ using DbCommand command = Connection . CreateCommand ( ) ;
54+ command . CommandText = commandText ;
55+ command . CommandType = commandType ;
56+ command . CommandTimeout = ServiceBrokerDefaults . CommandTimeout ;
57+
58+ parameters . VisitInputParameters ( ( name , type , value , isOutput , customInputType ) => CollectInputParameter ( command , name , type , value , isOutput , customInputType ) ) ;
59+
60+ using ( EnterCancellationScope ( cancellationToken , command ) )
61+ {
62+ // Normally we would use ExecuteReaderAsync(cancellationToken) here,
63+ // but our receive procedures are using RAISERROR WITH NOWAIT to report progress in realtime.
64+ // The usage of NOWAIT however seems to block the cancellation.
65+ // Therefore we have to use the sync method and cancel the command ourselves.
66+ // See: https://stackoverflow.com/questions/24738417/canceling-sql-server-query-with-cancellationtoken/24834029#24834029
67+ ICollection < T > result ;
68+ using ( IDataReader reader = await Task . Run ( command . ExecuteReader , cancellationToken ) . ConfigureAwait ( false ) )
69+ {
70+ result = reader . Parse < T > ( ) . ToArray ( ) ;
71+ }
72+ parameters . VisitOutputParameters ( name => CollectOutputParameter ( command , name ) ) ;
73+ return result ;
74+ }
75+ }
76+
77+ protected override IEnumerable < TReturn > QueryMany < TReturn > ( string commandText , CommandType commandType , ParametersVisitor parameters , Type [ ] types , Func < object [ ] , TReturn > map , string splitOn ) => throw new NotImplementedException ( ) ;
78+
79+ protected override T QuerySingleOrDefault < T > ( string commandText , CommandType commandType , ParametersVisitor parameters ) => throw new NotImplementedException ( ) ;
80+
81+ protected override Task < T > QuerySingleOrDefaultAsync < T > ( string commandText , CommandType commandType , ParametersVisitor parameters , CancellationToken cancellationToken ) => throw new NotImplementedException ( ) ;
82+
83+ protected override IMultipleResultReader QueryMultiple ( string commandText , CommandType commandType , ParametersVisitor parameters ) => throw new NotImplementedException ( ) ;
84+
85+ protected override Task < IMultipleResultReader > QueryMultipleAsync ( string commandText , CommandType commandType , ParametersVisitor parameters , CancellationToken cancellationToken ) => throw new NotImplementedException ( ) ;
86+
87+ protected override void OnInfoMessage ( string message ) => _logger . LogDebug ( $ "[SQL] { message } ") ;
88+
89+ protected override void DisposeConnection ( )
90+ {
91+ // Disposal of the connection is responsibility of the consumer.
92+ // This is currently done by registering this as a scoped instance, that will be disposed after each request.
93+ }
94+
95+ private IDisposable EnterCancellationScope ( CancellationToken cancellationToken , IDbCommand command )
96+ {
97+ return cancellationToken . Register ( ( ) => HandleCancellationRequest ( command ) ) ;
98+ }
99+
100+ private void HandleCancellationRequest ( IDbCommand command )
101+ {
102+ _logger . LogDebug ( "Cancelling current service broker queue read operation" ) ;
103+ command ? . Cancel ( ) ; // this method throws a SqlException => catch needed!
104+ }
105+
106+ private static void CollectInputParameter ( DbCommand command , string name , DbType type , object value , bool isOutput , CustomInputType customInputType )
107+ {
108+ DbParameter parameter = command . CreateParameter ( ) ;
109+ parameter . ParameterName = name ;
110+ parameter . DbType = type ;
111+ parameter . Value = value ;
112+ parameter . Direction = isOutput ? ParameterDirection . Output : ParameterDirection . Input ;
113+ command . Parameters . Add ( parameter ) ;
114+ }
115+
116+ private static object ? CollectOutputParameter ( DbCommand command , string name ) => command . Parameters [ name ] . Value ;
117+ }
118+ }
0 commit comments