//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Persistence { using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Runtime; using System.Runtime.Diagnostics; using System.Runtime.Serialization; using System.ServiceModel.Diagnostics; using System.Text; using System.Xml; [Obsolete("The WF3 types are deprecated. Instead, please use the new WF4 types from System.Activities.*")] public class SqlPersistenceProviderFactory : PersistenceProviderFactory { static readonly TimeSpan maxSecondsTimeSpan = TimeSpan.FromSeconds(int.MaxValue); const string connectionStringNameParameter = "connectionStringName"; const string lockTimeoutParameter = "lockTimeout"; const string serializeAsTextParameter = "serializeAsText"; List activeCommands; string canonicalConnectionString; string connectionString; CreateHandler createHandler; DeleteHandler deleteHandler; Guid hostId; LoadHandler loadHandler; TimeSpan lockTimeout; bool serializeAsText; UnlockHandler unlockHandler; UpdateHandler updateHandler; public SqlPersistenceProviderFactory(string connectionString) : this(connectionString, false, TimeSpan.Zero) { } public SqlPersistenceProviderFactory(string connectionString, bool serializeAsText) : this(connectionString, serializeAsText, TimeSpan.Zero) { } public SqlPersistenceProviderFactory(string connectionString, bool serializeAsText, TimeSpan lockTimeout) { this.ConnectionString = connectionString; this.LockTimeout = lockTimeout; this.SerializeAsText = serializeAsText; this.loadHandler = new LoadHandler(this); this.createHandler = new CreateHandler(this); this.updateHandler = new UpdateHandler(this); this.unlockHandler = new UnlockHandler(this); this.deleteHandler = new DeleteHandler(this); } public SqlPersistenceProviderFactory(NameValueCollection parameters) { if (parameters == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("parameters"); } this.connectionString = null; this.LockTimeout = TimeSpan.Zero; this.SerializeAsText = false; foreach (string key in parameters.Keys) { switch (key) { case connectionStringNameParameter: ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[parameters[key]]; if (settings == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument( SR2.GetString(SR2.ConnectionStringNameIncorrect, parameters[key])); } this.connectionString = settings.ConnectionString; break; case serializeAsTextParameter: this.SerializeAsText = bool.Parse(parameters[key]); break; case lockTimeoutParameter: this.LockTimeout = TimeSpan.Parse(parameters[key], CultureInfo.InvariantCulture); break; default: throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument( key, SR2.GetString(SR2.UnknownSqlPersistenceConfigurationParameter, key, connectionStringNameParameter, serializeAsTextParameter, lockTimeoutParameter)); } } if (this.connectionString == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument( SR2.GetString(SR2.ConnectionStringNameParameterRequired, connectionStringNameParameter)); } this.loadHandler = new LoadHandler(this); this.createHandler = new CreateHandler(this); this.updateHandler = new UpdateHandler(this); this.unlockHandler = new UnlockHandler(this); this.deleteHandler = new DeleteHandler(this); } public string ConnectionString { get { return this.connectionString; } set { if (string.IsNullOrEmpty(value)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value"); } this.connectionString = value; } } public TimeSpan LockTimeout { get { return this.lockTimeout; } set { // Allowed values are TimeSpan.Zero (no locking), TimeSpan.MaxValue (infinite locking), // and any values between 1 and int.MaxValue seconds if (value < TimeSpan.Zero || (value > TimeSpan.FromSeconds(int.MaxValue) && value != TimeSpan.MaxValue)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new ArgumentOutOfRangeException( "value", SR2.GetString(SR2.LockTimeoutOutOfRange))); } this.lockTimeout = value; } } public bool SerializeAsText { get { return this.serializeAsText; } set { this.serializeAsText = value; } } protected override TimeSpan DefaultCloseTimeout { get { return PersistenceProvider.DefaultOpenClosePersistenceTimout; } } protected override TimeSpan DefaultOpenTimeout { get { return PersistenceProvider.DefaultOpenClosePersistenceTimout; } } bool IsLockingTurnedOn { get { return this.lockTimeout != TimeSpan.Zero; } } int LockTimeoutAsInt { get { // Consider storing lockTimeout as int32 TotalSeconds instead if (this.lockTimeout == TimeSpan.Zero) { return -1; } else if (this.lockTimeout == TimeSpan.MaxValue) { return 0; } else { Fx.Assert(this.lockTimeout <= TimeSpan.FromSeconds(int.MaxValue), "The lockTimeout should have been checked in the constructor."); return Convert.ToInt32(this.lockTimeout.TotalSeconds); } } } public override PersistenceProvider CreateProvider(Guid id) { base.ThrowIfDisposedOrNotOpen(); if (Guid.Empty == id) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("id", SR2.GetString(SR2.SqlPersistenceProviderRequiresNonEmptyGuid)); } return new SqlPersistenceProvider(id, this); } protected override void OnAbort() { if (this.activeCommands != null) { lock (this.activeCommands) { foreach (SqlCommand command in this.activeCommands) { command.Cancel(); } } } } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { ValidateCommandTimeout(timeout); return new CloseAsyncResult(this, timeout, callback, state); } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { ValidateCommandTimeout(timeout); return new OpenAsyncResult(this, timeout, callback, state); } protected override void OnClose(TimeSpan timeout) { } protected override void OnEndClose(IAsyncResult result) { if (result == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("result"); } CloseAsyncResult.End(result); } protected override void OnEndOpen(IAsyncResult result) { if (result == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("result"); } OpenAsyncResult.End(result); } protected override void OnOpen(TimeSpan timeout) { ValidateCommandTimeout(timeout); try { PerformOpen(timeout); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new PersistenceException( SR2.GetString(SR2.ErrorOpeningSqlPersistenceProvider), e)); } } static int ConvertTimeSpanToSqlTimeout(TimeSpan timeout) { if (timeout == TimeSpan.MaxValue) { return 0; } else { Fx.Assert(timeout <= TimeSpan.FromSeconds(int.MaxValue), "Timeout should have been validated before entering this method."); return Convert.ToInt32(timeout.TotalSeconds); } } IAsyncResult BeginCreate(Guid id, object instance, TimeSpan timeout, bool unlockInstance, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); if (instance == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("instance"); } ValidateCommandTimeout(timeout); return new OperationAsyncResult(this.createHandler, this, id, timeout, callback, state, instance, unlockInstance); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801")] IAsyncResult BeginDelete(Guid id, object instance, TimeSpan timeout, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); ValidateCommandTimeout(timeout); return new OperationAsyncResult(this.deleteHandler, this, id, timeout, callback, state); } IAsyncResult BeginLoad(Guid id, TimeSpan timeout, bool lockInstance, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); ValidateCommandTimeout(timeout); return new OperationAsyncResult(this.loadHandler, this, id, timeout, callback, state, lockInstance); } IAsyncResult BeginUnlock(Guid id, TimeSpan timeout, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); ValidateCommandTimeout(timeout); return new OperationAsyncResult(this.unlockHandler, this, id, timeout, callback, state); } IAsyncResult BeginUpdate(Guid id, object instance, TimeSpan timeout, bool unlockInstance, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); if (instance == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("instance"); } ValidateCommandTimeout(timeout); return new OperationAsyncResult(this.updateHandler, this, id, timeout, callback, state, instance, unlockInstance); } void CleanupCommand(SqlCommand command) { lock (this.activeCommands) { this.activeCommands.Remove(command); } command.Dispose(); } object Create(Guid id, object instance, TimeSpan timeout, bool unlockInstance) { base.ThrowIfDisposedOrNotOpen(); if (instance == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("instance"); } ValidateCommandTimeout(timeout); PerformOperation(this.createHandler, id, timeout, instance, unlockInstance); return null; } SqlCommand CreateCommand(SqlConnection connection, TimeSpan timeout) { SqlCommand command = connection.CreateCommand(); command.CommandTimeout = ConvertTimeSpanToSqlTimeout(timeout); lock (this.activeCommands) { this.activeCommands.Add(command); } return command; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801")] void Delete(Guid id, object instance, TimeSpan timeout) { base.ThrowIfDisposedOrNotOpen(); ValidateCommandTimeout(timeout); PerformOperation(this.deleteHandler, id, timeout); } object DeserializeInstance(object serializedInstance, bool isText) { object instance; NetDataContractSerializer serializer = new NetDataContractSerializer(); if (isText) { StringReader stringReader = new StringReader((string)serializedInstance); XmlReader xmlReader = XmlReader.Create(stringReader); instance = serializer.ReadObject(xmlReader); xmlReader.Close(); stringReader.Close(); } else { XmlDictionaryReader dictionaryReader = XmlDictionaryReader.CreateBinaryReader((byte[])serializedInstance, XmlDictionaryReaderQuotas.Max); instance = serializer.ReadObject(dictionaryReader); dictionaryReader.Close(); } return instance; } object EndCreate(IAsyncResult result) { if (result == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("result"); } OperationAsyncResult.End(result); return null; } void EndDelete(IAsyncResult result) { if (result == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("result"); } OperationAsyncResult.End(result); } object EndLoad(IAsyncResult result) { if (result == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("result"); } return OperationAsyncResult.End(result); } void EndUnlock(IAsyncResult result) { OperationAsyncResult.End(result); } object EndUpdate(IAsyncResult result) { if (result == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("result"); } OperationAsyncResult.End(result); return null; } byte[] GetBinarySerializedForm(object instance) { NetDataContractSerializer serializer = new NetDataContractSerializer(); MemoryStream memStr = new MemoryStream(); XmlDictionaryWriter dictionaryWriter = XmlDictionaryWriter.CreateBinaryWriter(memStr); serializer.WriteObject(dictionaryWriter, instance); dictionaryWriter.Flush(); byte[] bytes = memStr.ToArray(); dictionaryWriter.Close(); memStr.Close(); return bytes; } string GetConnectionString(TimeSpan timeout) { if (this.canonicalConnectionString != null) { StringBuilder sb = new StringBuilder(this.canonicalConnectionString); sb.Append(ConvertTimeSpanToSqlTimeout(timeout)); return sb.ToString(); } return this.connectionString; } string GetXmlSerializedForm(object instance) { NetDataContractSerializer serializer = new NetDataContractSerializer(); MemoryStream memStr = new MemoryStream(); serializer.WriteObject(memStr, instance); string xml = UnicodeEncoding.UTF8.GetString(memStr.ToArray()); memStr.Close(); return xml; } object Load(Guid id, TimeSpan timeout, bool lockInstance) { base.ThrowIfDisposedOrNotOpen(); ValidateCommandTimeout(timeout); return PerformOperation(this.loadHandler, id, timeout, lockInstance); } SqlConnection OpenConnection(TimeSpan timeout) { // Do I need to do timeout decrementing? SqlConnection connection = new SqlConnection(GetConnectionString(timeout)); connection.Open(); return connection; } void PerformOpen(TimeSpan timeout) { string lowerCaseConnectionString = this.connectionString.ToUpper(CultureInfo.InvariantCulture); if (!lowerCaseConnectionString.Contains("CONNECTION TIMEOUT") && !lowerCaseConnectionString.Contains("CONNECTIONTIMEOUT")) { this.canonicalConnectionString = this.connectionString.Trim(); if (this.canonicalConnectionString.EndsWith(";", StringComparison.Ordinal)) { this.canonicalConnectionString += "Connection Timeout="; } else { this.canonicalConnectionString += ";Connection Timeout="; } } // Check that the connection string is valid using (SqlConnection connection = new SqlConnection(GetConnectionString(timeout))) { if (DiagnosticUtility.ShouldTraceInformation) { Dictionary openParameters = new Dictionary(2) { { "IsLocking", this.IsLockingTurnedOn ? "True" : "False" }, { "LockTimeout", this.lockTimeout.ToString() } }; TraceRecord record = new DictionaryTraceRecord(openParameters); TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.SqlPersistenceProviderOpenParameters, SR.GetString(SR.TraceCodeSqlPersistenceProviderOpenParameters), record, this, null); } connection.Open(); } this.activeCommands = new List(); this.hostId = Guid.NewGuid(); } object PerformOperation(OperationHandler handler, Guid id, TimeSpan timeout, params object[] additionalParameters) { int resultCode; object returnValue = null; if (DiagnosticUtility.ShouldTraceInformation) { string traceText = SR2.GetString(SR2.SqlPrsistenceProviderOperationAndInstanceId, handler.OperationName, id.ToString()); TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.SqlPersistenceProviderSQLCallStart, SR.GetString(SR.TraceCodeSqlPersistenceProviderSQLCallStart), new StringTraceRecord("OperationDetail", traceText), this, null); } try { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); using (SqlConnection connection = OpenConnection(timeoutHelper.RemainingTime())) { SqlCommand command = CreateCommand(connection, timeoutHelper.RemainingTime()); try { handler.SetupCommand(command, id, additionalParameters); if (handler.ExecuteReader) { using (SqlDataReader reader = command.ExecuteReader()) { returnValue = handler.ProcessReader(reader); } } else { command.ExecuteNonQuery(); } resultCode = (int)command.Parameters["@result"].Value; } finally { CleanupCommand(command); } } } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new PersistenceException( SR2.GetString(SR2.PersistenceOperationError, handler.OperationName), e)); } Exception toThrow = handler.ProcessResult(resultCode, id, returnValue); if (DiagnosticUtility.ShouldTraceInformation) { string traceText = SR2.GetString(SR2.SqlPrsistenceProviderOperationAndInstanceId, handler.OperationName, id.ToString()); TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.SqlPersistenceProviderSQLCallEnd, SR.GetString(SR.TraceCodeSqlPersistenceProviderSQLCallEnd), new StringTraceRecord("OperationDetail", traceText), this, null); } if (toThrow != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(toThrow); } return returnValue; } void Unlock(Guid id, TimeSpan timeout) { base.ThrowIfDisposedOrNotOpen(); if (this.unlockHandler.ShortcutExecution) { return; } ValidateCommandTimeout(timeout); PerformOperation(this.unlockHandler, id, timeout); } object Update(Guid id, object instance, TimeSpan timeout, bool unlockInstance) { base.ThrowIfDisposedOrNotOpen(); if (instance == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("instance"); } ValidateCommandTimeout(timeout); PerformOperation(this.updateHandler, id, timeout, instance, unlockInstance); return null; } void ValidateCommandTimeout(TimeSpan timeout) { if (timeout <= TimeSpan.Zero || (timeout > SqlPersistenceProviderFactory.maxSecondsTimeSpan && timeout != TimeSpan.MaxValue)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument( "timeout", SR2.GetString(SR2.CommandTimeoutOutOfRange)); } } class CloseAsyncResult : AsyncResult { public CloseAsyncResult(SqlPersistenceProviderFactory provider, TimeSpan timeout, AsyncCallback callback, object state) : base(callback, state) { // There is no point in even pretending SqlConnection.Close needs async provider.OnClose(timeout); Complete(true); } public static void End(IAsyncResult result) { AsyncResult.End(result); } } class CreateHandler : OperationHandler { public CreateHandler(SqlPersistenceProviderFactory provider) : base(provider) { } public override string OperationName { get { return "Create"; } } public override Exception ProcessResult(int resultCode, Guid id, object loadedInstance) { switch (resultCode) { case 0: // Success return null; case 1: // Already exists return new PersistenceException(SR2.GetString(SR2.InstanceAlreadyExists, id)); case 2: // Some other error return new PersistenceException(SR2.GetString(SR2.InsertFailed, id)); default: return new PersistenceException(SR2.GetString(SR2.UnknownStoredProcResult)); } } public override void SetupCommand(SqlCommand command, Guid id, params object[] additionalParameters) { Fx.Assert(additionalParameters != null && additionalParameters.Length == 2, "Should have had 2 additional parameters."); Fx.Assert(additionalParameters[1].GetType() == typeof(bool), "Parameter at index 1 should have been a boolean."); object instance = additionalParameters[0]; command.CommandType = CommandType.StoredProcedure; command.CommandText = "InsertInstance"; SqlParameter idParameter = new SqlParameter("@id", SqlDbType.UniqueIdentifier); idParameter.Value = id; command.Parameters.Add(idParameter); SqlParameter instanceParameter = new SqlParameter("@instance", SqlDbType.Image); SqlParameter instanceXmlParameter = new SqlParameter("@instanceXml", SqlDbType.Xml); if (this.provider.serializeAsText) { instanceXmlParameter.Value = this.provider.GetXmlSerializedForm(instance); instanceParameter.Value = null; } else { instanceParameter.Value = this.provider.GetBinarySerializedForm(instance); instanceXmlParameter.Value = null; } command.Parameters.Add(instanceParameter); command.Parameters.Add(instanceXmlParameter); SqlParameter unlockInstanceParameter = new SqlParameter("@unlockInstance", SqlDbType.Bit); unlockInstanceParameter.Value = (bool)additionalParameters[1]; command.Parameters.Add(unlockInstanceParameter); SqlParameter lockOwnerParameter = new SqlParameter("@hostId", SqlDbType.UniqueIdentifier); lockOwnerParameter.Value = this.provider.hostId; command.Parameters.Add(lockOwnerParameter); SqlParameter lockTimeoutParameter = new SqlParameter("@lockTimeout", SqlDbType.Int); lockTimeoutParameter.Value = this.provider.LockTimeoutAsInt; command.Parameters.Add(lockTimeoutParameter); SqlParameter resultParameter = new SqlParameter("@result", SqlDbType.Int); resultParameter.Direction = ParameterDirection.Output; command.Parameters.Add(resultParameter); } } class DeleteHandler : OperationHandler { public DeleteHandler(SqlPersistenceProviderFactory provider) : base(provider) { } public override string OperationName { get { return "Delete"; } } public override Exception ProcessResult(int resultCode, Guid id, object loadedInstance) { switch (resultCode) { case 0: // Success return null; case 1: // Instance not found return new InstanceNotFoundException(id); case 2: // Could not acquire lock return new InstanceLockException(id, SR2.GetString(SR2.DidNotOwnLock, id, OperationName)); default: return new PersistenceException( SR2.GetString(SR2.UnknownStoredProcResult)); } } public override void SetupCommand(SqlCommand command, Guid id, params object[] additionalParameters) { Fx.Assert(additionalParameters == null || additionalParameters.Length == 0, "Should not have gotten any additional parameters."); command.CommandType = CommandType.StoredProcedure; command.CommandText = "DeleteInstance"; SqlParameter idParameter = new SqlParameter("@id", SqlDbType.UniqueIdentifier); idParameter.Value = id; command.Parameters.Add(idParameter); SqlParameter hostIdParameter = new SqlParameter("@hostId", SqlDbType.UniqueIdentifier); hostIdParameter.Value = this.provider.hostId; command.Parameters.Add(hostIdParameter); SqlParameter lockTimeoutParameter = new SqlParameter("@lockTimeout", SqlDbType.Int); lockTimeoutParameter.Value = this.provider.LockTimeoutAsInt; command.Parameters.Add(lockTimeoutParameter); SqlParameter resultParameter = new SqlParameter("@result", SqlDbType.Int); resultParameter.Direction = ParameterDirection.Output; command.Parameters.Add(resultParameter); } } class LoadHandler : OperationHandler { public LoadHandler(SqlPersistenceProviderFactory provider) : base(provider) { } public override bool ExecuteReader { get { return true; } } public override string OperationName { get { return "Load"; } } public override object ProcessReader(SqlDataReader reader) { if (reader.Read()) { bool isXml = ((int)reader["isXml"] == 0 ? false : true); object serializedInstance; if (isXml) { serializedInstance = reader["instanceXml"]; } else { serializedInstance = reader["instance"]; } if (serializedInstance != null) { return this.provider.DeserializeInstance(serializedInstance, isXml); } } return null; } public override Exception ProcessResult(int resultCode, Guid id, object loadedInstance) { Exception toReturn = null; switch (resultCode) { case 0: // Success break; case 1: // Instance not found toReturn = new InstanceNotFoundException(id); break; case 2: // Could not acquire lock toReturn = new InstanceLockException(id); break; default: toReturn = new PersistenceException(SR2.GetString(SR2.UnknownStoredProcResult)); break; } if (toReturn == null) { if (loadedInstance == null) { toReturn = new PersistenceException(SR2.GetString(SR2.SerializationFormatMismatch)); } } return toReturn; } public override void SetupCommand(SqlCommand command, Guid id, params object[] additionalParameters) { Fx.Assert(additionalParameters != null && additionalParameters.Length == 1, "Should have had 1 additional parameter."); Fx.Assert(additionalParameters[0].GetType() == typeof(bool), "Parameter 0 should have been a boolean."); command.CommandType = CommandType.StoredProcedure; command.CommandText = "LoadInstance"; SqlParameter idParameter = new SqlParameter("@id", SqlDbType.UniqueIdentifier); idParameter.Value = id; command.Parameters.Add(idParameter); SqlParameter lockInstanceParameter = new SqlParameter("@lockInstance", SqlDbType.Bit); lockInstanceParameter.Value = (bool)additionalParameters[0]; command.Parameters.Add(lockInstanceParameter); SqlParameter hostIdParameter = new SqlParameter("@hostId", SqlDbType.UniqueIdentifier); hostIdParameter.Value = this.provider.hostId; command.Parameters.Add(hostIdParameter); SqlParameter lockTimeoutParameter = new SqlParameter("@lockTimeout", SqlDbType.Int); lockTimeoutParameter.Value = this.provider.LockTimeoutAsInt; command.Parameters.Add(lockTimeoutParameter); SqlParameter resultParameter = new SqlParameter("@result", SqlDbType.Int); resultParameter.Direction = ParameterDirection.Output; command.Parameters.Add(resultParameter); } } class OpenAsyncResult : AsyncResult { SqlPersistenceProviderFactory provider; TimeSpan timeout; public OpenAsyncResult(SqlPersistenceProviderFactory provider, TimeSpan timeout, AsyncCallback callback, object state) : base(callback, state) { Fx.Assert(provider != null, "Provider should never be null."); this.provider = provider; this.timeout = timeout; ActionItem.Schedule(ScheduledCallback, null); } public static void End(IAsyncResult result) { AsyncResult.End(result); } void ScheduledCallback(object state) { Exception completionException = null; try { this.provider.PerformOpen(this.timeout); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = new PersistenceException( SR2.GetString(SR2.ErrorOpeningSqlPersistenceProvider), e); } Complete(false, completionException); } } class OperationAsyncResult : AsyncResult { protected SqlPersistenceProviderFactory provider; static AsyncCallback commandCallback = Fx.ThunkCallback(new AsyncCallback(CommandExecutionComplete)); SqlCommand command; OperationHandler handler; Guid id; object instance; // We are using virtual methods from the constructor on purpose [SuppressMessage("Microsoft.Usage", "CA2214")] public OperationAsyncResult(OperationHandler handler, SqlPersistenceProviderFactory provider, Guid id, TimeSpan timeout, AsyncCallback callback, object state, params object[] additionalParameters) : base(callback, state) { Fx.Assert(provider != null, "Provider should never be null."); this.handler = handler; this.provider = provider; this.id = id; if (this.handler.ShortcutExecution) { Complete(true); return; } TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); SqlConnection connection = this.provider.OpenConnection(timeoutHelper.RemainingTime()); bool completeSelf = false; Exception delayedException = null; try { this.command = this.provider.CreateCommand(connection, timeoutHelper.RemainingTime()); this.handler.SetupCommand(this.command, this.id, additionalParameters); IAsyncResult result = null; if (this.handler.ExecuteReader) { result = this.command.BeginExecuteReader(commandCallback, this); } else { result = this.command.BeginExecuteNonQuery(commandCallback, this); } if (result.CompletedSynchronously) { delayedException = CompleteOperation(result); completeSelf = true; } } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } try { connection.Close(); this.provider.CleanupCommand(this.command); } catch (Exception e1) { if (Fx.IsFatal(e1)) { throw; } // do not rethrow non-fatal exceptions thrown from cleanup code } finally { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new PersistenceException( SR2.GetString(SR2.PersistenceOperationError, this.handler.OperationName), e)); } } if (completeSelf) { connection.Close(); this.provider.CleanupCommand(this.command); if (delayedException != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(delayedException); } Complete(true); } } public object Instance { get { return this.instance; } } public static object End(IAsyncResult result) { OperationAsyncResult operationResult = AsyncResult.End(result); return operationResult.Instance; } static void CommandExecutionComplete(IAsyncResult result) { if (result.CompletedSynchronously) { return; } OperationAsyncResult operationResult = (OperationAsyncResult)result.AsyncState; Exception completionException = null; try { completionException = operationResult.CompleteOperation(result); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = new PersistenceException( SR2.GetString(SR2.PersistenceOperationError, operationResult.handler.OperationName), e); } finally { try { operationResult.command.Connection.Close(); operationResult.provider.CleanupCommand(operationResult.command); } catch (Exception e1) { if (Fx.IsFatal(e1)) { throw; } // do not rethrow non-fatal exceptions thrown from cleanup code } } operationResult.Complete(false, completionException); } Exception CompleteOperation(IAsyncResult result) { Exception delayedException = null; if (this.handler.ExecuteReader) { using (SqlDataReader reader = this.command.EndExecuteReader(result)) { this.instance = this.handler.ProcessReader(reader); } } else { this.command.EndExecuteNonQuery(result); } int resultCode = (int)this.command.Parameters["@result"].Value; delayedException = this.handler.ProcessResult(resultCode, this.id, this.instance); return delayedException; } } abstract class OperationHandler { protected SqlPersistenceProviderFactory provider; public OperationHandler(SqlPersistenceProviderFactory provider) { this.provider = provider; } public virtual bool ExecuteReader { get { return false; } } public abstract string OperationName { get; } public virtual bool ShortcutExecution { get { return false; } } public virtual object ProcessReader(SqlDataReader reader) { return null; } public abstract Exception ProcessResult(int resultCode, Guid id, object loadedInstance); public abstract void SetupCommand(SqlCommand command, Guid id, params object[] additionalParameters); } class SqlPersistenceProvider : LockingPersistenceProvider { SqlPersistenceProviderFactory factory; public SqlPersistenceProvider(Guid id, SqlPersistenceProviderFactory factory) : base(id) { this.factory = factory; } protected override TimeSpan DefaultCloseTimeout { get { return TimeSpan.FromSeconds(15); } } protected override TimeSpan DefaultOpenTimeout { get { return TimeSpan.FromSeconds(15); } } public override IAsyncResult BeginCreate(object instance, TimeSpan timeout, bool unlockInstance, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); return this.factory.BeginCreate(this.Id, instance, timeout, unlockInstance, callback, state); } public override IAsyncResult BeginDelete(object instance, TimeSpan timeout, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); return this.factory.BeginDelete(this.Id, instance, timeout, callback, state); } public override IAsyncResult BeginLoad(TimeSpan timeout, bool lockInstance, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); return this.factory.BeginLoad(this.Id, timeout, lockInstance, callback, state); } public override IAsyncResult BeginUnlock(TimeSpan timeout, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); return this.factory.BeginUnlock(this.Id, timeout, callback, state); } public override IAsyncResult BeginUpdate(object instance, TimeSpan timeout, bool unlockInstance, AsyncCallback callback, object state) { base.ThrowIfDisposedOrNotOpen(); return this.factory.BeginUpdate(this.Id, instance, timeout, unlockInstance, callback, state); } public override object Create(object instance, TimeSpan timeout, bool unlockInstance) { base.ThrowIfDisposedOrNotOpen(); return this.factory.Create(this.Id, instance, timeout, unlockInstance); } public override void Delete(object instance, TimeSpan timeout) { base.ThrowIfDisposedOrNotOpen(); this.factory.Delete(this.Id, instance, timeout); } public override object EndCreate(IAsyncResult result) { return this.factory.EndCreate(result); } public override void EndDelete(IAsyncResult result) { this.factory.EndDelete(result); } public override object EndLoad(IAsyncResult result) { return this.factory.EndLoad(result); } public override void EndUnlock(IAsyncResult result) { this.factory.EndUnlock(result); } public override object EndUpdate(IAsyncResult result) { return this.factory.EndUpdate(result); } public override object Load(TimeSpan timeout, bool lockInstance) { base.ThrowIfDisposedOrNotOpen(); return this.factory.Load(this.Id, timeout, lockInstance); } public override void Unlock(TimeSpan timeout) { base.ThrowIfDisposedOrNotOpen(); this.factory.Unlock(this.Id, timeout); } public override object Update(object instance, TimeSpan timeout, bool unlockInstance) { base.ThrowIfDisposedOrNotOpen(); return this.factory.Update(this.Id, instance, timeout, unlockInstance); } protected override void OnAbort() { } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { return new CompletedAsyncResult(callback, state); } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { return new CompletedAsyncResult(callback, state); } protected override void OnClose(TimeSpan timeout) { } protected override void OnEndClose(IAsyncResult result) { CompletedAsyncResult.End(result); } protected override void OnEndOpen(IAsyncResult result) { CompletedAsyncResult.End(result); } protected override void OnOpen(TimeSpan timeout) { } } class UnlockHandler : OperationHandler { public UnlockHandler(SqlPersistenceProviderFactory provider) : base(provider) { } public override string OperationName { get { return "Unlock"; } } public override bool ShortcutExecution { get { return !this.provider.IsLockingTurnedOn; } } public override Exception ProcessResult(int resultCode, Guid id, object loadedInstance) { switch (resultCode) { case 0: // Success return null; case 1: // Instance not found return new InstanceNotFoundException(id); case 2: // Could not acquire lock return new InstanceLockException(id, SR2.GetString(SR2.DidNotOwnLock, id, OperationName)); default: return new PersistenceException(SR2.GetString(SR2.UnknownStoredProcResult)); } } public override void SetupCommand(SqlCommand command, Guid id, params object[] additionalParameters) { Fx.Assert(additionalParameters == null || additionalParameters.Length == 0, "There should not be any additional parameters."); command.CommandType = CommandType.StoredProcedure; command.CommandText = "UnlockInstance"; SqlParameter idParameter = new SqlParameter("@id", SqlDbType.UniqueIdentifier); idParameter.Value = id; command.Parameters.Add(idParameter); SqlParameter hostIdParameter = new SqlParameter("@hostId", SqlDbType.UniqueIdentifier); hostIdParameter.Value = this.provider.hostId; command.Parameters.Add(hostIdParameter); SqlParameter lockTimeoutParameter = new SqlParameter("@lockTimeout", SqlDbType.Int); lockTimeoutParameter.Value = this.provider.LockTimeoutAsInt; command.Parameters.Add(lockTimeoutParameter); SqlParameter resultParameter = new SqlParameter("@result", SqlDbType.Int); resultParameter.Direction = ParameterDirection.Output; command.Parameters.Add(resultParameter); } } class UpdateHandler : OperationHandler { public UpdateHandler(SqlPersistenceProviderFactory provider) : base(provider) { } public override string OperationName { get { return "Update"; } } public override Exception ProcessResult(int resultCode, Guid id, object loadedInstance) { switch (resultCode) { case 0: // Success return null; case 1: // Instance did not exist return new InstanceNotFoundException(id, SR2.GetString(SR2.InstanceNotFoundForUpdate, id)); case 2: // Did not have lock return new InstanceLockException(id, SR2.GetString(SR2.DidNotOwnLock, id, OperationName)); default: return new PersistenceException(SR2.GetString(SR2.UnknownStoredProcResult)); } } public override void SetupCommand(SqlCommand command, Guid id, params object[] additionalParameters) { Fx.Assert(additionalParameters != null && additionalParameters.Length == 2, "Should have had 2 additional parameters."); Fx.Assert(additionalParameters[1].GetType() == typeof(bool), "Parameter at index 1 should have been a boolean."); object instance = additionalParameters[0]; command.CommandType = CommandType.StoredProcedure; command.CommandText = "UpdateInstance"; SqlParameter idParameter = new SqlParameter("@id", SqlDbType.UniqueIdentifier); idParameter.Value = id; command.Parameters.Add(idParameter); SqlParameter instanceParameter = new SqlParameter("@instance", SqlDbType.Image); SqlParameter instanceXmlParameter = new SqlParameter("@instanceXml", SqlDbType.Xml); if (this.provider.serializeAsText) { instanceXmlParameter.Value = this.provider.GetXmlSerializedForm(instance); instanceParameter.Value = null; } else { instanceParameter.Value = this.provider.GetBinarySerializedForm(instance); instanceXmlParameter.Value = null; } command.Parameters.Add(instanceParameter); command.Parameters.Add(instanceXmlParameter); SqlParameter unlockInstanceParameter = new SqlParameter("@unlockInstance", SqlDbType.Bit); unlockInstanceParameter.Value = (bool)additionalParameters[1]; command.Parameters.Add(unlockInstanceParameter); SqlParameter lockOwnerParameter = new SqlParameter("@hostId", SqlDbType.UniqueIdentifier); lockOwnerParameter.Value = this.provider.hostId; command.Parameters.Add(lockOwnerParameter); SqlParameter lockTimeoutParameter = new SqlParameter("@lockTimeout", SqlDbType.Int); lockTimeoutParameter.Value = this.provider.LockTimeoutAsInt; command.Parameters.Add(lockTimeoutParameter); SqlParameter resultParameter = new SqlParameter("@result", SqlDbType.Int); resultParameter.Direction = ParameterDirection.Output; command.Parameters.Add(resultParameter); } } } }