I was looking for a good way to organize validation rules within BeforeSaveEntity method and I have found this comment in the file: TodoContextProvider.cs within the project: BreezeMvcSPATemplate:
// A second DbContext for db access during custom save validation.
// "this.Context" is reserved for Breeze save only!
Why this.Context can not be used?
Excellent question. The answer isn't obvious and it's not easy to cover briefly. I will try.
The EFContextProvider
takes the save data from the client and (ultimately) turns these data into entities within the EFContextProvider.Context
. When the save is approved, the EFContextProvider
calls the SaveChanges
method on this EF Context
and all of its contents are saved as a single transaction.
There are two potential problems.
Client data can never be fully trusted. If you have business rules that limit what an authorized user can see or change, you must compare the client-derived entity to the corresponding entity from the database.
An EF Context
cannot contain two copies of the "same entity". It can't hold two entities with the same key. So you can't use the EFContextProvider.Context
both to fetch the clean copy from the database and to hold the copy with changes.
You'll need a second Context
to get the clean copy and you'll have to write logic to compare the critical values of the entity-to-save in the EFContextProvider.Context
with the values of the clean entity in the second Context
.
Many validation do not require comparison of values with a clean entity.
For example, the out-of-the-box System.ComponentModel.DataAnnotations
attributes, such as Required
and MaxLength
are simple data validations to determine if an entity is self-consistent. Either there is a value or there is not. The value is less than the maximum length or it is not. You don't need a comparison entity for such tests.
You could write your own custom System.ComponentModel.DataAnnotations
attributes that compare data values within a single entity. You might have a rule that says that order.InvoiceDate
must be on-or-before order.ShipDate
. that is also a self-consistency test and you won't need a comparison entity for that one either.
If these are the only kinds of validation you care about - and you're using an EF DbContext
- you can let EF run them for you during its save processing. You won't need a second Context
.
But cross entity validations are another story. In a cross-entity validation, entity 'A' is valid only when some condition is true for entity 'B' (and perhaps 'C', 'D', 'E', ...). For example, you may require that an order item have a parent order that is already in the database.
There is an excellent chance that the parent order is not in the EFContextProvider.Context
at the time you are validating the order item.
"No problem," you say. "I'll just navigate to the parent with someItem.Order
."
No you cannot. First, it won't work because lazy loading is disabled for the EFContextProvider.Context
. The EFContextProvider
disables lazy loading mostly to break circular references during serialization but also to prevent performance killing "n+1" bugs on the server.
You can get around that by loading any entity or related entities at will. But then you hit the second problem: the entity you load for validation could conflict with another entity that you are trying to save in this batch.
The EFContextProvider
doesn't populate its Context
all at once. It starts validating the entities one-by-one, adding them to the Context
as it goes.
Continuing our example, suppose we had loaded the parent order for someItem
during validation. That order is now in EFContextProvider.Context
.
The save process continues to the next entity and ... surprise, surprise ... the next entity happens to be the very same parent order. The EFContextProvider
tries to attach this copy to the Context
which already has a copy (the one we just loaded) ... it can't.
There's a conflict. Which of the two orders belongs in the EFContextProvider
? The clean copy we just loaded for validation purposes ... or the one that came from the client with modifications to be saved?
Maybe you think you know the answer. Maybe I agree. But the fact is, the EFContextProvider
throws an exception because there is already an order with that key in the Context
.
If all your validations are self-consistency checks, the EFContextProvider.Context
is all you need. You won't have to create a second Context
But if you have data security concerns and/or business logic that involves other entities, you need a second Context
... and you'll need sufficient EF skills to use that Context
.
This is not a limitation of Breeze or the Entity Framework. Non-trivial business logic demands comparable server-side complexity no matter what technology you choose. That's the nature of the beast.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With