Thursday, December 27, 2007

Optmistic Concurrency withTimestamps, LLBLGen and Web Services

Found a great article on how to implement optimistic concurrency with LLBLGEN and SQL Server:

Optmistic Concurrency and Timestamps in SQL Server Using LLBLGen Pro O/R Mapper by David Hayden.

Now, I am using XML Web Services and passing entities back and forth, and what I have found is that the DBValue property on each field is NOT serialized! I have not found a better solution as yet, but I am currently just using the field's CurrentValue. So, assuming the timestamp properties used do not get changed on the client, it will work, but this is a problem that will need to be fixed.

Update: Looks like there was an issue with the deserialization in version 2.5 (October 25 2007 release). The December release has apparently fixed this issue:

Modifications with LLBLGEN 2.5

I am inherently lazy, which is a bad thing when it comes to coding, but the solution described by LLBLGEN's help and David Hayden relies on a factory class for each entity. Sure, we should probably modify the templates and create a new factory class for each of our entities, but for what I wanted, that sounded like too much work.

Now, in LLBLGEN 2.5, all entities are now derived from another generated partial class called CommonEntityBase. And, lets face it, we are probably going to create our timestamp field on every entity and call it the same name (in this case, every entity has a timetamp field called LastModified). So, I created a new partial class definition for CommonEntityBase that allows me to get access to the timestamp field I will use for all entities' concurrency control.

public partial class CommonEntityBase
private const string LAST_MODIFIED_FIELD_NAME = "LastModified";

public IEntityField2 LastModifiedField
get { return this.Fields[LAST_MODIFIED_FIELD_NAME]; }

This then allows me to create a single factory for concurrency predicate expressions:
public class EntityConcurrencyFactory : IConcurrencyPredicateFactory
public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate,
object containingEntity)
IPredicateExpression toReturn = new PredicateExpression();
CommonEntityBase entity = (CommonEntityBase)containingEntity;

switch (predicateTypeToCreate)
case ConcurrencyPredicateType.Delete:
case ConcurrencyPredicateType.Save:
// only for updates
toReturn.Add(new FieldCompareValuePredicate(entity.LastModifiedField,
null, ComparisonOperator.Equal, entity.LastModifiedField.CurrentValue));
return toReturn;

Two classes for all entities - sounds good to me. Now, using David's example, there isn't any difference in the code (just every entity uses the same factory):
DataAccessAdapter adapter = new DataAccessAdapter(...);

BlogEntity blog = new BlogEntity();
blog.BlogId = 1;


blog.Name = "New Name";

IPredicateExpression expression = new EntityConcurrencyFactory()
.CreatePredicate(ConcurrencyPredicateType.Save, blog);

adapter.SaveEntity(blog, true, expression);

You can still change optimistic concurrency strategies easily, but it is easier to apply to all of your entities at once.


Anonymous said...

Hello Adrian,

I am using WCF and WinForms application and faced the same issue since last 2 months about DBValue not serialized when using WCF (Windows Communication Foundation) service.

Recently I upgraded to latest LLBLGen version and now when WinForms client send the Entity (DTO) over the intranet/network I can able to see DBValue and CurrentValue on WCF Service (server) side.

That means current version of LLBLGen fixed this issue? Can you able to check and advice.



Adrian Brown said...

Hi Amit,

I am using version 2.5, and as far as I know, this is the latest version.

Not sure if I mentioned it in the article, but I am not using WCF - just plain old .NET 2.0 web services. Perhaps this is the difference.

I'll have a look into it when I am back at work next week.


Anonymous said...

Hi Adrian,

Thanks for your reply.

see below link:𐷉



Adrian Brown said...

Thanks Amit, that explains it! I was using the October 25 release.

I've updated my post with that link.


Anonymous said...

I like this alot... but I don't think it will handle all the objects in the graph that may live below the main object being saved.

For example, if I am saving Blog and Blog contains BlogItems (which each in turn contains changed BlogItemAttributes), and I want to just save everything that's changed in blog and below it will only apply the concurrency check to the top object Blog. Ideally I would want the transaction to fail if any of the obejcts could not be saved. Any ideas?

Adrian Brown said...

Yeah you are right. My contrived example will only work for BlogEntity - any other entity in the graph will not use any concurrency check.

If you are going to save more than one entity in the graph, you will need to use the concurrency predicate factory to create a predicate expression that caters for all types. You should still able to use the one EntityConcurrencyFactory though.

Anonymous said...

Thanks Adrian, that's exactly what I've done now, and it seems to be working!

Below is the code I'm using in case others are interested. It defaults all entities (inheriting from CommonEntityBase) to optimistic concurrency with a default field name of "ModifiedDate". (The field can be overriden for any particular object or blanked out to eliminate this concurrency check, or another factor can always be assigned).

This allows all entities (even in a graph) to have simple optimistic concurrency by default. My code is using VB (not my choice...) and SelfServicing (again, not my choice), but it should all be the same for Adpater. (Flip IEntityField to IEntityField2). This site does a good VB to C# conversion:

Another note is that I use a datetime field, not a timestamp field in SQL Server so I also have a trigger to update the ModifiedDate on insert/update. The code for that is below also.


Partial Public Class CommonEntityBase

Protected Overrides Function CreateConcurrencyPredicateFactory() As SD.LLBLGen.Pro.ORMSupportClasses.IConcurrencyPredicateFactory
Return New OptimisticConcurrencyFactory_ViaTimestampCheck
End Function

Public Property ConcurrencyCheckFieldName() As String
Return _concurrencyCheckFieldName
End Get
Set(ByVal value As String)
_concurrencyCheckFieldName = value
End Set
End Property
Private _concurrencyCheckFieldName As String = "ModifiedDate"

End Class

Public Class OptimisticConcurrencyFactory_ViaTimestampCheck
Implements IConcurrencyPredicateFactory

Public Function CreatePredicate(ByVal predicateTypeToCreate As ConcurrencyPredicateType, ByVal containingEntity As Object) As IPredicateExpression Implements SD.LLBLGen.Pro.ORMSupportClasses.IConcurrencyPredicateFactory.CreatePredicate
Dim toReturn As IPredicateExpression = Nothing
Dim entity As CommonEntityBase = DirectCast(containingEntity, CommonEntityBase)

If entity.ConcurrencyCheckFieldName <> "" Then
toReturn = New PredicateExpression()
Dim lastModifiedDateField As IEntityField = entity.Fields(entity.ConcurrencyCheckFieldName)
Select Case predicateTypeToCreate
Case ConcurrencyPredicateType.Delete, ConcurrencyPredicateType.Save
toReturn.Add(New FieldCompareValuePredicate(lastModifiedDateField, ComparisonOperator.Equal, lastModifiedDateField.CurrentValue))
Exit Select
End Select
End If
Return toReturn

End Function

End Class

Trigger Code:
CREATE TRIGGER [dbo].[tr_Blog_Concurrency] ON [dbo].[Blog] FOR INSERT, UPDATE
UPDATE Blog SET ModifiedDate = getdate()
FROM Blog INNER JOIN inserted ON blog.blogID = inserted.blogID

Anonymous said...

ambien pill
Ambien - Wipe away insomnia
Order Ambien (Zolpidem) drugs at reputable online pharmacy and save money. No hidden fees!
[url=]zolpidem drug[/url]
Ambien should be taken before going to bed and when about to sleep. - cheap ambien
Ambien should be taken before going to bed and when about to sleep.

Anonymous said...
This comment has been removed by a blog administrator.
soju said...

I highly entrust the use of Ambien which you can buy on WWW.MEDSHEAVEN.COM even without prescription. MF