Using this.Context inside BeforeSaveEntity
Asked Answered
B

1

6

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?

Brownfield answered 25/1, 2013 at 8:21 Comment(0)
P
10

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.

1. Data integrity and security

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.

2. Cross-entity validation

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.

Conclusion

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.

Pyosis answered 27/1, 2013 at 8:21 Comment(3)
Your answer is very thorough, but I'm still missing something. The relationship between ListTodo and UserProfile seems to be an application foreign key, not a database one. Is that to do with Breeze?Marmion
No. That is just an example that tumbled out of Microsoft's original example. Take a look at NorthwindEntitySaveGuard in DocCode sample (it's in the DocCode.DataAccess.EF project); focus on the ReadContext. FWIW, the "SaveGuard" technique is at "level 2" in server-side validation sophistication. "Level 3" would be a "validation rules engine" approach which awaits a future demonstration.Pyosis
What if Breeze exposed a BeforeCommitEntities hook? I can see this being more useful for the cross-validation scenario: Breeze would have already completed loading entities that it needs to in order to satisfy the changes supplied to the SaveChanges method, I would then be able to load extra entities to perform the cross-entity validation without causing conflict with Breeze loading entities from the database.Phlox

© 2022 - 2024 — McMap. All rights reserved.