Generated code - Validation per field or per entity

Preface

Business logic can be implemented in a variety of ways. Roughly you can divide the business rules in three groups:

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.

note Note:
For people migrating from LLBLGen Pro v1.0.2005.1 or earlier, it's key to understand that in V3 (which was introduced in v2.0) there's just 1 interface for both field and entity validation: IValidator, there are no longer two interfaces used. The reason for this is that by using 1 interface it simplifies writing validation logic and also limits the amount of code to write. Also be sure to read the Migrating your code section to learn more about breaking changes related to validator classes and how to migrate your validation code to the new system.

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.
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, which are None (value 0, an exception will be thrown), Truncate (value 1, the fraction will be truncated to match the scale value) or 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.

Validation logic inside Entity classes

The first of the two ways to add validation logic to the entity 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.

note Note:
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;
}
' VB.NET in partial class or user code region of OrderEntity
Protected Overrides Function OnValidateFieldValue(fieldIndex As Integer, value As Object) As Boolean
	Dim toReturn As Boolean = True
	Select Case CType(fieldIndex, OrderFieldIndex)
		Case OrderFieldIndex.OrderId
			' id is valid if the value is > 0
			toReturn = (CInt(value) > 0)
		Case Else
			toReturn = True
	End Select
	Return toReturn
End Function

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. Validate was the entity validate method in v1.0.2005.1 and earlier of LLBLGen Pro.

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.

In v1.0.2005.1 and earlier, LLBLGen Pro generated for each entity a validator class, called entitynameValidator. This isn't the case anymore. You can still generate validator classes per entity however, you just have to enable the SD.Tasks.Generic.ValidatorClassesGenerator task in the preset of your choice at Tab 3 of the generator configuration dialog.
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:
  1. 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.
  2. 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.
  3. 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.

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.

note Note:
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;
}
' VB.NET in validator class deriving from ValidatorBase.
Protected Overrides Function ValidateFieldValue( involvedEntity As IEntityCore, fieldIndex As Integer, value As Object) As Boolean
	Dim toReturn As Boolean = True
	Select Case CType(fieldIndex, OrderFieldIndex)
		Case OrderFieldIndex.OrderId
			' id is valid if the value is > 0
			toReturn = (CInt(value) > 0)
		Case Else
			toReturn = True
	End Select
	Return toReturn
End Function

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.

// C#
[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 );
	}
}
' VB.NET
<DependencyInjectionInfo(GetType(CustomerEntity), "Validator", _
    ContextType := DependencyInjectionContextType.Singleton)> _
Public Class CustomerValidator
	Inherits 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 Overrides Sub ValidateEntityBeforeSave(involvedEntity As IEntityCore)
		Dim toValidate As CustomerEntity = CType(involvedEntity, CustomerEntity)
		If toValidate.BillingAddress Is Nothing Then
			Throw New ORMEntityValidationException( "BillingAddress can't be null", toValidate )
		End If
		If  toValidate.VisitingAddress Is Nothing Then
			Throw New ORMEntityValidationException( "VisitingAddress can't be null", toValidate )
		End If
		MyBase.ValidateEntityBeforeSave( involvedEntity )
	End Sub
End Class

IDataErrorInfo implementation

The .NET interface IDataErrorInfo is now implemented on EntityBase and EntityBase2. 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.

LLBLGen Pro v3.0 documentation. ©2010 Solutions Design