/// <summary> /// Find existing instance of ProcessManager /// FindData() and UpdateData() are part of the same transaction. /// FindData() opens new connection and transaction. /// UPDLOCK is placed onf the relevant row to prevent reads until the transaction is commited in UpdateData /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public IPersistanceData <T> FindData <T>(IProcessManagerPropertyMapper mapper, Message message) where T : class, IProcessManagerData { var mapping = mapper.Mappings.FirstOrDefault(m => m.MessageType == message.GetType()) ?? mapper.Mappings.First(m => m.MessageType == typeof(Message)); string tableName = typeof(T).Name; var sbXPath = new StringBuilder(); sbXPath.Append("(/" + tableName); foreach (var prop in mapping.PropertiesHierarchy.Reverse()) { sbXPath.Append("/" + prop.Key); } sbXPath.Append(")[1]"); XPathExpression xPathExpression; try { xPathExpression = XPathExpression.Compile(sbXPath.ToString()); } catch (XPathException ex) { Logger.ErrorFormat("Error compiling xpath expression. {0}", ex.Message); throw; } // Message Propery Value object msgPropValue = mapping.MessageProp.Invoke(message); SqlServerData <T> result = null; if (!GetTableNameExists(tableName)) { return(null); } using (var sqlConnection = new SqlConnection(_connectionString)) { sqlConnection.Open(); using (var command = new SqlCommand()) { command.Connection = sqlConnection; command.CommandTimeout = _commandTimeout; command.CommandText = string.Format(@"SELECT * FROM {0} WHERE DataXml.value('{1}', 'nvarchar(max)') = @val", tableName, xPathExpression.Expression); command.Parameters.Add(new SqlParameter { ParameterName = "@val", Value = msgPropValue }); try { var reader = command.ExecuteReader(CommandBehavior.SingleResult); if (reader.HasRows) { reader.Read(); var serializer = new XmlSerializer(typeof(T)); object res; using (TextReader r = new StringReader(reader["DataXml"].ToString())) { res = serializer.Deserialize(r); } result = new SqlServerData <T> { Id = (Guid)reader["Id"], Data = (T)res, Version = (int)reader["Version"] }; } reader.Dispose(); } finally { sqlConnection.Close(); } } } return(result); }
/// <summary> /// Create new instance of ProcessManager /// When multiple threads try to create new ProcessManager instance, only the first one is allowed. /// All subsequent threads will update data instead. /// </summary> /// <param name="data"></param> public void InsertData(IProcessManagerData data) { string tableName = GetTableName(data); var sqlServerData = new SqlServerData <IProcessManagerData> { Data = data, Version = 1, Id = data.CorrelationId }; var xmlSerializer = new XmlSerializer(data.GetType()); var sww = new StringWriter(); XmlWriter writer = XmlWriter.Create(sww); xmlSerializer.Serialize(writer, data); var dataXml = sww.ToString(); using (var sqlConnection = new SqlConnection(_connectionString)) { sqlConnection.Open(); using (var dbTransaction = sqlConnection.BeginTransaction(IsolationLevel.ReadCommitted)) { // Insert if doesn't exist, else update (only the first one is allowed) string upsertSql = string.Format(@"if exists (select * from {0} with (updlock,serializable) WHERE Id = @Id) begin UPDATE {0} SET DataXml = @DataXml, Version = @Version WHERE Id = @Id end else begin INSERT {0} (Id, Version, DataXml) VALUES (@Id,@Version,@DataXml) end", tableName); using (var command = new SqlCommand(upsertSql)) { command.Connection = sqlConnection; command.Transaction = dbTransaction; command.Parameters.Add("@Id", SqlDbType.UniqueIdentifier).Value = data.CorrelationId; command.Parameters.Add("@Version", SqlDbType.Int).Value = sqlServerData.Version; command.Parameters.Add("@DataXml", SqlDbType.Xml).Value = dataXml; try { command.ExecuteNonQuery(); dbTransaction.Commit(); } catch { dbTransaction.Rollback(); throw; } finally { sqlConnection.Close(); } } } } }