--
You received this message because you are subscribed to the Google Groups "Particular Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to particularsoftw...@googlegroups.com.
To post to this group, send email to particula...@googlegroups.com.
Visit this group at http://groups.google.com/group/particularsoftware.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/07184861-7342-499e-afa0-31502e091ba1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Hi Szymon,
The Saga code is below. I am not overriding any NHibernate mappings, everything is out of the box NServiceBus.Nhibernate Nuget stuff. The Saga data is only accessed by NServiceBus. Nothing else is using that database.
Here is the Saga code:
public partial class ProcessOrderHandler :
IHandleMessages<OrderProcessed>,
IHandleTimeouts<ProcessOrderTimeout>,
IHandleTimeouts<ProcessOrderLater>
{
ILog _logger;
[Dependency]
public IOrderMapper OrderMapper { get; set; }
[Dependency]
public IOrderRepository OrderRepository { get; set; }
[Dependency]
public IConfigurationRepository ConfigurationRepository { get; set; }
public ProcessOrderHandler()
{
_logger = LogManager.GetLogger(GetType());
}
partial void HandleImplementation(ProcessOrder message)
{
_logger.DebugFormat("OrderService received {0} message with order id {0}/order type {1} ", message.GetType().Name, message.OrderID, message.OrderTypeCode ?? "not set");
var processMessage = message;
try
{
var config = ConfigurationRepository.GetConfiguration(ConfigurationRepository.GetAppsetting(Constants.APPSETTINGS_KEY_CONFIGURATION_NAME) ?? "Dev");
if (string.IsNullOrWhiteSpace(message.OrderTypeCode))
{
processMessage = OrderRepository.GetOrderPickupDetails(message) as ProcessOrder;
if (processMessage == null || string.IsNullOrEmpty(processMessage.OrderTypeCode))
{
var msg = string.Format("Order with ID {0} was not found in the repository or is invalid.", message.OrderID);
_logger.Info(msg);
if (processMessage == null)
processMessage = message;
Bus.Publish(new OrderFailed
{
BroadcastStatus = true,
OrderID = processMessage.OrderID,
OrderTypeCode = processMessage.OrderTypeCode,
WorkflowTypeCode = processMessage.WorkflowTypeCode,
Message = msg
});
MarkAsComplete();
return;
}
processMessage.OrderTypeCode = processMessage.OrderTypeCode.Split(':')[0];
Data.ProcessOrder = processMessage;
}
if (processMessage.ExecutionTime > DateTime.UtcNow + config.OrderSettings.ExecutionDelayAllowedTimeOffset || processMessage.OverrideExecutionTime != null || processMessage.OverrideExecutionDelay != null)
{
Data.TimeOutVersion++;
if (processMessage.OverrideExecutionTime != null)
{
_logger.InfoFormat("Order {0} delayed execution until {1} because of caller override. Timeout version {2}", processMessage.OrderID, processMessage.OverrideExecutionTime, Data.TimeOutVersion);
var overrideExecutionTime = new DateTime(processMessage.OverrideExecutionTime.Value.Ticks, DateTimeKind.Utc);
RequestTimeout<ProcessOrderLater>(overrideExecutionTime, new ProcessOrderLater { TimeOutVersion = Data.TimeOutVersion });
}
if (processMessage.OverrideExecutionDelay != null)
{
_logger.InfoFormat("Order {0} delayed execution by {1} because of caller override. Timeout version {2}", processMessage.OrderID, processMessage.OverrideExecutionDelay, Data.TimeOutVersion);
RequestTimeout<ProcessOrderLater>(processMessage.OverrideExecutionDelay.Value, new ProcessOrderLater { TimeOutVersion = Data.TimeOutVersion });
}
if (processMessage.ExecutionTime > DateTime.UtcNow + config.OrderSettings.ExecutionDelayAllowedTimeOffset)
{
_logger.InfoFormat("Order {0} delayed execution until {1} because of EventDateTime. Timeout version {2}", processMessage.OrderID, processMessage.ExecutionTime, Data.TimeOutVersion);
var executionTime = processMessage.ExecutionTime;
RequestTimeout<ProcessOrderLater>(executionTime, new ProcessOrderLater { TimeOutVersion = Data.TimeOutVersion });
}
return;
}
var mappedOrder = OrderMapper.MapOrder(processMessage);
mappedOrder.IgnoreOrderExecutionTimeAndDelay = false;
_logger.DebugFormat("Publishing {0} with order id {1}/order type {2}", mappedOrder.GetType().Name, mappedOrder.OrderID, mappedOrder.OrderTypeCode);
Bus.Publish(mappedOrder);
}
catch (Exception ex)
{
var msg = string.Format("ProcessOrder failed for order {0} ({1})", processMessage.OrderID, ex.Message);
_logger.Error(msg, ex);
if (processMessage == null)
processMessage = message;
Bus.Publish(new OrderFailed
{
BroadcastStatus = processMessage.BroadcastStatus,
OrderID = processMessage.OrderID,
OrderTypeCode = processMessage.OrderTypeCode,
WorkflowTypeCode = processMessage.WorkflowTypeCode,
Message = msg
});
}
RequestTimeout<ProcessOrderTimeout>(new TimeSpan(7, 0, 0, 0)); // Clean up the saga after a week in case something goes awry
}
public void Timeout(ProcessOrderLater timeout)
{
_logger.DebugFormat("ProcessOrderLater version {0} for order id {1}", timeout.TimeOutVersion, Data.ProcessOrder.OrderID);
if (timeout.TimeOutVersion != Data.TimeOutVersion)
{
_logger.InfoFormat("ProcessOrderLater version {0} for order id {1} is cancelled. Exiting.", timeout.TimeOutVersion, Data.ProcessOrder.OrderID);
return;
}
Data.ProcessOrder.OrderTypeCode = null; // force reload of order details
_logger.DebugFormat("Rehandling order {0} after execution timeout", Data.ProcessOrder.OrderID);
HandleImplementation(Data.ProcessOrder);
}
public void Timeout(ProcessOrderTimeout timeout)
{
_logger.InfoFormat("OrderService timeout order id {0}. Cleaning up.", Data.ProcessOrder.OrderID);
MarkAsComplete();
}
public void Handle(OrderProcessed message)
{
_logger.DebugFormat("OrderService received " + message.GetType().Name);
var response = new ProcessOrderResponse
{
OrderID = message.OrderID,
OrderTypeCode = message.OrderTypeCode,
OrderConfigCode = Data.ProcessOrder.OrderConfigCode,
OrderHandlerFullyQualifiedName = Data.ProcessOrder.OrderHandlerFullyQualifiedName,
CorrelationID = message.CorrelationID,
ResponseCode = ProcessOrderResponseCode.OK.ToString(),
BroadcastStatus = message.BroadcastStatus
};
ReplyToOriginator(response);
//if (!ProcessFollowUpOrders(message.FollowUpOrders))
ProcessFollowUpOrders(message.FollowUpOrders);
checkAndMarkAsComplete();
}
private bool ProcessFollowUpOrders(IEnumerable<int> followUpOrders)
{
if (followUpOrders == null || followUpOrders.Count() == 0)
{
_logger.Debug("No follow up orders.");
return false;
}
foreach (var orderID in followUpOrders)
{
_logger.DebugFormat("Adding follow up order with id {0}", orderID);
var order = new ProcessOrder
{
CorrelationID = Data.CorrelationID,
OrderID = orderID
};
Bus.SendLocal(order);
}
return true;
}
private void checkAndMarkAsComplete()
{
// TODO: Find a good solution to knowing whether there are more outstanding orders on this saga.
//if (Data.ActiveOrders == null || Data.ActiveOrders.Count == 0)
//MarkAsComplete();
}
}
Morten Petterøe
Chief Technology Officer
________________________________________________________________________________
--
You received this message because you are subscribed to a topic in the Google Groups "Particular Software" group.
To unsubscribe from this topic, visit
https://groups.google.com/d/topic/particularsoftware/FtN-z7GD0PA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to
particularsoftw...@googlegroups.com.
To post to this group, send email to
particula...@googlegroups.com.
Visit this group at http://groups.google.com/group/particularsoftware.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/CAE87YxpQGNQZL4r%2BejAWtABsSXVTHcFvWKjursnOp05PE3UxBQ%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/7ba6799b965649d19c13bd14f839b943%40MBX02B-IAD3.mex06.mlsrvr.com.
Hi again,
The ProcessOrderLater timeout is requested both in the ProcessOrderLater timeout handler and in the HandleImplementation method itself (look in the “if (processMessage.ExecutionTime…” block).
You are right, there is no other difference between 0 or n follow up orders than Bus.SendLocal. It’s a kind of a recursive process. The order handler keeps calling itself until there are no more orders to process.
I am using log4net using UDP to log2console, so I should be able to give what you want from the Nhibernate log. Do you want everything, or just some sections of the debug log?
Thanks,
Morten
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/CAE87YxpcTHoiBYSKmVUKjujx3prEYEfNO4so2XyoJCqYOtm1DQ%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/77e38f15554f4685ac1f7deb1ac0507d%40MBX02B-IAD3.mex06.mlsrvr.com.
Ah. I see the confusion J
This project started as a ServiceMatrix solution. The actual handler implementation is in another partial class. So “HandleImplementation” in my class is the same as a regular Handle(ProcessOrder message) implementation. That means that the HandleImplementation is called on every incoming ProcessOrder command. Here is the actual handler code:
public void Handle(ProcessOrder message)
{
// Store message in Saga Data for later use
this.Data.ProcessOrder = message;
// Handle message on partial class
this.HandleImplementation(message);
// Check if Saga is Completed
CheckIfAllMessagesReceived();
}
I have run a new order and put SQL Profiler on it to get the SQL log. I have attached the trace file here. The SQL trace shows the flow below. As you can see in the sequence diagram from ServiceInsight, the first order processes fine, but the follow up order fails.

To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/CAE87YxoZFYy5J2x44uesw-WYS_SdSpsgHoszYOy6a%3Db3fwb2GA%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/df95beedd15e4002a6dfd61513546190%40MBX02B-IAD3.mex06.mlsrvr.com.
Hi again Szymon,
I have attached the table scripts. I will go through the update statements in the trace to see if I see anything wrong.
If the problem is with precisions, how do I fix this? Is there a way to tell Nhibernate how my data structure looks?
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/CAE87YxqdPLTRH161JMUGztCN7yvYyQ4T%3D8_tHi81gJJK1L-%3Diw%40mail.gmail.com.
Hi again Szymon,
I think I am a bit closer to seeing what the problem is.
My saga has Saga Data properties containing various incoming messages that are used during the Saga lifetime. I am correlating my sagas based on a Guid property “CorrelationID” on every order.
When NHibernate does the initial Saga “INSERT”, it correctly inserts NULL on the CorrelationID on the empty message properties. For example, I have a property on my ProcessOrderHandlerSagaData called GenerateSmsOrderProcessed, which is a message that is handled by the Saga. When the saga is first invoked, this property is null. The following is a snippet of the NHibernate “INSERT” which correctly sets the CorrelationID of the empty GenerateSmsOrderProcessed property to null:
GenerateSmsOrderProcessedCorrelationID
@p36,
@p36 uniqueidentifier,
@p36=NULL,
When the next saga invocation is made, the GenerateSmsOrderProcessed property is still empty, but the “SELECT” made by NHibernate is wrong. It assumes a Guid.Empty instead of a null value on the missing CorrelationID. Which means that the SELECT returns zero rows. Here is a snippet from the NHibernate “SELECT”:
AND GenerateSmsOrderProcessedCorrelationID = @p41
@p41 uniqueidentifier,
@p41='00000000-0000-0000-0000-000000000000',
Is there a way to trick NHibernate to select only based on the “CorrelationID” property on the Order message (the property I use to map messages to Sagas)? The long list of “AND” statements in the select seems to be a very brittle piece of functionality.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/CAE87YxqdPLTRH161JMUGztCN7yvYyQ4T%3D8_tHi81gJJK1L-%3Diw%40mail.gmail.com.
Sorry; when I write “SELECT” below, I mean “UPDATE”….
Morten Petterøe
Chief Technology Officer
________________________________________________________________________________
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/1c239eac719d42768314daa5126c5e2f%40MBX02B-IAD3.mex06.mlsrvr.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/b5bbccdbf4214e2babe6c20681793676%40MBX02B-IAD3.mex06.mlsrvr.com.
Thanks Szymon,
I will try the RowVersion trick you mention here. In the mean time, here is my saga data class
public partial class ProcessOrderHandlerSagaData
{
public virtual Guid Id { get; set; }
public virtual string Originator { get; set; }
public virtual string OriginalMessageId { get; set; }
public virtual ProcessOrder ProcessOrder { get; set; }
public virtual EmailOrderProcessed GenerateEmailOrderProcessed { get; set; }
public virtual SendEmailOrderProcessed SendEmailOrderProcessed { get; set; }
public virtual GenerateSmsOrderProcessed GenerateSmsOrderProcessed { get; set; }
public virtual SendSmsOrderProcessed SendSmsOrderProcessed { get; set; }
[Unique]
public virtual Guid CorrelationID
{
get
{
if (ProcessOrder == null)
return Guid.Empty;
else
return ProcessOrder.CorrelationID;
}
}
public virtual int TimeOutVersion { get; set; }
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/CAE87YxqD%3DfyugQamZrdbNUZ5znUXo18h86dFS%3Do6GLV_sNETgA%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/ebf4500f1ef3445581b9df52e8d459d0%40MBX02B-IAD3.mex06.mlsrvr.com.
Btw;
All the XxxOrderProcessed messages derive from this one:
public class OrderProcessed : IOrderProcessed, ISagaCorrelator
{
public int OrderID { get; set; }
public IEnumerable<int> FollowUpOrders { get; set; }
public string OrderTypeCode { get; set; }
public string WorkflowTypeCode { get; set; }
public bool BroadcastStatus { get; set; }
public string Operator { get; set; }
public Guid CorrelationID { get; set; }
}
Morten Petterøe
Chief Technology Officer
________________________________________________________________________________
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/ebf4500f1ef3445581b9df52e8d459d0%40MBX02B-IAD3.mex06.mlsrvr.com.
Here it is. See OrderProcessed in my previous email
public class GenerateSmsOrderProcessed : OrderProcessed
{
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/CAE87YxqX0bx6w1CN_hYYQN6%3D0Yyf%2B%2BSLihSY58HroGN7ue47hA%40mail.gmail.com.
One quick question;
Will these problems go away if I switch to RavenDB?
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/8270a1ec98f14a7aa3f3b44f5c68f3b4%40MBX02B-IAD3.mex06.mlsrvr.com.
Hi again,
I have now done one test with the [RowVersion] attribute, and so far it looks promising J. This was just one test, so I need to use the system a little bit to see it it holds up with load.

Morten Petterøe
Chief Technology Officer
________________________________________________________________________________
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/8270a1ec98f14a7aa3f3b44f5c68f3b4%40MBX02B-IAD3.mex06.mlsrvr.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/8cd653ae7759473cbe736fd7a7536462%40MBX02B-IAD3.mex06.mlsrvr.com.
Thank you Szymon for spending time on this problem.
I will put the saga data on a diet J. It was the ServiceMatrix generator that made this code, so I didn’t think much about it. But there is certainly room for losing a bit of extra fat here.
To view this discussion on the web visit https://groups.google.com/d/msgid/particularsoftware/CAE87Yxqo1zVyJWq0xcfHP%3D_4UL5bLfWQpygyGqOH---YYH7bTA%40mail.gmail.com.