Concurrency control
Adapter contains an advanced concurrency mechanism, in such a way that you can decide how to implement concurrency control in your application. It is often better to schedule concurrency aspects at a high level in your application, however if you are required to check whether a save can take place or not, you can.
Similar to SelfServicing, Adapter allows you to specify a predicate expression object with the SaveEntity() method. This predicate expression is included in the UPDATE query (it's ignored in an INSERT query) so you can specify exactly if the update should take place.
Adapter also allows you to implement the interface IConcurrencyPredicateFactory
, and
instances of that interface can be inserted into entity class instances.
If such a factory is present inside an entity class instance,
SaveEntity() will automatically request a predicate object from that
factory to include in the UPDATE query. This way you can provide
concurrency predicates during a recursive save action.
To filter on the original database values fetched into the entity to be saved, you can create for example FieldCompareValuePredicate instances which use the EntityField2.DbValue property. Even though a field is changed in memory through code, the DbValue property of a field will have the original value read from the database. You can use this for optimistic concurrency schemes. See for an example below. If the field is NULL in the database, DbValue is null (C#) or Nothing (VB.NET). To efficiently read the DbValue of a field, use the GetDbValue method on an entity's Fields object as shown below in the example.
See for more information on predicates and filtering Getting started with filtering.
Below an example implementation of IConcurrencyPredicateFactory
, which
returns predicates which test for equality on EmployeeID for the
particular Order. This will make sure the Save or Delete action will
only succeed if the entity in the database has still the same value for
EmployeeID as the in-memory entity had when it was loaded from the
database.
private class OrderConcurrencyFilterFactory :
IConcurrencyPredicateFactory
{
public IPredicateExpression CreatePredicate(
ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
{
IPredicateExpression toReturn = new PredicateExpression();
OrderEntity order = (OrderEntity)containingEntity;
switch(predicateTypeToCreate)
{
case ConcurrencyPredicateType.Delete:
toReturn.Add(OrderFields.EmployeeID == order.Fields.GetDbValue((int)OrderFieldIndex.EmployeeID));
break;
case ConcurrencyPredicateType.Save:
// only for updates
toReturn.Add(OrderFields.EmployeeID == order.Fields.GetDbValue((int)OrderFieldIndex.EmployeeID));
break;
}
return toReturn;
}
}
Private Class OrderConcurrencyFilterFactory
Implements IConcurrencyPredicateFactory
Public Function CreatePredicate( _
predicateTypeToCreate As ConcurrencyPredicateType, containingEntity As object) _
As IPredicateExpression Implements IConcurrencyPredicateFactory.CreatePredicate
Dim toReturn As New PredicateExpression()
Dim order As OrderEntity = CType(containingEntity, OrderEntity)
Select Case predicateTypeToCreate
Case ConcurrencyPredicateType.Delete
toReturn.Add(OrderFields.EmployeeID = _
order.Fields.GetDbValue(CInt(OrderFieldIndex.EmployeeID)))
Case ConcurrencyPredicateType.Save
' only for updates
toReturn.Add(OrderFields.EmployeeID = _
order.Fields.GetDbValue(CInt(OrderFieldIndex.EmployeeID)))
End Select
Return toReturn
End Function
End Class
During recursive saves, if a save action fails, which can be caused by a ConcurrencyPredicateFactory produced predicate, thus if no rows are affected by the save action, an ORMConcurrencyException is thrown by the save logic, which will terminate any transaction started by the recursive save.
To set an IConcurrencyPredicateFactory
object when an entity is
created or initialized, please see the section Adding your own code to
the generated classes which discusses
various ways to adjust the generated code to add your own initialization
code which for example sets the IConcurrencyPredicateFactory
instance
for a particular object.
You can also set an IConcurrencyPredicateFactory
instance of an entity using the
ConcurrencyPredicateFactoryToUse property of an entity collection to
automatically set the ConcurrencyPredicateFactoryToUse property of an
entity when it's added to the particular entity collection.
A third way to set an entity class instance's IConcurrencyPredicateFactory
is by using Dependency
Injection.