SqlDependency.cs 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  1. //------------------------------------------------------------------------------
  2. // <copyright file="SqlDependency.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">[....]</owner>
  6. // <owner current="true" primary="true">[....]</owner>
  7. // <owner current="false" primary="false">[....]</owner>
  8. //------------------------------------------------------------------------------
  9. namespace System.Data.SqlClient {
  10. using System;
  11. using System.Collections;
  12. using System.Collections.Generic;
  13. using System.Data;
  14. using System.Data.Common;
  15. using System.Data.ProviderBase;
  16. using System.Data.Sql;
  17. using System.Data.SqlClient;
  18. using System.Diagnostics;
  19. using System.Globalization;
  20. using System.IO;
  21. using System.Runtime.CompilerServices;
  22. using System.Runtime.InteropServices;
  23. using System.Runtime.Remoting;
  24. using System.Runtime.Serialization.Formatters.Binary;
  25. using System.Security;
  26. using System.Security.Cryptography;
  27. using System.Security.Permissions;
  28. using System.Security.Principal;
  29. using System.Text;
  30. using System.Threading;
  31. using System.Net;
  32. using System.Xml;
  33. using System.Runtime.Versioning;
  34. public sealed class SqlDependency {
  35. // ---------------------------------------------------------------------------------------------------------
  36. // Private class encapsulating the user/identity information - either SQL Auth username or Windows identity.
  37. // ---------------------------------------------------------------------------------------------------------
  38. internal class IdentityUserNamePair {
  39. private DbConnectionPoolIdentity _identity;
  40. private string _userName;
  41. internal IdentityUserNamePair(DbConnectionPoolIdentity identity, string userName) {
  42. Debug.Assert( (identity == null && userName != null) ||
  43. (identity != null && userName == null), "Unexpected arguments!");
  44. _identity = identity;
  45. _userName = userName;
  46. }
  47. internal DbConnectionPoolIdentity Identity {
  48. get {
  49. return _identity;
  50. }
  51. }
  52. internal string UserName {
  53. get {
  54. return _userName;
  55. }
  56. }
  57. override public bool Equals(object value) {
  58. IdentityUserNamePair temp = (IdentityUserNamePair) value;
  59. bool result = false;
  60. if (null == temp) { // If passed value null - false.
  61. result = false;
  62. }
  63. else if (this == temp) { // If instances equal - true.
  64. result = true;
  65. }
  66. else {
  67. if (_identity != null) {
  68. if (_identity.Equals(temp._identity)) {
  69. result = true;
  70. }
  71. }
  72. else if (_userName == temp._userName) {
  73. result = true;
  74. }
  75. }
  76. return result;
  77. }
  78. override public int GetHashCode() {
  79. int hashValue = 0;
  80. if (null != _identity) {
  81. hashValue = _identity.GetHashCode();
  82. }
  83. else {
  84. hashValue = _userName.GetHashCode();
  85. }
  86. return hashValue;
  87. }
  88. }
  89. // ----------------------------------------
  90. // END IdentityHashHelper private class.
  91. // ----------------------------------------
  92. // ----------------------------------------------------------------------
  93. // Private class encapsulating the database, service info and hash logic.
  94. // ----------------------------------------------------------------------
  95. private class DatabaseServicePair {
  96. private string _database;
  97. private string _service; // Store the value, but don't use for equality or hashcode!
  98. internal DatabaseServicePair(string database, string service) {
  99. Debug.Assert(database != null, "Unexpected argument!");
  100. _database = database;
  101. _service = service;
  102. }
  103. internal string Database {
  104. get {
  105. return _database;
  106. }
  107. }
  108. internal string Service {
  109. get {
  110. return _service;
  111. }
  112. }
  113. override public bool Equals(object value) {
  114. DatabaseServicePair temp = (DatabaseServicePair) value;
  115. bool result = false;
  116. if (null == temp) { // If passed value null - false.
  117. result = false;
  118. }
  119. else if (this == temp) { // If instances equal - true.
  120. result = true;
  121. }
  122. else if (_database == temp._database) {
  123. result = true;
  124. }
  125. return result;
  126. }
  127. override public int GetHashCode() {
  128. return _database.GetHashCode();
  129. }
  130. }
  131. // ----------------------------------------
  132. // END IdentityHashHelper private class.
  133. // ----------------------------------------
  134. // ----------------------------------------------------------------------------
  135. // Private class encapsulating the event and it's registered execution context.
  136. // ----------------------------------------------------------------------------
  137. internal class EventContextPair {
  138. private OnChangeEventHandler _eventHandler;
  139. private ExecutionContext _context;
  140. private SqlDependency _dependency;
  141. private SqlNotificationEventArgs _args;
  142. static private ContextCallback _contextCallback = new ContextCallback(InvokeCallback);
  143. internal EventContextPair(OnChangeEventHandler eventHandler, SqlDependency dependency) {
  144. Debug.Assert(eventHandler != null && dependency != null, "Unexpected arguments!");
  145. _eventHandler = eventHandler;
  146. _context = ExecutionContext.Capture();
  147. _dependency = dependency;
  148. }
  149. override public bool Equals(object value) {
  150. EventContextPair temp = (EventContextPair) value;
  151. bool result = false;
  152. if (null == temp) { // If passed value null - false.
  153. result = false;
  154. }
  155. else if (this == temp) { // If instances equal - true.
  156. result = true;
  157. }
  158. else {
  159. if (_eventHandler == temp._eventHandler) { // Handler for same delegates are reference equivalent.
  160. result = true;
  161. }
  162. }
  163. return result;
  164. }
  165. override public int GetHashCode() {
  166. return _eventHandler.GetHashCode();
  167. }
  168. internal void Invoke(SqlNotificationEventArgs args) {
  169. _args = args;
  170. ExecutionContext.Run(_context, _contextCallback, this);
  171. }
  172. private static void InvokeCallback(object eventContextPair) {
  173. EventContextPair pair = (EventContextPair) eventContextPair;
  174. pair._eventHandler(pair._dependency, (SqlNotificationEventArgs) pair._args);
  175. }
  176. }
  177. // ----------------------------------------
  178. // END EventContextPair private class.
  179. // ----------------------------------------
  180. // ----------------
  181. // Instance members
  182. // ----------------
  183. // SqlNotificationRequest required state members
  184. // Only used for SqlDependency.Id.
  185. private readonly string _id = Guid.NewGuid().ToString() + ";" + _appDomainKey;
  186. private string _options; // Concat of service & db, in the form "service=x;local database=y".
  187. private int _timeout;
  188. // Various SqlDependency required members
  189. private bool _dependencyFired = false;
  190. // SQL BU DT 382314 - we are required to implement our own event collection to preserve ExecutionContext on callback.
  191. private List<EventContextPair> _eventList = new List<EventContextPair>();
  192. private object _eventHandlerLock = new object(); // Lock for event serialization.
  193. // Track the time that this dependency should time out. If the server didn't send a change
  194. // notification or a time-out before this point then the client will perform a client-side
  195. // timeout.
  196. private DateTime _expirationTime = DateTime.MaxValue;
  197. // Used for invalidation of dependencies based on which servers they rely upon.
  198. // It's possible we will over invalidate if unexpected server failure occurs (but not server down).
  199. private List<string> _serverList = new List<string>();
  200. // --------------
  201. // Static members
  202. // --------------
  203. private static object _startStopLock = new object();
  204. private static readonly string _appDomainKey = Guid.NewGuid().ToString();
  205. // Hashtable containing all information to match from a server, user, database triple to the service started for that
  206. // triple. For each server, there can be N users. For each user, there can be N databases. For each server, user,
  207. // database, there can only be one service.
  208. private static Dictionary<string, Dictionary<IdentityUserNamePair, List<DatabaseServicePair>>> _serverUserHash =
  209. new Dictionary<string, Dictionary<IdentityUserNamePair, List<DatabaseServicePair>>>(StringComparer.OrdinalIgnoreCase);
  210. private static SqlDependencyProcessDispatcher _processDispatcher = null;
  211. // The following two strings are used for AppDomain.CreateInstance.
  212. private static readonly string _assemblyName = (typeof(SqlDependencyProcessDispatcher)).Assembly.FullName;
  213. private static readonly string _typeName = (typeof(SqlDependencyProcessDispatcher)).FullName;
  214. // -----------
  215. // BID members
  216. // -----------
  217. internal const Bid.ApiGroup NotificationsTracePoints = (Bid.ApiGroup)0x2000;
  218. private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
  219. private static int _objectTypeCount; // Bid counter
  220. internal int ObjectID {
  221. get {
  222. return _objectID;
  223. }
  224. }
  225. // ------------
  226. // Constructors
  227. // ------------
  228. [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)]
  229. public SqlDependency() : this(null, null, SQL.SqlDependencyTimeoutDefault) {
  230. }
  231. [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)]
  232. public SqlDependency(SqlCommand command) : this(command, null, SQL.SqlDependencyTimeoutDefault) {
  233. }
  234. [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)]
  235. public SqlDependency(SqlCommand command, string options, int timeout) {
  236. IntPtr hscp;
  237. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency|DEP> %d#, options: '%ls', timeout: '%d'", ObjectID, options, timeout);
  238. try {
  239. if (InOutOfProcHelper.InProc) {
  240. throw SQL.SqlDepCannotBeCreatedInProc();
  241. }
  242. if (timeout < 0) {
  243. throw SQL.InvalidSqlDependencyTimeout("timeout");
  244. }
  245. _timeout = timeout;
  246. if (null != options) { // Ignore null value - will force to default.
  247. _options = options;
  248. }
  249. AddCommandInternal(command);
  250. SqlDependencyPerAppDomainDispatcher.SingletonInstance.AddDependencyEntry(this); // Add dep to hashtable with Id.
  251. }
  252. finally {
  253. Bid.ScopeLeave(ref hscp);
  254. }
  255. }
  256. // -----------------
  257. // Public Properties
  258. // -----------------
  259. [
  260. ResCategoryAttribute(Res.DataCategory_Data),
  261. ResDescriptionAttribute(Res.SqlDependency_HasChanges)
  262. ]
  263. public bool HasChanges {
  264. get {
  265. return _dependencyFired;
  266. }
  267. }
  268. [
  269. ResCategoryAttribute(Res.DataCategory_Data),
  270. ResDescriptionAttribute(Res.SqlDependency_Id)
  271. ]
  272. public string Id {
  273. get {
  274. return _id;
  275. }
  276. }
  277. // -------------------
  278. // Internal Properties
  279. // -------------------
  280. internal static string AppDomainKey {
  281. get {
  282. return _appDomainKey;
  283. }
  284. }
  285. internal DateTime ExpirationTime {
  286. get {
  287. return _expirationTime;
  288. }
  289. }
  290. internal string Options {
  291. get {
  292. string result = null;
  293. if (null != _options) {
  294. result = _options;
  295. }
  296. return result;
  297. }
  298. }
  299. internal static SqlDependencyProcessDispatcher ProcessDispatcher {
  300. get {
  301. return _processDispatcher;
  302. }
  303. }
  304. internal int Timeout {
  305. get {
  306. return _timeout;
  307. }
  308. }
  309. // ------
  310. // Events
  311. // ------
  312. [
  313. ResCategoryAttribute(Res.DataCategory_Data),
  314. ResDescriptionAttribute(Res.SqlDependency_OnChange)
  315. ]
  316. public event OnChangeEventHandler OnChange {
  317. // EventHandlers to be fired when dependency is notified.
  318. add {
  319. IntPtr hscp;
  320. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.OnChange-Add|DEP> %d#", ObjectID);
  321. try {
  322. if (null != value) {
  323. SqlNotificationEventArgs sqlNotificationEvent = null;
  324. lock (_eventHandlerLock) {
  325. if (_dependencyFired) { // If fired, fire the new event immediately.
  326. Bid.NotificationsTrace("<sc.SqlDependency.OnChange-Add|DEP> Dependency already fired, firing new event.\n");
  327. sqlNotificationEvent = new SqlNotificationEventArgs(SqlNotificationType.Subscribe, SqlNotificationInfo.AlreadyChanged, SqlNotificationSource.Client);
  328. }
  329. else {
  330. Bid.NotificationsTrace("<sc.SqlDependency.OnChange-Add|DEP> Dependency has not fired, adding new event.\n");
  331. EventContextPair pair = new EventContextPair(value, this);
  332. if (!_eventList.Contains(pair)) {
  333. _eventList.Add(pair);
  334. }
  335. else {
  336. throw SQL.SqlDependencyEventNoDuplicate(); // SQL BU DT 382314
  337. }
  338. }
  339. }
  340. if (null != sqlNotificationEvent) { // Delay firing the event until outside of lock.
  341. value(this, sqlNotificationEvent);
  342. }
  343. }
  344. }
  345. finally {
  346. Bid.ScopeLeave(ref hscp);
  347. }
  348. }
  349. remove {
  350. IntPtr hscp;
  351. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.OnChange-Remove|DEP> %d#", ObjectID);
  352. try {
  353. if (null != value) {
  354. EventContextPair pair = new EventContextPair(value, this);
  355. lock (_eventHandlerLock) {
  356. int index = _eventList.IndexOf(pair);
  357. if (0 <= index) {
  358. _eventList.RemoveAt(index);
  359. }
  360. }
  361. }
  362. }
  363. finally {
  364. Bid.ScopeLeave(ref hscp);
  365. }
  366. }
  367. }
  368. // --------------
  369. // Public Methods
  370. // --------------
  371. [
  372. ResCategoryAttribute(Res.DataCategory_Data),
  373. ResDescriptionAttribute(Res.SqlDependency_AddCommandDependency)
  374. ]
  375. public void AddCommandDependency(SqlCommand command) {
  376. // Adds command to dependency collection so we automatically create the SqlNotificationsRequest object
  377. // and listen for a notification for the added commands.
  378. IntPtr hscp;
  379. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.AddCommandDependency|DEP> %d#", ObjectID);
  380. try {
  381. if (command == null) {
  382. throw ADP.ArgumentNull("command");
  383. }
  384. AddCommandInternal(command);
  385. }
  386. finally {
  387. Bid.ScopeLeave(ref hscp);
  388. }
  389. }
  390. [System.Security.Permissions.ReflectionPermission(System.Security.Permissions.SecurityAction.Assert, MemberAccess=true)]
  391. private static ObjectHandle CreateProcessDispatcher(_AppDomain masterDomain) {
  392. return masterDomain.CreateInstance(_assemblyName, _typeName);
  393. }
  394. // ----------------------------------
  395. // Static Methods - public & internal
  396. // ----------------------------------
  397. // Method to obtain AppDomain reference and then obtain the reference to the process wide dispatcher for
  398. // Start() and Stop() method calls on the individual SqlDependency instances.
  399. // SxS: this method retrieves the primary AppDomain stored in native library. Since each System.Data.dll has its own copy of native
  400. // library, this call is safe in SxS
  401. [ResourceExposure(ResourceScope.None)]
  402. [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
  403. private static void ObtainProcessDispatcher() {
  404. byte[] nativeStorage = SNINativeMethodWrapper.GetData();
  405. if (nativeStorage == null) {
  406. Bid.NotificationsTrace("<sc.SqlDependency.ObtainProcessDispatcher|DEP> nativeStorage null, obtaining dispatcher AppDomain and creating ProcessDispatcher.\n");
  407. #if DEBUG // Possibly expensive, limit to debug.
  408. Bid.NotificationsTrace("<sc.SqlDependency.ObtainProcessDispatcher|DEP> AppDomain.CurrentDomain.FriendlyName: %ls\n", AppDomain.CurrentDomain.FriendlyName);
  409. #endif
  410. _AppDomain masterDomain = SNINativeMethodWrapper.GetDefaultAppDomain();
  411. if (null != masterDomain) {
  412. ObjectHandle handle = CreateProcessDispatcher(masterDomain);
  413. if (null != handle) {
  414. SqlDependencyProcessDispatcher dependency = (SqlDependencyProcessDispatcher) handle.Unwrap();
  415. if (null != dependency) {
  416. _processDispatcher = dependency.SingletonProcessDispatcher; // Set to static instance.
  417. // Serialize and set in native.
  418. ObjRef objRef = GetObjRef(_processDispatcher);
  419. BinaryFormatter formatter = new BinaryFormatter();
  420. MemoryStream stream = new MemoryStream();
  421. GetSerializedObject(objRef, formatter, stream);
  422. SNINativeMethodWrapper.SetData(stream.GetBuffer()); // Native will be forced to synchronize and not overwrite.
  423. }
  424. else {
  425. Bid.NotificationsTrace("<sc.SqlDependency.ObtainProcessDispatcher|DEP|ERR> ERROR - ObjectHandle.Unwrap returned null!\n");
  426. throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyObtainProcessDispatcherFailureObjectHandle);
  427. }
  428. }
  429. else {
  430. Bid.NotificationsTrace("<sc.SqlDependency.ObtainProcessDispatcher|DEP|ERR> ERROR - AppDomain.CreateInstance returned null!\n");
  431. throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyProcessDispatcherFailureCreateInstance);
  432. }
  433. }
  434. else {
  435. Bid.NotificationsTrace("<sc.SqlDependency.ObtainProcessDispatcher|DEP|ERR> ERROR - unable to obtain default AppDomain!\n");
  436. throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyProcessDispatcherFailureAppDomain);
  437. }
  438. }
  439. else {
  440. Bid.NotificationsTrace("<sc.SqlDependency.ObtainProcessDispatcher|DEP> nativeStorage not null, obtaining existing dispatcher AppDomain and ProcessDispatcher.\n");
  441. #if DEBUG // Possibly expensive, limit to debug.
  442. Bid.NotificationsTrace("<sc.SqlDependency.ObtainProcessDispatcher|DEP> AppDomain.CurrentDomain.FriendlyName: %ls\n", AppDomain.CurrentDomain.FriendlyName);
  443. #endif
  444. BinaryFormatter formatter = new BinaryFormatter();
  445. MemoryStream stream = new MemoryStream(nativeStorage);
  446. _processDispatcher = GetDeserializedObject(formatter, stream); // Deserialize and set for appdomain.
  447. Bid.NotificationsTrace("<sc.SqlDependency.ObtainProcessDispatcher|DEP> processDispatcher obtained, ID: %d\n", _processDispatcher.ObjectID);
  448. }
  449. }
  450. // ---------------------------------------------------------
  451. // Static security asserted methods - limit scope of assert.
  452. // ---------------------------------------------------------
  453. [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.RemotingConfiguration)]
  454. private static ObjRef GetObjRef(SqlDependencyProcessDispatcher _processDispatcher) {
  455. return RemotingServices.Marshal(_processDispatcher);
  456. }
  457. [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.SerializationFormatter)]
  458. private static void GetSerializedObject(ObjRef objRef, BinaryFormatter formatter, MemoryStream stream) {
  459. formatter.Serialize(stream, objRef);
  460. }
  461. [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.SerializationFormatter)]
  462. private static SqlDependencyProcessDispatcher GetDeserializedObject(BinaryFormatter formatter, MemoryStream stream) {
  463. object result = formatter.Deserialize(stream);
  464. Debug.Assert(result.GetType() == typeof(SqlDependencyProcessDispatcher), "Unexpected type stored in native!");
  465. return (SqlDependencyProcessDispatcher) result;
  466. }
  467. // -------------------------
  468. // Static Start/Stop methods
  469. // -------------------------
  470. [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)]
  471. public static bool Start(string connectionString) {
  472. return Start(connectionString, null, true);
  473. }
  474. [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)]
  475. public static bool Start(string connectionString, string queue) {
  476. return Start(connectionString, queue, false);
  477. }
  478. internal static bool Start(string connectionString, string queue, bool useDefaults) {
  479. IntPtr hscp;
  480. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.Start|DEP> AppDomainKey: '%ls', queue: '%ls'", AppDomainKey, queue);
  481. try {
  482. // The following code exists in Stop as well. It exists here to demand permissions as high in the stack
  483. // as possible.
  484. if (InOutOfProcHelper.InProc) {
  485. throw SQL.SqlDepCannotBeCreatedInProc();
  486. }
  487. if (ADP.IsEmpty(connectionString)) {
  488. if (null == connectionString) {
  489. throw ADP.ArgumentNull("connectionString");
  490. }
  491. else {
  492. throw ADP.Argument("connectionString");
  493. }
  494. }
  495. if (!useDefaults && ADP.IsEmpty(queue)) { // If specified but null or empty, use defaults.
  496. useDefaults = true;
  497. queue = null; // Force to null - for proper hashtable comparison for default case.
  498. }
  499. // Create new connection options for demand on their connection string. We modify the connection string
  500. // and assert on our modified string when we create the container.
  501. SqlConnectionString connectionStringObject = new SqlConnectionString(connectionString);
  502. connectionStringObject.DemandPermission();
  503. if (connectionStringObject.LocalDBInstance!=null) {
  504. LocalDBAPI.DemandLocalDBPermissions();
  505. }
  506. // End duplicate Start/Stop logic.
  507. bool errorOccurred = false;
  508. bool result = false;
  509. lock (_startStopLock) {
  510. try {
  511. if (null == _processDispatcher) { // Ensure _processDispatcher reference is present - inside lock.
  512. ObtainProcessDispatcher();
  513. }
  514. if (useDefaults) { // Default listener.
  515. string server = null;
  516. DbConnectionPoolIdentity identity = null;
  517. string user = null;
  518. string database = null;
  519. string service = null;
  520. bool appDomainStart = false;
  521. RuntimeHelpers.PrepareConstrainedRegions();
  522. try { // CER to ensure that if Start succeeds we add to hash completing setup.
  523. // Start using process wide default service/queue & database from connection string.
  524. result = _processDispatcher.StartWithDefault( connectionString,
  525. out server,
  526. out identity,
  527. out user,
  528. out database,
  529. ref service,
  530. _appDomainKey,
  531. SqlDependencyPerAppDomainDispatcher.SingletonInstance,
  532. out errorOccurred,
  533. out appDomainStart);
  534. Bid.NotificationsTrace("<sc.SqlDependency.Start|DEP> Start (defaults) returned: '%d', with service: '%ls', server: '%ls', database: '%ls'\n", result, service, server, database);
  535. }
  536. finally {
  537. if (appDomainStart && !errorOccurred) { // If success, add to hashtable.
  538. IdentityUserNamePair identityUser = new IdentityUserNamePair(identity, user);
  539. DatabaseServicePair databaseService = new DatabaseServicePair(database, service);
  540. if (!AddToServerUserHash(server, identityUser, databaseService)) {
  541. try {
  542. Stop(connectionString, queue, useDefaults, true);
  543. }
  544. catch (Exception e) { // Discard stop failure!
  545. if (!ADP.IsCatchableExceptionType(e)) {
  546. throw;
  547. }
  548. ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now.
  549. Bid.NotificationsTrace("<sc.SqlDependency.Start|DEP|ERR> Exception occurred from Stop() after duplicate was found on Start().\n");
  550. }
  551. throw SQL.SqlDependencyDuplicateStart();
  552. }
  553. }
  554. }
  555. }
  556. else { // Start with specified service/queue & database.
  557. result = _processDispatcher.Start(connectionString,
  558. queue,
  559. _appDomainKey,
  560. SqlDependencyPerAppDomainDispatcher.SingletonInstance);
  561. Bid.NotificationsTrace("<sc.SqlDependency.Start|DEP> Start (user provided queue) returned: '%d'\n", result);
  562. // No need to call AddToServerDatabaseHash since if not using default queue user is required
  563. // to provide options themselves.
  564. }
  565. }
  566. catch (Exception e) {
  567. if (!ADP.IsCatchableExceptionType(e)) {
  568. throw;
  569. }
  570. ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now.
  571. Bid.NotificationsTrace("<sc.SqlDependency.Start|DEP|ERR> Exception occurred from _processDispatcher.Start(...), calling Invalidate(...).\n");
  572. throw;
  573. }
  574. }
  575. return result;
  576. }
  577. finally {
  578. Bid.ScopeLeave(ref hscp);
  579. }
  580. }
  581. [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)]
  582. public static bool Stop(string connectionString) {
  583. return Stop(connectionString, null, true, false);
  584. }
  585. [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)]
  586. public static bool Stop(string connectionString, string queue) {
  587. return Stop(connectionString, queue, false, false);
  588. }
  589. internal static bool Stop(string connectionString, string queue, bool useDefaults, bool startFailed) {
  590. IntPtr hscp;
  591. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.Stop|DEP> AppDomainKey: '%ls', queue: '%ls'", AppDomainKey, queue);
  592. try {
  593. // The following code exists in Stop as well. It exists here to demand permissions as high in the stack
  594. // as possible.
  595. if (InOutOfProcHelper.InProc) {
  596. throw SQL.SqlDepCannotBeCreatedInProc();
  597. }
  598. if (ADP.IsEmpty(connectionString)) {
  599. if (null == connectionString) {
  600. throw ADP.ArgumentNull("connectionString");
  601. }
  602. else {
  603. throw ADP.Argument("connectionString");
  604. }
  605. }
  606. if (!useDefaults && ADP.IsEmpty(queue)) { // If specified but null or empty, use defaults.
  607. useDefaults = true;
  608. queue = null; // Force to null - for proper hashtable comparison for default case.
  609. }
  610. // Create new connection options for demand on their connection string. We modify the connection string
  611. // and assert on our modified string when we create the container.
  612. SqlConnectionString connectionStringObject = new SqlConnectionString(connectionString);
  613. connectionStringObject.DemandPermission();
  614. if (connectionStringObject.LocalDBInstance!=null) {
  615. LocalDBAPI.DemandLocalDBPermissions();
  616. }
  617. // End duplicate Start/Stop logic.
  618. bool result = false;
  619. lock (_startStopLock) {
  620. if (null != _processDispatcher) { // If _processDispatcher null, no Start has been called.
  621. try {
  622. string server = null;
  623. DbConnectionPoolIdentity identity = null;
  624. string user = null;
  625. string database = null;
  626. string service = null;
  627. if (useDefaults) {
  628. bool appDomainStop = false;
  629. RuntimeHelpers.PrepareConstrainedRegions();
  630. try { // CER to ensure that if Stop succeeds we remove from hash completing teardown.
  631. // Start using process wide default service/queue & database from connection string.
  632. result = _processDispatcher.Stop( connectionString,
  633. out server,
  634. out identity,
  635. out user,
  636. out database,
  637. ref service,
  638. _appDomainKey,
  639. out appDomainStop);
  640. }
  641. finally {
  642. if (appDomainStop && !startFailed) { // If success, remove from hashtable.
  643. Debug.Assert(!ADP.IsEmpty(server) && !ADP.IsEmpty(database), "Server or Database null/Empty upon successfull Stop()!");
  644. IdentityUserNamePair identityUser = new IdentityUserNamePair(identity, user);
  645. DatabaseServicePair databaseService = new DatabaseServicePair(database, service);
  646. RemoveFromServerUserHash(server, identityUser, databaseService);
  647. }
  648. }
  649. }
  650. else {
  651. bool ignored = false;
  652. result = _processDispatcher.Stop( connectionString,
  653. out server,
  654. out identity,
  655. out user,
  656. out database,
  657. ref queue,
  658. _appDomainKey,
  659. out ignored);
  660. // No need to call RemoveFromServerDatabaseHash since if not using default queue user is required
  661. // to provide options themselves.
  662. }
  663. }
  664. catch (Exception e) {
  665. if (!ADP.IsCatchableExceptionType(e)) {
  666. throw;
  667. }
  668. ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now.
  669. }
  670. }
  671. }
  672. return result;
  673. }
  674. finally {
  675. Bid.ScopeLeave(ref hscp);
  676. }
  677. }
  678. // --------------------------------
  679. // General static utility functions
  680. // --------------------------------
  681. private static bool AddToServerUserHash(string server, IdentityUserNamePair identityUser, DatabaseServicePair databaseService) {
  682. IntPtr hscp;
  683. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.AddToServerUserHash|DEP> server: '%ls', database: '%ls', service: '%ls'", server, databaseService.Database, databaseService.Service);
  684. try {
  685. bool result = false;
  686. lock (_serverUserHash) {
  687. Dictionary<IdentityUserNamePair, List<DatabaseServicePair>> identityDatabaseHash;
  688. if (!_serverUserHash.ContainsKey(server)) {
  689. Bid.NotificationsTrace("<sc.SqlDependency.AddToServerUserHash|DEP> Hash did not contain server, adding.\n");
  690. identityDatabaseHash = new Dictionary<IdentityUserNamePair, List<DatabaseServicePair>>();
  691. _serverUserHash.Add(server, identityDatabaseHash);
  692. }
  693. else {
  694. identityDatabaseHash = _serverUserHash[server];
  695. }
  696. List<DatabaseServicePair> databaseServiceList;
  697. if (!identityDatabaseHash.ContainsKey(identityUser)) {
  698. Bid.NotificationsTrace("<sc.SqlDependency.AddToServerUserHash|DEP> Hash contained server but not user, adding user.\n");
  699. databaseServiceList = new List<DatabaseServicePair>();
  700. identityDatabaseHash.Add(identityUser, databaseServiceList);
  701. }
  702. else {
  703. databaseServiceList = identityDatabaseHash[identityUser];
  704. }
  705. if (!databaseServiceList.Contains(databaseService)) {
  706. Bid.NotificationsTrace("<sc.SqlDependency.AddToServerUserHash|DEP> Adding database.\n");
  707. databaseServiceList.Add(databaseService);
  708. result = true;
  709. }
  710. else {
  711. Bid.NotificationsTrace("<sc.SqlDependency.AddToServerUserHash|DEP|ERR> ERROR - hash already contained server, user, and database - we will throw!.\n");
  712. }
  713. }
  714. return result;
  715. }
  716. finally {
  717. Bid.ScopeLeave(ref hscp);
  718. }
  719. }
  720. private static void RemoveFromServerUserHash(string server, IdentityUserNamePair identityUser, DatabaseServicePair databaseService) {
  721. IntPtr hscp;
  722. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.RemoveFromServerUserHash|DEP> server: '%ls', database: '%ls', service: '%ls'", server, databaseService.Database, databaseService.Service);
  723. try {
  724. lock (_serverUserHash) {
  725. Dictionary<IdentityUserNamePair, List<DatabaseServicePair>> identityDatabaseHash;
  726. if (_serverUserHash.ContainsKey(server)) {
  727. identityDatabaseHash = _serverUserHash[server];
  728. List<DatabaseServicePair> databaseServiceList;
  729. if (identityDatabaseHash.ContainsKey(identityUser)) {
  730. databaseServiceList = identityDatabaseHash[identityUser];
  731. int index = databaseServiceList.IndexOf(databaseService);
  732. if (index >= 0) {
  733. Bid.NotificationsTrace("<sc.SqlDependency.RemoveFromServerUserHash|DEP> Hash contained server, user, and database - removing database.\n");
  734. databaseServiceList.RemoveAt(index);
  735. if (databaseServiceList.Count == 0) {
  736. Bid.NotificationsTrace("<sc.SqlDependency.RemoveFromServerUserHash|DEP> databaseServiceList count 0, removing the list for this server and user.\n");
  737. identityDatabaseHash.Remove(identityUser);
  738. if (identityDatabaseHash.Count == 0) {
  739. Bid.NotificationsTrace("<sc.SqlDependency.RemoveFromServerUserHash|DEP> identityDatabaseHash count 0, removing the hash for this server.\n");
  740. _serverUserHash.Remove(server);
  741. }
  742. }
  743. }
  744. else {
  745. Bid.NotificationsTrace("<sc.SqlDependency.RemoveFromServerUserHash|DEP|ERR> ERROR - hash contained server and user but not database!\n");
  746. Debug.Assert(false, "Unexpected state - hash did not contain database!");
  747. }
  748. }
  749. else {
  750. Bid.NotificationsTrace("<sc.SqlDependency.RemoveFromServerUserHash|DEP|ERR> ERROR - hash contained server but not user!\n");
  751. Debug.Assert(false, "Unexpected state - hash did not contain user!");
  752. }
  753. }
  754. else {
  755. Bid.NotificationsTrace("<sc.SqlDependency.RemoveFromServerUserHash|DEP|ERR> ERROR - hash did not contain server!\n");
  756. Debug.Assert(false, "Unexpected state - hash did not contain server!");
  757. }
  758. }
  759. }
  760. finally {
  761. Bid.ScopeLeave(ref hscp);
  762. }
  763. }
  764. internal static string GetDefaultComposedOptions(string server, string failoverServer, IdentityUserNamePair identityUser, string database) {
  765. // Server must be an exact match, but user and database only needs to match exactly if there is more than one
  766. // for the given user or database passed. That is ambiguious and we must fail.
  767. IntPtr hscp;
  768. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.GetDefaultComposedOptions|DEP> server: '%ls', failoverServer: '%ls', database: '%ls'", server, failoverServer, database);
  769. try {
  770. string result;
  771. lock (_serverUserHash) {
  772. if (!_serverUserHash.ContainsKey(server)) {
  773. if (0 == _serverUserHash.Count) { // Special error for no calls to start.
  774. Bid.NotificationsTrace("<sc.SqlDependency.GetDefaultComposedOptions|DEP|ERR> ERROR - no start calls have been made, about to throw.\n");
  775. throw SQL.SqlDepDefaultOptionsButNoStart();
  776. }
  777. else if (!ADP.IsEmpty(failoverServer) && _serverUserHash.ContainsKey(failoverServer)) {
  778. Bid.NotificationsTrace("<sc.SqlDependency.GetDefaultComposedOptions|DEP> using failover server instead\n");
  779. server = failoverServer;
  780. }
  781. else {
  782. Bid.NotificationsTrace("<sc.SqlDependency.GetDefaultComposedOptions|DEP|ERR> ERROR - not listening to this server, about to throw.\n");
  783. throw SQL.SqlDependencyNoMatchingServerStart();
  784. }
  785. }
  786. Dictionary<IdentityUserNamePair, List<DatabaseServicePair>> identityDatabaseHash = _serverUserHash[server];
  787. List<DatabaseServicePair> databaseList = null;
  788. if (!identityDatabaseHash.ContainsKey(identityUser)) {
  789. if (identityDatabaseHash.Count > 1) {
  790. Bid.NotificationsTrace("<sc.SqlDependency.GetDefaultComposedOptions|DEP|ERR> ERROR - not listening for this user, but listening to more than one other user, about to throw.\n");
  791. throw SQL.SqlDependencyNoMatchingServerStart();
  792. }
  793. else {
  794. // Since only one user, - use that.
  795. // Foreach - but only one value present.
  796. foreach (KeyValuePair<IdentityUserNamePair, List<DatabaseServicePair>> entry in identityDatabaseHash) {
  797. databaseList = entry.Value;
  798. break; // Only iterate once.
  799. }
  800. }
  801. }
  802. else {
  803. databaseList = identityDatabaseHash[identityUser];
  804. }
  805. DatabaseServicePair pair = new DatabaseServicePair(database, null);
  806. DatabaseServicePair resultingPair = null;
  807. int index = databaseList.IndexOf(pair);
  808. if (index != -1) { // Exact match found, use it.
  809. resultingPair = databaseList[index];
  810. }
  811. if (null != resultingPair) { // Exact database match.
  812. database = FixupServiceOrDatabaseName(resultingPair.Database); // Fixup in place.
  813. string quotedService = FixupServiceOrDatabaseName(resultingPair.Service);
  814. result = "Service="+quotedService+";Local Database="+database;
  815. }
  816. else { // No exact database match found.
  817. if (databaseList.Count == 1) { // If only one database for this server/user, use it.
  818. object[] temp = databaseList.ToArray(); // Must copy, no other choice but foreach.
  819. resultingPair = (DatabaseServicePair) temp[0];
  820. Debug.Assert(temp.Length == 1, "If databaseList.Count==1, why does copied array have length other than 1?");
  821. string quotedDatabase = FixupServiceOrDatabaseName(resultingPair.Database);
  822. string quotedService = FixupServiceOrDatabaseName(resultingPair.Service);
  823. result = "Service="+quotedService+";Local Database="+quotedDatabase;
  824. }
  825. else { // More than one database for given server, ambiguous - fail the default case!
  826. Bid.NotificationsTrace("<sc.SqlDependency.GetDefaultComposedOptions|DEP|ERR> ERROR - SqlDependency.Start called multiple times for this server/user, but no matching database.\n");
  827. throw SQL.SqlDependencyNoMatchingServerDatabaseStart();
  828. }
  829. }
  830. }
  831. Debug.Assert(!ADP.IsEmpty(result), "GetDefaultComposedOptions should never return null or empty string!");
  832. Bid.NotificationsTrace("<sc.SqlDependency.GetDefaultComposedOptions|DEP> resulting options: '%ls'.\n", result);
  833. return result;
  834. }
  835. finally {
  836. Bid.ScopeLeave(ref hscp);
  837. }
  838. }
  839. // ----------------
  840. // Internal Methods
  841. // ----------------
  842. // Called by SqlCommand upon execution of a SqlNotificationRequest class created by this dependency. We
  843. // use this list for a reverse lookup based on server.
  844. internal void AddToServerList(string server) {
  845. IntPtr hscp;
  846. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.AddToServerList|DEP> %d#, server: '%ls'", ObjectID, server);
  847. try {
  848. lock (_serverList) {
  849. int index = _serverList.BinarySearch(server, StringComparer.OrdinalIgnoreCase);
  850. if (0 > index) { // If less than 0, item was not found in list.
  851. Bid.NotificationsTrace("<sc.SqlDependency.AddToServerList|DEP> Server not present in hashtable, adding server: '%ls'.\n", server);
  852. index = ~index; // BinarySearch returns the 2's compliment of where the item should be inserted to preserver a sorted list after insertion.
  853. _serverList.Insert(index, server);
  854. }
  855. }
  856. }
  857. finally {
  858. Bid.ScopeLeave(ref hscp);
  859. }
  860. }
  861. internal bool ContainsServer(string server) {
  862. lock (_serverList) {
  863. return _serverList.Contains(server);
  864. }
  865. }
  866. internal string ComputeHashAndAddToDispatcher(SqlCommand command) {
  867. IntPtr hscp;
  868. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.ComputeHashAndAddToDispatcher|DEP> %d#, SqlCommand: %d#", ObjectID, command.ObjectID);
  869. try {
  870. // Create a string representing the concatenation of the connection string, command text and .ToString on all parameter values.
  871. // This string will then be mapped to unique notification ID (new GUID). We add the guid and the hash to the app domain
  872. // dispatcher to be able to map back to the dependency that needs to be fired for a notification of this
  873. // command.
  874. // VSTS 59821: add Connection string to prevent redundant notifications when same command is running against different databases or SQL servers
  875. //
  876. string commandHash = ComputeCommandHash(command.Connection.ConnectionString, command); // calculate the string representation of command
  877. string idString = SqlDependencyPerAppDomainDispatcher.SingletonInstance.AddCommandEntry(commandHash, this); // Add to map.
  878. Bid.NotificationsTrace("<sc.SqlDependency.ComputeHashAndAddToDispatcher|DEP> computed id string: '%ls'.\n", idString);
  879. return idString;
  880. }
  881. finally {
  882. Bid.ScopeLeave(ref hscp);
  883. }
  884. }
  885. internal void Invalidate(SqlNotificationType type, SqlNotificationInfo info, SqlNotificationSource source) {
  886. IntPtr hscp;
  887. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.Invalidate|DEP> %d#", ObjectID);
  888. try {
  889. List<EventContextPair> eventList = null;
  890. lock (_eventHandlerLock) {
  891. if (_dependencyFired &&
  892. SqlNotificationInfo.AlreadyChanged != info &&
  893. SqlNotificationSource.Client != source) {
  894. if (ExpirationTime < DateTime.UtcNow) {
  895. // There is a small window in which SqlDependencyPerAppDomainDispatcher.TimeoutTimerCallback
  896. // raises Timeout event but before removing this event from the list. If notification is received from
  897. // server in this case, we will hit this code path.
  898. // It is safe to ignore this race condition because no event is sent to user and no leak happens.
  899. Bid.NotificationsTrace("<sc.SqlDependency.Invalidate|DEP> ignore notification received after timeout!");
  900. }
  901. else {
  902. Debug.Assert(false, "Received notification twice - we should never enter this state!");
  903. Bid.NotificationsTrace("<sc.SqlDependency.Invalidate|DEP|ERR> ERROR - notification received twice - we should never enter this state!");
  904. }
  905. }
  906. else {
  907. // It is the invalidators responsibility to remove this dependency from the app domain static hash.
  908. _dependencyFired = true;
  909. eventList = _eventList;
  910. _eventList = new List<EventContextPair>(); // Since we are firing the events, null so we do not fire again.
  911. }
  912. }
  913. if (eventList != null) {
  914. Bid.NotificationsTrace("<sc.SqlDependency.Invalidate|DEP> Firing events.\n");
  915. foreach(EventContextPair pair in eventList) {
  916. pair.Invoke(new SqlNotificationEventArgs(type, info, source));
  917. }
  918. }
  919. }
  920. finally {
  921. Bid.ScopeLeave(ref hscp);
  922. }
  923. }
  924. // This method is used by SqlCommand.
  925. internal void StartTimer(SqlNotificationRequest notificationRequest) {
  926. IntPtr hscp;
  927. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.StartTimer|DEP> %d#", ObjectID);
  928. try {
  929. if(_expirationTime == DateTime.MaxValue) {
  930. Bid.NotificationsTrace("<sc.SqlDependency.StartTimer|DEP> We've timed out, executing logic.\n");
  931. int seconds = SQL.SqlDependencyServerTimeout;
  932. if (0 != _timeout) {
  933. seconds = _timeout;
  934. }
  935. if (notificationRequest != null && notificationRequest.Timeout < seconds && notificationRequest.Timeout != 0) {
  936. seconds = notificationRequest.Timeout;
  937. }
  938. // VSDD 563926: we use UTC to check if SqlDependency is expired, need to use it here as well.
  939. _expirationTime = DateTime.UtcNow.AddSeconds(seconds);
  940. SqlDependencyPerAppDomainDispatcher.SingletonInstance.StartTimer(this);
  941. }
  942. }
  943. finally {
  944. Bid.ScopeLeave(ref hscp);
  945. }
  946. }
  947. // ---------------
  948. // Private Methods
  949. // ---------------
  950. private void AddCommandInternal(SqlCommand cmd) {
  951. if (cmd != null) { // Don't bother with BID if command null.
  952. IntPtr hscp;
  953. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.AddCommandInternal|DEP> %d#, SqlCommand: %d#", ObjectID, cmd.ObjectID);
  954. try {
  955. SqlConnection connection = cmd.Connection;
  956. if (cmd.Notification != null) {
  957. // Fail if cmd has notification that is not already associated with this dependency.
  958. if (cmd._sqlDep == null || cmd._sqlDep != this) {
  959. Bid.NotificationsTrace("<sc.SqlDependency.AddCommandInternal|DEP|ERR> ERROR - throwing command has existing SqlNotificationRequest exception.\n");
  960. throw SQL.SqlCommandHasExistingSqlNotificationRequest();
  961. }
  962. }
  963. else {
  964. bool needToInvalidate = false;
  965. lock (_eventHandlerLock) {
  966. if (!_dependencyFired) {
  967. cmd.Notification = new SqlNotificationRequest();
  968. cmd.Notification.Timeout = _timeout;
  969. // Add the command - A dependancy should always map to a set of commands which haven't fired.
  970. if (null != _options) { // Assign options if user provided.
  971. cmd.Notification.Options = _options;
  972. }
  973. cmd._sqlDep = this;
  974. }
  975. else {
  976. // We should never be able to enter this state, since if we've fired our event list is cleared
  977. // and the event method will immediately fire if a new event is added. So, we should never have
  978. // an event to fire in the event list once we've fired.
  979. Debug.Assert(0 == _eventList.Count, "How can we have an event at this point?");
  980. if (0 == _eventList.Count) { // Keep logic just in case.
  981. Bid.NotificationsTrace("<sc.SqlDependency.AddCommandInternal|DEP|ERR> ERROR - firing events, though it is unexpected we have events at this point.\n");
  982. needToInvalidate = true; // Delay invalidation until outside of lock.
  983. }
  984. }
  985. }
  986. if (needToInvalidate) {
  987. Invalidate(SqlNotificationType.Subscribe, SqlNotificationInfo.AlreadyChanged, SqlNotificationSource.Client);
  988. }
  989. }
  990. }
  991. finally {
  992. Bid.ScopeLeave(ref hscp);
  993. }
  994. }
  995. }
  996. private string ComputeCommandHash(string connectionString, SqlCommand command) {
  997. IntPtr hscp;
  998. Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependency.ComputeCommandHash|DEP> %d#, SqlCommand: %d#", ObjectID, command.ObjectID);
  999. try {
  1000. // Create a string representing the concatenation of the connection string, the command text and .ToString on all its parameter values.
  1001. // This string will then be mapped to the notification ID.
  1002. // All types should properly support a .ToString for the values except
  1003. // byte[], char[], and XmlReader.
  1004. // NOTE - I hope this doesn't come back to bite us. :(
  1005. StringBuilder builder = new StringBuilder();
  1006. // add the Connection string and the Command text
  1007. builder.AppendFormat("{0};{1}", connectionString, command.CommandText);
  1008. // append params
  1009. for (int i=0; i<command.Parameters.Count; i++) {
  1010. object value = command.Parameters[i].Value;
  1011. if (value == null || value == DBNull.Value) {
  1012. builder.Append("; NULL");
  1013. }
  1014. else {
  1015. Type type = value.GetType();
  1016. if (type == typeof(Byte[])) {
  1017. builder.Append(";");
  1018. byte[] temp = (byte[]) value;
  1019. for (int j=0; j<temp.Length; j++) {
  1020. builder.Append(temp[j].ToString("x2", CultureInfo.InvariantCulture));
  1021. }
  1022. }
  1023. else if (type == typeof(Char[])) {
  1024. builder.Append((char[]) value);
  1025. }
  1026. else if (type == typeof(XmlReader)) {
  1027. builder.Append(";");
  1028. // Cannot .ToString XmlReader - just allocate GUID.
  1029. // This means if XmlReader is used, we will not reuse IDs.
  1030. builder.Append(Guid.NewGuid().ToString());
  1031. }
  1032. else {
  1033. builder.Append(";");
  1034. builder.Append(value.ToString());
  1035. }
  1036. }
  1037. }
  1038. string result = builder.ToString();
  1039. Bid.NotificationsTrace("<sc.SqlDependency.ComputeCommandHash|DEP> ComputeCommandHash result: '%ls'.\n", result);
  1040. return result;
  1041. }
  1042. finally {
  1043. Bid.ScopeLeave(ref hscp);
  1044. }
  1045. }
  1046. // Basic copy of function in SqlConnection.cs for ChangeDatabase and similar functionality. Since this will
  1047. // only be used for default service and database provided by server, we do not need to worry about an already
  1048. // quoted value.
  1049. static internal string FixupServiceOrDatabaseName(string name) {
  1050. if (!ADP.IsEmpty(name)) {
  1051. return "\"" + name.Replace("\"", "\"\"") + "\"";
  1052. }
  1053. else {
  1054. return name;
  1055. }
  1056. }
  1057. }
  1058. }