Field and Entity validation
Business logic can be implemented in a variety of ways. Roughly you can divide the business rules in three groups:
- business logic rules which deal with just a single field of one entity, for example a value for an ID field may not be less than 0. In this section, this is called Field validation.
- business logic rules which work with multiple fields of a single entity, for example the shipping date of an order can't be earlier in time than the orderdate itself. In this section, this is called Entity validation.
- business logic rules which deal with multiple entities, for example a customer is a gold customer if the customer has more than n orders in the last m months
For the first two groups, LLBLGen Pro offers a rich validation mechanism which you can fill in using two different ways: implement the business rules inside the entity class or implement the business rules inside a Validator class.
This section describes both ways, starting by describing how to add validation logic directly to your entity classes. LLBLGen Pro's entity classes also have built-in validation to check whether a value possibly matches the field's characteristics. For example if an entity field is of type string and has a length of 10, and the field is set to a string with length 20, the built-in validation logic will throw an exception. This is further discussed in detail in the paragraph below.
Built-in field validation logic
LLBLGen Pro's entities have validation logic built-in for field values. This logic is executed every time an entity field's value is set, either through the properties or through entity.SetNewFieldValue(). The validation logic is meant to prevent database errors when the entity is persisted so it rejects every value which doesn't match the field's aspects: for string based fields and byte-array based fields, the length of the value is checked compared to the length set for the field.
For numeric fields the precision is checked and for fields with a fraction like Decimal, Single/float and Double based fields the scale is also checked. The built-in validation logic also checks if a field which isn't nullable is set to a null value. When an overflow is detected, an exception is thrown, the IDataErrorInfo error message for the field is set and the value is rejected.
Bypassing built-in validation logic
It might be that you don't want to use the built-in validation logic and want to use your own validation logic instead. You can make LLBLGen Pro bypass the built-in validation logic using the static (Shared) property on EntityBase (if you're using SeflServicing) or EntityBase2 (if you're using Adapter) called BuildInValidationBypassMode.
Set that property to any of the BuildInValidationBypass values, which are NoBypass (value 0, default), AlwaysBypass (value 1) or BypassWhenValidatorPresent (value 2). If you choose to use AlwaysBypass and there's no validator present no validation on the field will be performed.
You can also set this property through the application's config file by adding this line to the appSettings section of your application's config file:
<add key="buildInValidationBypassMode" value="0"/>
Where value is one of the values 0, 1 or 2 for resp. NoBypass, AlwaysBypass or BypassWhenValidatorPresent.
Netstandard 2.0+
If you're using the Netstandard 2.0+ build of the runtime, there's no .config file support and you have to use the RuntimeConfiguration system to configure this setting other than setting the field directly.
Defining the Scale overflow correction action to use
If you opt for the built-in validation logic and LLBLGen Pro detects a scale overflow for a field, it will truncate the fractional part of the value specified to the Scale size set for the field, e.g. if the scale is set to 2 for a given field and the field is set to the value 10.1234, the value the field is set to will be 10.12.
It might be this is not what you want: perhaps you want to round the value or throw an exception. You can define the scale overflow correction action by setting the ScaleOverflowCorrectionActionToUse property on EntityBase (if you're using SelfServicing) or EntityBase2 (if you're using Adapter) to any of the ScaleOverflowCorrectionAction values:
- None (value 0) an exception will be thrown
- Truncate (value 1) the fraction will be truncated to match the scale value
- Round (value 2) rounds the fraction to a value which fits the scale and rounds values using Math.Round(value, scale).
You can also set this value using the config file of your application by adding the following line to the appSettings section of your application's config file:
<add key="scaleOverflowCorrectionActionToUse" value="0"/>
Where value is one of the values 0, 1 or 2 for resp. None, Truncate or Round.
Netstandard 2.0+
If you're using the Netstandard 2.0+ build of the runtime, there's no .config file support and you have to use the RuntimeConfiguration system to configure this setting other than setting the field directly.
Validation logic inside Entity classes
The first of the two ways to add validation logic to the LLBLGen Pro framework is by adding the validation logic directly to the entity classes. The recommended way to do this is by defining partial classes of the generated entity classes.
Validation logic is added to the entity classes by overriding pre-defined methods of the entity base class. The LLBLGen Pro runtime will call during the entity life-cycle at pre-defined places a variety of methods on the entity, namely the method matching the current state of the entity and the action which is about to be taken on the entity.
By overriding these pre-defined methods, the code you add to the override will then be executed when the method is called by the runtime. By default the pre-defined methods are virtual / Overridable methods which call into an existing validator object, if the entity has a validator object set, otherwise they're no-ops.
Field validation
Every entity has a protected virtual (Overridable in VB.NET) method which is called by the routine which checks if a field should be set to a new value: OnValidateFieldValue. This method is called by the entity's SetValue routine after it has performed preliminairy checks on the new value, for example to see if a provided string value isn't too long for the set length of the field and also after Authorization has been granted by authorization logic.
If the routine returns true, the value is an acceptable value, otherwise the value should be rejected. OnValidateFieldValue() accepts the fieldindex of the field which value is about to be set and the new value. As this method is implemented on the entity, you can perform whatever check you need, as long as you return true if the value should be accepted or false if not.
If false is returned, the value is simply rejected and the field isn't set to a new value. The default implementation checks if there's a Validator object set in the entity and if so, it simply calls ValidateFieldValue on that Validator object to perform the validation. If no Validator is set, the default implementation simply returns true.
An override of OnValidateFieldValue is a good candidate for the situation where you want to set an error message for IDataErrorInfo if the value isn't correct. In that case see the IDataErrorInfo section below.
Be aware that the value passed to OnValidateFieldValue could be null / Nothing if the field to validate is a nullable field.
Example
// C# in partial class or user code region of OrderEntity
protected override bool OnValidateFieldValue( int fieldIndex, object value )
{
bool toReturn = true;
switch((OrderFieldIndex)fieldIndex)
{
case OrderFieldIndex.OrderId:
// id is valid if the value is > 0
toReturn = ((int)value > 0);
break;
default:
toReturn = true;
break;
}
return toReturn;
}
This field validation routine added to the OrderEntity class through either a partial class or by adding this code to the user code region for custom code of the OrderEntity class (at the bottom of the generated class), checks if the value specified for OrderId is > 0. All values for other fields are ignored by this validation logic and will simply bypass this routine by returning true, which means the value set is always accepted.
Entity validation
Entities can be validated in various contexts, for example when they're loaded from the database, before or after they're saved, before they're deleted etc. For each context a special method is created in the entity base class as protected virtual (Overridable). The methods defined are below with the description when they're called.
It's up to you to override one or more of these methods in a partial class of an entity or in a user code region (or include template). You don't have to override a method if you don't need validation for the entity for that context.
If validation fails, but it's not major, don't throw an exception but simply set the IDataErrorInfo error text on the entity. If the entity isn't valid, throw an ORMEntityValidationException. The LLBLGen Pro code simply calls OnValidateEntitycontextdescription on the entity and moves on if no exception is thrown.
There's also a general method: ValidateEntity(), which isn't used internally by the LLBLGen Pro runtime, but can be used by your own code to validate an entity in any context. The method Validate has been kept, though is now marked Obsolete. People who use Validate() should call ValidateEntity() from now on.
- OnValidateEntityAfterLoad. This method is called right after the entity has been filled with data and is ready to be used. Use this method to be sure a loaded entity contains the data you expect it to have. If the fetch method specifies a prefetch path, the entity is validated before the prefetch path has been loaded as well. All AfterLoad validation is performed right after the entity's own fetch routine has been completed and the state has been set. For bulk fetches of entities into collections, the entity is validated right before the entity will be added to the collection.
- OnValidateEntityBeforeSave. This method is called right before the save sequence for the entity will start.
- OnValidateEntityAfterSave. This method is called when the entity's save action has been completed and a refetch (if applicable) has taken place. If a refetch action takes place (Adapter) the AfterLoad validation is performed before this event. In SelfServicing, refetching of an entity occurs when a property is accessed after a save, so in SelfServicing AfterLoad will take place after AfterSave validation.
- OnValidateEntityBeforeDelete. This method is called when the entity's delete action is about to be executed.
Validation logic inside validator classes
It can be that you don't want to add validation logic to the generated classes but want to store this validation logic in a separate class, a Validator class and of which you can plug an instance of into an entity object. LLBLGen Pro supports this through Validator classes. Validator classes implements the interface IValidator.
To make life easier for you, we provide a basic implementation of this interface in the form of the base class ValidatorBase. Please consult the LLBLGen Pro reference manual for details about this base class and the interface.
LLBLGen Pro doesn't generate for each entity a validator class, however the tasks to do so are in the presets, yet they're disabled. You can enable LLBLGen Pro to generate validator classes per entity (called entitynameValidator) however: Enable the SD.Tasks.Generic.ValidatorClassesGenerator task in the preset of your choice by editing the preset in the preset viewer in the LLBLGen Pro Designer.
Setting an entity's Validator
If you've decided to use Validator classes to place your validation logic in, you'll be confronted with the question: how to set the right Validator in every entity object? You've three options:
- Setting the Validator property of an entity object manually. This is straight forward, but error prone: if you forget to set a validator, validation using this validator object isn't performed. Also, entities fetched in bulk into a collection are created using the factories so you have to alter these as well. You could opt for overriding OnInitializing in an entity to add the creation of the Validator class.
- By overriding the Entity method CreateValidator. This is a protected virtual (Protected Overridable) method which by default returns null / Nothing. You can override this method in a partial class or user code region of the Entity class to create the Validator to use for the entity. The LLBLGen Pro runtime framework will take care of calling the method. One way to create an override for most entities is by using a template. Please see the LLBLGen Pro SDK documentation for details about how to write templates to generate additional code into entities and in separate files. Also please see Adding Adding your own code to the generated classes for details.
- By using Dependency Injection. Using the Dependency Injection mechanism build into LLBLGen Pro, the defined Validators are injected into the entity objects for you. This option is the least amount of work.
Entity classes don't get a validator instance by default, even if you generate them through LLBLGen Pro.
As mentioned above, the methods implemented on the entity, like OnValidateEntityAfterLoad, call into an existing validator object. The following list informs you which entity method is calling which validator method and thus which method you should override in your own validator class to validate an entity in a given context.
- ValidateFieldValue. Called by entity.OnValidateFieldValue.
- ValidateEntityAfterLoad. Called by entity.OnValidateEntityAfterLoad.
- ValidateEntityBeforeSave. Called by entity.OnValidateEntityBeforeSave.
- ValidateEntityAfterSave. Called by entity.OnValidateEntityAfterSave.
- ValidateEntityBeforeDelete. Called by entity.OnValidateEntityBeforeDelete.
- ValidateEntity. Called by entity.ValidateEntity
Field validation
The example above in the field validation section for code inside entity classes can also be implemented by adding the code to a validator class instead. This lead to the following code.
Be aware that the value passed to OnValidateFieldValue could be null / Nothing if the field to validate is a nullable field.
Example
// C# in validator class deriving from ValidatorBase.
protected override bool ValidateFieldValue( IEntityCore involvedEntity, int fieldIndex, object value )
{
bool toReturn = true;
switch((OrderFieldIndex)fieldIndex)
{
case OrderFieldIndex.OrderId:
// id is valid if the value is > 0
toReturn = ((int)value > 0);
break;
default:
toReturn = true;
break;
}
return toReturn;
}
Entity validation
The following example illustrates a CustomerValidator class which checks if the BillingAddress or VisitingAddress references to an AddressEntity are indeed set to a value and throws an ORMEntityValidationException if this isn't the case. The exception makes sure any transaction currently in progress will be terminated. The validator is injected into all customer entities using Dependency Injection.
[DependencyInjectionInfo(typeof(CustomerEntity), "Validator",
ContextType = DependencyInjectionContextType.Singleton)]
public class CustomerValidator : ValidatorBase
{
/// <summary>
/// Method to validate the containing entity right before the save sequence for the entity will start.
/// LLBLGen Pro will call this method right after the containing entity is selected from the save queue
/// to be saved.
/// </summary>
/// <param name="involvedEntity">The involved entity.</param>
public override void ValidateEntityBeforeSave( IEntityCore involvedEntity )
{
CustomerEntity toValidate = (CustomerEntity)involvedEntity;
if( toValidate.BillingAddress == null )
{
throw new ORMEntityValidationException( "BillingAddress can't be null", toValidate );
}
if( toValidate.VisitingAddress == null )
{
throw new ORMEntityValidationException( "VisitingAddress can't be null", toValidate );
}
base.ValidateEntityBeforeSave( involvedEntity );
}
}
IDataErrorInfo implementation
EntityBase and EntityBase2 implement the .NET interface IDataErrorInfo. Two methods have been added to the entities: SetEntityError and SetEntityFieldError, which allows external code to set the error of a field and/or entity. If append is set to true when calling SetEntityFieldError, the error message is appended to an existing message for that field using a semi-colon as separator.
Entity field validation, which is triggered by SetNewFieldValue(), sets the field error if an exception occurs or when the custom field validator fails. The error message is appended to an existing message.