Concurrency control

There is an overloaded Save() variant which takes a predicate object, also known as a filter. This is also the case for Delete(). This filter is constructed using the objects described in Getting started with filtering and can be used to set a condition when the update (or delete, when the filter is used as a parameter to Delete()) has to take place (the filter is ignored when the entity is new).

For example, a predicate object that contains a field = value compare clause for a timestamp column in the table where the entity-to-update is located. If the entity's timestamp column is not the same (if you have defined that in your predicate object passed to Save()), the save is not performed, or in the case of calling Delete() with a predicate, the delete will not take place.

To filter on the original database values fetched into the entity to be saved, you can create for example FieldCompareValuePredicate instances which use the EntityField's 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).

LLBLGen Pro supports another form of supplying predicates for filters during Save or Delete actions: implementing IConcurrencyPredicateFactory. You can implement this interface to produce, based on the type of action (save or delete) and the entity the predicate is for, an IPredicateExpression object which is then used as the filter for the action (Save or Delete).

Each entity object has a property, ConcurrencyPredicateFactoryToUse, which can be set to an instance of IConcurrencyPredicateFactory. If specified, each Save() and Delete() call on the entity will consult this object for a filter object. This is also the case for recursive saves. If you want concurrency control deep down a recursive save, it's key that you set those object's ConcurrencyPredicateFactoryToUse property to an instance of IConcurrencyPredicateFactory. IConcurrencyPredicateFactory instances can't be shared between Adapter and SelfServicing code, however the source code can be shared.

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)
    {
        var toReturn = new PredicateExpression();
        var 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;
    }
}

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 modify 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 EntityCollection 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.