using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using System.Collections; using System.Collections.Generic; using System.Text; using System.Xml; using System.Reflection; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.ComponentModel.Design.Serialization; using System.Workflow.Runtime; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Serialization; using System.Workflow.Runtime.Hosting; namespace System.Workflow.Runtime.Tracking { [Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")] public class SqlTrackingWorkflowInstance { #region Private Members private delegate void LoadFromReader(SqlDataReader reader, object parameter); private static int _deadlock = 1205; private static short _retries = 5; private string _connectionString = null; private bool _autoRefresh = false; DateTime _currDT = DateTime.UtcNow, _actMinDT = SqlDateTime.MinValue.Value, _userMinDT = SqlDateTime.MinValue.Value, _instMinDT = SqlDateTime.MinValue.Value, _childMinDT = SqlDateTime.MinValue.Value, _changesMinDT = SqlDateTime.MinValue.Value, _invMinDT = SqlDateTime.MinValue.Value; long _internalId = -1; Guid _id; DateTime _initialized; Guid _invoker = Guid.Empty; WorkflowStatus _status; Type _workflowType = null; bool _changed = false; List _activityEvents = new List(); List _userEvents = new List(); List _workflowEvents = new List(); List _invoked = new List(); Activity _def = null; #endregion Private Members #region Constructors private SqlTrackingWorkflowInstance() { } internal SqlTrackingWorkflowInstance(string connectionString) { if (null == connectionString) throw new ArgumentNullException("connectionString"); _connectionString = connectionString; } #endregion Constructors #region Properties public bool AutoRefresh { get { return _autoRefresh; } set { _autoRefresh = value; } } public Guid WorkflowInstanceId { get { return _id; } set { _id = value; } } public long WorkflowInstanceInternalId { get { return _internalId; } set { _internalId = value; } } public DateTime Initialized { get { return _initialized; } set { _initialized = value; } } public Guid InvokingWorkflowInstanceId { get { return _invoker; } set { _invoker = value; } } public WorkflowStatus Status { get { return _status; } set { _status = value; } } public Type WorkflowType { get { return _workflowType; } set { _workflowType = value; } } public bool WorkflowDefinitionUpdated { get { if (_autoRefresh) Refresh(); LoadDef(); return _changed; } } public IList ActivityEvents { get { if (_autoRefresh) Refresh(); LoadActivityEvents(); return _activityEvents; } } public IList UserEvents { get { if (_autoRefresh) Refresh(); LoadUserEvents(); return _userEvents; } } public IList WorkflowEvents { get { if (_autoRefresh) Refresh(); LoadWorkflowEvents(); return _workflowEvents; } } public Activity WorkflowDefinition { get { if (_autoRefresh) Refresh(); LoadDef(); return _def; } } public IList InvokedWorkflows { get { if (_autoRefresh) Refresh(); LoadInvokedWorkflows(); return _invoked; } } #endregion Properties public void Refresh() { _currDT = DateTime.UtcNow; } private void LoadActivityEvents() { SqlCommand cmd = CreateInternalIdDateTimeCommand("[dbo].[GetActivityEventsWithDetails]", _actMinDT); ExecuteRetried(cmd, LoadActivityEventsFromReader); } private void LoadActivityEventsFromReader(SqlDataReader reader, object parameter) { if (null == reader) throw new ArgumentNullException("reader"); // // There should always be 4 recordsets in this reader! // Dictionary activities = new Dictionary(); // // Build a dictionary of activity records so that we can match // annotation and artifact records from subsequent recordsets DateTime tmpMin = SqlDateTime.MinValue.Value; while (reader.Read()) { string qId = reader.GetString(0); ActivityExecutionStatus status = (ActivityExecutionStatus)reader[1]; DateTime dt = reader.GetDateTime(2); Guid context = reader.GetGuid(3), parentContext = reader.GetGuid(4); int order = reader.GetInt32(5); if (reader.IsDBNull(6) || reader.IsDBNull(7)) throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentCulture, ExecutionStringManager.SqlTrackingTypeNotFound, qId)); Type type = Type.GetType(reader.GetString(6) + ", " + reader.GetString(7), true, false); long eventId = reader.GetInt64(8); DateTime dbDt = reader.GetDateTime(9); activities.Add(eventId, new ActivityTrackingRecord(type, qId, context, parentContext, status, dt, order, null)); if (dbDt > tmpMin) tmpMin = dbDt; } if (!reader.NextResult()) throw new ArgumentException(ExecutionStringManager.InvalidActivityEventReader); // // If we have annotations on the event itself, add them while (reader.Read()) { long eventId = reader.GetInt64(0); string annotation = null; if (!reader.IsDBNull(1)) annotation = reader.GetString(1); ActivityTrackingRecord activity = null; if (activities.TryGetValue(eventId, out activity)) { if (null != activity) activity.Annotations.Add(annotation); } } if (!reader.NextResult()) throw new ArgumentException(ExecutionStringManager.InvalidActivityEventReader); // // Build a dictionary of artifact records so that we can match // annotation records from subsequent recordsets BinaryFormatter formatter = new BinaryFormatter(); Dictionary artifacts = new Dictionary(); while (reader.Read()) { long eventId = reader.GetInt64(0); long artId = reader.GetInt64(1); string name = reader.GetString(2), strData = null; object data = null; // // These may both be null if (!reader.IsDBNull(3)) strData = reader.GetString(3); if (!reader.IsDBNull(4)) data = formatter.Deserialize(new MemoryStream((Byte[])reader[4])); TrackingDataItem item = new TrackingDataItem(); item.FieldName = name; if (null != data) item.Data = data; else item.Data = strData; artifacts.Add(artId, item); // // Find the event to which this artifact belongs and add it to the record ActivityTrackingRecord activity = null; if (activities.TryGetValue(eventId, out activity)) { if (null != activity) activity.Body.Add(item); } } if (!reader.NextResult()) throw new ArgumentException(ExecutionStringManager.InvalidActivityEventReader); // // If we have annotations add them to the appropriate artifact while (reader.Read()) { long artId = reader.GetInt64(0); string annotation = null; if (!reader.IsDBNull(1)) annotation = reader.GetString(1); // // Find the right artifact and give it the annotation TrackingDataItem item = null; if (artifacts.TryGetValue(artId, out item)) { if (null != item) item.Annotations.Add(annotation); } } _activityEvents.AddRange(activities.Values); // // Set the min value to the most recent event that we got with this query // Don't overwrite the previous min if nothing came back for this query if (tmpMin > SqlDateTime.MinValue.Value) _actMinDT = tmpMin; return; } private void LoadUserEvents() { SqlCommand cmd = CreateInternalIdDateTimeCommand("[dbo].[GetUserEventsWithDetails]", _userMinDT); ExecuteRetried(cmd, LoadUserEventsFromReader); } private void LoadUserEventsFromReader(SqlDataReader reader, object parameter) { if (null == reader) throw new ArgumentNullException("reader"); // // There should always be 4 recordsets in this reader! // BinaryFormatter formatter = new BinaryFormatter(); Dictionary userEvents = new Dictionary(); // // Build a dictionary of activity records so that we can match // annotation and artifact records from subsequent recordsets DateTime tmpMin = SqlDateTime.MinValue.Value; while (reader.Read()) { string qId = reader.GetString(0); DateTime dt = reader.GetDateTime(1); Guid context = reader.GetGuid(2), parentContext = reader.GetGuid(3); int order = reader.GetInt32(4); string key = null; if (!reader.IsDBNull(5)) key = reader.GetString(5); // // Get the user data from the serialized column if we can // Try the string column if serialized column is null // If both are null the user data was null originally object userData = null; if (!reader.IsDBNull(7)) userData = formatter.Deserialize(new MemoryStream((Byte[])reader[7])); else if (!reader.IsDBNull(6)) userData = reader.GetString(6); if (reader.IsDBNull(8) || reader.IsDBNull(9)) throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentCulture, ExecutionStringManager.SqlTrackingTypeNotFound, qId)); Type type = Type.GetType(reader.GetString(8) + ", " + reader.GetString(9), true, false); long eventId = reader.GetInt64(10); DateTime dbDt = reader.GetDateTime(11); userEvents.Add(eventId, new UserTrackingRecord(type, qId, context, parentContext, dt, order, key, userData)); if (dbDt > tmpMin) tmpMin = dbDt; } if (!reader.NextResult()) throw new ArgumentException(ExecutionStringManager.InvalidUserEventReader); // // If we have annotations on the event itself, add them while (reader.Read()) { long eventId = reader.GetInt64(0); string annotation = null; if (!reader.IsDBNull(1)) annotation = reader.GetString(1); UserTrackingRecord user = null; if (userEvents.TryGetValue(eventId, out user)) { if (null != user) user.Annotations.Add(annotation); } } if (!reader.NextResult()) throw new ArgumentException(ExecutionStringManager.InvalidUserEventReader); // // Build a dictionary of artifact records so that we can match // annotation records from subsequent recordsets Dictionary artifacts = new Dictionary(); while (reader.Read()) { long eventId = reader.GetInt64(0); long artId = reader.GetInt64(1); string name = reader.GetString(2), strData = null; object data = null; // // These may both be null if (!reader.IsDBNull(3)) strData = reader.GetString(3); if (!reader.IsDBNull(4)) data = formatter.Deserialize(new MemoryStream((Byte[])reader[4])); TrackingDataItem item = new TrackingDataItem(); item.FieldName = name; if (null != data) item.Data = data; else item.Data = strData; artifacts.Add(artId, item); // // Find the event to which this artifact belongs and add it to the record UserTrackingRecord user = null; if (userEvents.TryGetValue(eventId, out user)) { if (null != user) user.Body.Add(item); } } if (!reader.NextResult()) throw new ArgumentException(ExecutionStringManager.InvalidUserEventReader); // // If we have annotations add them to the appropriate artifact while (reader.Read()) { long artId = reader.GetInt64(0); string annotation = null; if (!reader.IsDBNull(1)) annotation = reader.GetString(1); // // Find the right artifact and give it the annotation TrackingDataItem item = null; if (artifacts.TryGetValue(artId, out item)) { if (null != item) item.Annotations.Add(annotation); } } _userEvents.AddRange(userEvents.Values); // // Set the min dt to query for next time to the most recent event we got for this query. // Don't overwrite the previous min if nothing came back for this query if (tmpMin > SqlDateTime.MinValue.Value) _userMinDT = tmpMin; return; } private void LoadWorkflowEvents() { SqlCommand cmd = CreateInternalIdDateTimeCommand("[dbo].[GetWorkflowInstanceEventsWithDetails]", _instMinDT); ExecuteRetried(cmd, LoadWorkflowEventsFromReader); } private void LoadWorkflowEventsFromReader(SqlDataReader reader, object parameter) { if (null == reader) throw new ArgumentNullException("reader"); // // There should always be 2 recordsets in this reader! // DateTime tmpMin = SqlDateTime.MinValue.Value; Dictionary inst = new Dictionary(); while (reader.Read()) { TrackingWorkflowEvent evt = (TrackingWorkflowEvent)reader[0]; DateTime dt = reader.GetDateTime(1); int order = reader.GetInt32(2); object tmp = null; EventArgs args = null; if (!reader.IsDBNull(3)) { BinaryFormatter formatter = new BinaryFormatter(); tmp = formatter.Deserialize(new MemoryStream((Byte[])reader[3])); if (tmp is EventArgs) args = (EventArgs)tmp; } long eventId = reader.GetInt64(4); DateTime dbDt = reader.GetDateTime(5); inst.Add(eventId, new WorkflowTrackingRecord(evt, dt, order, args)); if (dbDt > tmpMin) tmpMin = dbDt; } if (!reader.NextResult()) throw new ArgumentException(ExecutionStringManager.InvalidWorkflowInstanceEventReader); // // Add any annotations while (reader.Read()) { long eventId = reader.GetInt64(0); string annotation = null; if (!reader.IsDBNull(1)) annotation = reader.GetString(1); WorkflowTrackingRecord rec = null; if (inst.TryGetValue(eventId, out rec)) { if (null != rec) rec.Annotations.Add(annotation); } } if (!reader.IsClosed) reader.Close(); // // Check if we have any WorkflowChange events in this list // If so pull back the change actions and reconstruct the args property foreach (KeyValuePair kvp in inst) { WorkflowTrackingRecord rec = kvp.Value; if (TrackingWorkflowEvent.Changed != rec.TrackingWorkflowEvent) continue; SqlCommand cmd = new SqlCommand(); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "[dbo].[GetWorkflowChangeEventArgs]"; cmd.Parameters.Add(new SqlParameter("@WorkflowInstanceInternalId", _internalId)); cmd.Parameters.Add(new SqlParameter("@BeginDateTime", SqlDateTime.MinValue.Value)); cmd.Parameters.Add(new SqlParameter("@WorkflowInstanceEventId", kvp.Key)); ExecuteRetried(cmd, LoadWorkflowChangeEventArgsFromReader, rec); } _workflowEvents.AddRange(inst.Values); // // set the min for the next query to the most recent event from this query // Don't overwrite the previous min if nothing came back for this query if (tmpMin > SqlDateTime.MinValue.Value) _instMinDT = tmpMin; } private void LoadWorkflowChangeEventArgsFromReader(SqlDataReader reader, object parameter) { if (null == reader) throw new ArgumentNullException("reader"); if (null == parameter) throw new ArgumentNullException("parameter"); WorkflowTrackingRecord record = parameter as WorkflowTrackingRecord; if (null == record) throw new ArgumentException(ExecutionStringManager.InvalidWorkflowChangeEventArgsParameter, "parameter"); if (!reader.Read()) throw new ArgumentException(ExecutionStringManager.InvalidWorkflowChangeEventArgsReader); StringReader sr = new StringReader(reader.GetString(0)); //Deserialize the xoml and set the root activity Activity def = null; WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer(); DesignerSerializationManager serializationManager = new DesignerSerializationManager(); IList errors = null; try { using (serializationManager.CreateSession()) { using (XmlReader xmlReader = XmlReader.Create(sr)) { def = serializer.Deserialize(serializationManager, xmlReader) as Activity; errors = serializationManager.Errors; } } } finally { sr.Close(); } if ((null == def) || ((null != errors) && (errors.Count > 0))) throw new WorkflowMarkupSerializationException(ExecutionStringManager.WorkflowMarkupDeserializationError); if (!reader.NextResult()) throw new ArgumentException(ExecutionStringManager.InvalidWorkflowChangeEventArgsReader); // // There is a result set that we don't care about for this scenario, skip it if (!reader.NextResult()) throw new ArgumentException(ExecutionStringManager.InvalidWorkflowChangeEventArgsReader); List actions = new List(); DateTime currDT = DateTime.MinValue; int currEventOrder = -1; int currOrder = -1; while (reader.Read()) { DateTime dt = reader.GetDateTime(1); int eventOrder = reader.GetInt32(2); int order = reader.GetInt32(3); // // Build temp lists as we read the results but // only save the last set of change actions if (dt > currDT && eventOrder > currEventOrder) { currEventOrder = eventOrder; currOrder = order; currDT = dt; actions = new List(); } using (sr = new StringReader(reader.GetString(0))) { using (serializationManager.CreateSession()) { using (XmlReader xmlReader = XmlReader.Create(sr)) { ActivityChangeAction aAction = serializer.Deserialize(serializationManager, xmlReader) as ActivityChangeAction; errors = serializationManager.Errors; if (null == aAction) throw new WorkflowMarkupSerializationException(ExecutionStringManager.WorkflowMarkupDeserializationError); actions.Add(aAction); aAction.ApplyTo(def); } } } } record.EventArgs = new TrackingWorkflowChangedEventArgs(actions, def); } private void LoadDef() { SqlCommand cmd = null; // // If we don't have the definition load it if (null == _def) { cmd = new SqlCommand(); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "[dbo].[GetWorkflowDefinition]"; cmd.Parameters.Add(new SqlParameter("@WorkflowInstanceInternalId", _internalId)); ExecuteRetried(cmd, LoadDefFromReader); } // // Now check for changes. If we find changes apply them to the definition cmd = CreateInternalIdDateTimeCommand("[dbo].[GetWorkflowChanges]", _changesMinDT); ExecuteRetried(cmd, LoadChangesFromReader); } private void LoadDefFromReader(SqlDataReader reader, object parameter) { if (null == reader) throw new ArgumentNullException("reader"); if (!reader.Read()) throw new ArgumentException(ExecutionStringManager.InvalidDefinitionReader); StringReader sr = new StringReader(reader.GetString(0)); //Deserialize the xoml and set the root activity WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer(); DesignerSerializationManager serializationManager = new DesignerSerializationManager(); IList errors = null; try { using (serializationManager.CreateSession()) { using (XmlReader xmlReader = XmlReader.Create(sr)) { _def = serializer.Deserialize(serializationManager, xmlReader) as Activity; errors = serializationManager.Errors; } } } finally { sr.Close(); } if ((null == _def) || ((null != errors) && (errors.Count > 0))) throw new WorkflowMarkupSerializationException(ExecutionStringManager.WorkflowMarkupDeserializationError); } private void LoadChangesFromReader(SqlDataReader reader, object parameter) { if (!reader.Read()) return; // // Reset the min to the most recent change event DateTime tmpDT = _changesMinDT; if (!reader.IsDBNull(0)) tmpDT = reader.GetDateTime(0); if (reader.NextResult()) { WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer(); DesignerSerializationManager serializationManager = new DesignerSerializationManager(); while (reader.Read()) { IList errors = null; using (StringReader sr = new StringReader(reader.GetString(0))) { using (serializationManager.CreateSession()) { using (XmlReader xmlReader = XmlReader.Create(sr)) { ActivityChangeAction aAction = serializer.Deserialize(serializationManager, xmlReader) as ActivityChangeAction; errors = serializationManager.Errors; if (null != aAction) aAction.ApplyTo(_def); else throw new WorkflowMarkupSerializationException(ExecutionStringManager.WorkflowMarkupDeserializationError); } } } } } if (tmpDT > _changesMinDT) { _changed = true; _changesMinDT = tmpDT; } } private void LoadInvokedWorkflows() { SqlCommand cmd = new SqlCommand(); cmd.CommandText = "[dbo].[GetInvokedWorkflows]"; cmd.CommandType = CommandType.StoredProcedure; SqlParameter param = new SqlParameter("@WorkflowInstanceId", SqlDbType.UniqueIdentifier); param.Value = _id; cmd.Parameters.Add(param); param = new SqlParameter("@BeginDateTime", SqlDbType.DateTime); param.Value = _invMinDT; cmd.Parameters.Add(param); param = new SqlParameter("@EndDateTime", SqlDbType.DateTime); param.Value = _currDT; cmd.Parameters.Add(param); ExecuteRetried(cmd, LoadInvokedWorkflowsFromReader); } private void LoadInvokedWorkflowsFromReader(SqlDataReader reader, object parameter) { if (null == reader) throw new ArgumentNullException("reader"); DateTime tmpMin = SqlDateTime.MinValue.Value; while (reader.Read()) { SqlTrackingWorkflowInstance inst = SqlTrackingQuery.BuildInstance(reader, _connectionString); if (inst.Initialized > tmpMin) tmpMin = inst.Initialized; _invoked.Add(inst); } // // set the min for the next query to the most recently invoked instance from this query // Don't overwrite the previous min if nothing came back for this query if (tmpMin > SqlDateTime.MinValue.Value) _invMinDT = tmpMin; } private void ExecuteRetried(SqlCommand cmd, LoadFromReader loader) { ExecuteRetried(cmd, loader, null); } private void ExecuteRetried(SqlCommand cmd, LoadFromReader loader, object loadFromReaderParam) { SqlDataReader reader = null; short count = 0; while (true) { try { using (SqlConnection conn = new SqlConnection(_connectionString)) { cmd.Connection = conn; cmd.Connection.Open(); reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); loader(reader, loadFromReaderParam); break; } } catch (SqlException se) { // // Retry if we deadlocked. // All other exceptions bubble if ((_deadlock == se.Number) && (++count < _retries)) continue; throw; } finally { if ((null != reader) && (!reader.IsClosed)) reader.Close(); } } } private SqlCommand CreateInternalIdDateTimeCommand(string commandText, DateTime minDT) { return CreateInternalIdDateTimeCommand(commandText, minDT, _currDT); } private SqlCommand CreateInternalIdDateTimeCommand(string commandText, DateTime minDT, DateTime maxDT) { if (null == commandText) throw new ArgumentNullException("commandText"); SqlCommand cmd = new SqlCommand(); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = commandText; // // Add parameter values for GetActivityEvents SqlParameter param = new SqlParameter("@WorkflowInstanceInternalId", SqlDbType.BigInt); param.Value = _internalId; cmd.Parameters.Add(param); param = new SqlParameter("@BeginDateTime", SqlDbType.DateTime); param.Value = minDT; cmd.Parameters.Add(param); param = new SqlParameter("@EndDateTime", SqlDbType.DateTime); param.Value = maxDT; cmd.Parameters.Add(param); return cmd; } } }