- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Validation and Exceptions
Joined: 26-Oct-2003
Another comment on how validating works:
In your documentation you make the following statement:
Exception strategy Exceptions are considered 'exceptional' in the runtime libraries and generated code. This means that exceptions are used to illustrate an exceptional state which requires fixing. They're not used to report a semantic failure. An example of a semantic failure is the save of a single entity without recursion. This save can fail due to various reasons. The save logic in the template groups will return false in that case and not an exception: the action simply failed. If an error occurs in the code, due to input or a code error, the code does throw exceptions or will simply bubble up the exception. If the exception is an exception which should be handled locally, the runtime libraries or the generated code will try to handle the exception and only if nothing helps it will be passed upwards. This can sometimes mean that the exception is encapsulated in a custom exception to provide more information about the error caught, for example when a query failed, the complete query is included in the exception.
I wondered how this stands up next to the recommend method for implementing IEntityValidator:
Validation per entity: implementing IEntityValidator Validation per entity is not done by default, as an entity has not an implementation of IEntityValidator by default. If you want entity-wide validation functionality, you have to implement IEntityValidator and store an instance of this implementation in the entity object you want entity-wide validation logic being applied to. The generated code performs entity-wide validation, if an IEntityValidator instance is present in the entity, when the entity is saved or when Validate() is called. Validate() is a method of every entity object. It normally returns true, if no IEntityValidator object is present, otherwise it calls the Validate() method of the IEntityValidator instance, passing the complete entity object to it.
**A special exception is defined to signal validation failures: ORMEntityValidationException(). It is recommended that a validation failure throws this exception as it will fail (and thus roll back!) transactions started for recursive saves. The exception is also useful to define a special catch clause for this exception to correct validation failures. **
Implementing IEntityValidator is very simple. Below is an example of IEntityValidator, which implements simple validation logic for the OrderEntity. The implementation as it is given is usable in SelfServicing and Adapter, however only in sourcecode. You can't share a compiled IEntityValidator class which is compiled against SelfServicing classes with an Adapter assembly.
It doesn't seem that using an exception is appropriate given the stated philosophy regarding exceptions. A failed validation seems to me the definition of a semantic failure. Wouldn't it be more appropriate to return a non-empty string in the event of a validation failure (similar to the boolean return value for .Save, but instead containing the failure message?), rather than raising an exceptional exception?
The only scenario I could come up with that may require a custom exception being raised is that of the transactional rollback. Perhaps such a signal is required to trigger a rollback? However, isn't that using exceptions as a control-of-flow device?
It's probably not changeable now, at any rate, but I was curious as to the rationale. Thanks!
Jeff... Thanks
jeffreygg wrote:
Another comment on how validating works:
(exceptions are exceptional, but with IEntityValidator you should throw an exception when it fails)
It doesn't seem that using an exception is appropriate given the stated philosophy regarding exceptions. A failed validation seems to me the definition of a semantic failure. Wouldn't it be more appropriate to return a non-empty string in the event of a validation failure (similar to the boolean return value for .Save, but instead containing the failure message?), rather than raising an exceptional exception?
The reason for this is the one you give yourself:
The only scenario I could come up with that may require a custom exception being raised is that of the transactional rollback. Perhaps such a signal is required to trigger a rollback? However, isn't that using exceptions as a control-of-flow device?
There is no other way. If you're deep down in a recursive save, the current routine context is not the transaction controller, i.e.: it doesn't have the authority to rollback the transaction, that's the upper routine, which started the transaction. To signal that routine to rollback the transaction, it's best to throw an exception, otherwise you end up in a misery like with the COM return values (the HRESULTs). The case with transactions is the exceptional (pun intended) case in where I'd use exceptions to control the flow of the program.
Joined: 26-Oct-2003
Perfect. Exactly what I needed to know. In general, let's say if some sort of transaction rollback wasn't necessary, would you still use exceptions to signal validation failures? I have to admit it's nice, and the resulting code is pretty clean, but I would think that if you start muddying the water with validation errors as exceptions, it's a slippery slope to start considering everything to be an exception.
Jeff...
jeffreygg wrote:
Perfect. Exactly what I needed to know.
In general, let's say if some sort of transaction rollback wasn't necessary, would you still use exceptions to signal validation failures? I have to admit it's nice, and the resulting code is pretty clean, but I would think that if you start muddying the water with validation errors as exceptions, it's a slippery slope to start considering everything to be an exception.
It's very simple: why do you validate and what should be done when the validation fails? You can decide what to do yourself. For example, you can test if the entity to validate is in a transaction by calling ParticipatesInTransaction (cast your entity to ITransactionalElement). If true is returned, you should thrown an exception, otherwise you can decide to return false.
(Edit): working on the COM+ support in adapter, I noticed there is a bug in adapter: ParticipatesInTransaction always returns false because the ContainingTransaction object is set to null (as it isn't used in Adapter, the functionality is embedded in teh DataAccessAdapter class itself). This is of course fixed in the v1.0.2004.1 lib.