Topic: Advice needed on handling updating objects while in a loop of objectcollection items |
Posted On: 12/3/2008 6:39 PM
Posted By: Mike Hamilton
|
WHEW! that subject was a mouthful, or should I say a textboxful! LOL
anyways here is the situation, hopefully I can explain it clearly so I can get advice on how I should handle it.
we have an object base on a table that is a queue for items that need to be sent. The problem is that this can be a long running process to compile and send these items so we want to update the table to show it is being processed so that if another run of the processing system starts this item would not be found in the find run to get items to be processed.
| Private Sub ProcessFolderSendQueue() |
| ' get reference to FolderSend |
| Using ReadyItems As New BizLibrary.FolderSend() |
| ' get the items ready to send (status=2) |
| ReadyItems.ObjectMode = Akal.QuickObjects.ObjectBase.ObjectModes.Search |
| ReadyItems.Status.Parse(2) |
| |
| ' set sort order |
| ReadyItems.Send_Method.Set(Akal.QuickObjects.ObjectBase.SortTypes.Ascending, 1) |
| ReadyItems.Date_Submitted.Set(Akal.QuickObjects.ObjectBase.SortTypes.Ascending, 2) |
| |
| ' do find |
| ReadyItems.Find() |
| |
| ' loop through any found |
| For Each ReadyItem As BizLibrary.FolderSend In ReadyItems.List |
| ' set as processing |
| ReadyItem.MarkProcessingStarted() |
| |
| ' do processing |
| |
| ' set as sent |
| ReadyItem.MarkProcessingComplete() |
| Next |
| End Using |
| End Sub |
| |
what I am wondering is how I should handle the MarkProcessingStarted() and MarkProcessingComplete() methods on the object.
what checks should I do in those methods? should I check the object is loaded using "Me.IsLoaded" first? should I just do this
| Public Sub MarkProcessingStarted() |
| With Me |
| .ObjectMode = ObjectModes.Save |
| .Status.Value = 3 ' 3= processing |
| .Update() |
| End With |
| End Sub |
| |
| |
or should I create a new object and run update on that object?
Is it reccomended (or safe) to go updating the objects while they are being processed as they are in the loop of the item colelction?
Thanks!
Mike
Mike,
This is an interesting question. To start of, it is safe to update object while looping through the item collection. In fact that sometimes is more desirable than creating a new instance to perform an update. If you create a new instance to do the update you will not have the capability perform a concurrency check out of the box, as in you can still do it but you will need to also assign/load the original values as well. If you use the same instance and you have the ConcurrencyMode = DetectChanges then the concurrency check is automatic.
From what I understand, you have a high number of objects that basically need to be processed. Once the processing starts on an Object you don't want another process/thread to process the same object. To solve this in addition to using the MarkProcessingStarted and MarkProcessingComplete methods I would add another method that will perform a quick check before starting the processing to make sure that no other process has begun processing. This however will cause extra queries going to the server and will add to the amount of processing time. This may not be an issue as you can reduce the query to pretty much "SELECT Status FROM FolderSend WHERE FolderSendID = X" but if you did the check for every record and you had 100k records you will be hitting your DB 100k times to perform a check.
Another solution would be to do something like this:
| Private Sub ProcessOrders() | | Using order As Orders = New Orders() | | order.Status.Value = 2 ' Ready To Send | | order.Status.UseInSearch = True | | | order.MaxRecords = 10 ' You could also limit the number of records you will be processing.
| | order.Find() | | | | order.Status.Value = 3 ' Processing | | order.Status.UseInSave = True | | | | order.OrderID.SearchMode = SearchModes.List | | ' The following line will get a comma seperated list of OrderID values that have already been loaded. | | order.OrderID.List.CustomValue = order.GetDelimetedValue(order.ResultTable.Rows, order.OrderID.Name, ",") | | | | ' After this statement only OrderID's UseInSearch property is set to True | | order.SetSearchFields(True, order.OrderID) | | | | If order.BulkUpdate() Then | | ' Now that marked multiple order objects (that we fetched earlier) as being processed, we can switch the SearchMode of OrderID to Value | | ' that way next time OrderID is used in Search criteria its single value will be used rather than the list of comma seperated values. | | order.OrderID.SearchMode = SearchModes.Value | | | | ' Now start processing the orders | | | | For Each o As Orders In order.List | | ' Do something with the Order | | | | o.Status.Value = 4 ' Complete | | o.Status.UseInSave = True | | | | If o.Update() Then | | 'MessageBox.Show("Order Processed for " + o.OrderID.ToString()) | | End If | | Next | | | | ' Another choice would be to run another BulkUpdate call and set all the processed rows to Complete in one SQL statement. | | | | End If | | End Using | | End Sub | | |
This choice of using BulkUpdate can significantly improve performance and reduce the load on your server as well.
As far as "what checks should I do in those methods? should I check the object is loaded using "Me.IsLoaded" first? should I just do this" is concerned: The method should primarily make sure that the Primary Key has a value or the Update will fail. Then could be done as below:
| If Not .MyID.IsNull Then | | .Status.Value = 3 | | .Update() ' well Ideally you would check if the Update worked or not | | End If |
I hope I didn't miss anything, but if I did or something is unclear or you need further clarification do let me know.
Thanks, Ish
| 12/4/2008 9:14:59 AM Ish Singh
|
Ish,
Thanks for the advice.
I had forgot about the check of the item to make sure it was not grabbed by another run of the processing method. I dont forsee the numbers being that huge so the check of status before processing will not be much. But I will think about doing the bulk updates as you suggested too.
I did go ahead and write the "Mark" methods on the object while waiting for response and did include the check of the PK as you listed, so I did get that right. LOL
Mike
| 12/4/2008 5:09:44 PM Mike Hamilton
|
Mike,
One more trick and word of caution. When you are looping through items and you want to run another query/select against the same object, I highly recommend using the AddResultToDataSet = False.
Here is an example:
| Public Function IsProcessed() As Boolean | | Me.AddResultsToDataSet = False | | Me.UseAllFieldsForDisplay(False) | | Me.OrderProcessed.Visible = True | | If Me.Load() And Me.OrderProcessed.Value Then | | Return True | | End If | | Return False | | End Function | | | | |
Ofcourse if your field can contain null values you can also do a check for .IsNull before checking the value :)
Thanks, Ish
| 12/4/2008 10:26:08 PM Ish Singh
|