If I have defined next in map file (1-to-Many relationship between Units and Machines):
this.HasRequired(t => t.Unit)
.WithMany(t => t.Machines)
.HasForeignKey(d => d.UnitId);
When I add new entity, Machine.UnitOfMeasurement = null, sp why does DbContext return no validation problem when calling GetValidationErrors and what can I do to detect them. If I allow EF to try to update, it will return meaningless message to user, like foreign key reference error, while I can extract meaningful information to user from DbEntityValidationResult (ex property name that issued a validation error).
I am using IDataErrorInfo for validation rules, not Attributes.
EF validation does not validate navigation properties. The reason for this is that a navigation property may be null not because a related entity does not exist but because lazy loading is enabled and the related entity is just not loaded. Also, even if the navigation property is null you might have set the corresponding foreign key property which basically means that relation exists but the navigation property has not been updated yet. Also note that forcing loading related entities would mean that validation brings your entire database to memory (after loading related entities they will be validated what would result in loading related entities of newly loaded entities and so on) - you don't want this to happen (EF actually turns off lazy loading during validation to prevent from this).
EF Validation does not support IDataErrorInfo - only validation attributes and IValidatableObject.
Related
A few weeks ago, I've asked how to save Many-To-Many associations with breeze.
Ward Bell came up with this nice solution: breeze: many-to-many issues when saving
I've implemented his solution and it works really well. One issue I've come up with recently though, is how to track errors ?
Taking Ward's example, we manipulate UserRoleVm instances. Therefore validationErrorsChanged will not be triggered for this property.
How could I use breeze to raise an error if say, the parent entity does not have at least one UserRoleVm entity in its collection ?
The UserRoleVm is a regular JavaScript object. It is not a Breeze entity and so does not participate in the Breeze validation support. There is no obvious way to make it do so (at least not obvious to me). Almost anything I can dream up would be more complicated than writing traditional, view-based validation.
What kind of validation do you need? In the example that I put together, the user can only add and remove roles (the equivalent of super powers). There is no way the user can touch any value of the corresponding mapping entity (which may not even exist yet).
When I turn my imagination loose, I speculate about the rules governing how many roles the user can have or whether certain combinations of rule are allowed ... or disallowed. Is that what you mean?
If I had such rules, I'd build validation logic into the outer ViewVM (not the UserRoleVMs) ... the VM that supervises the user's actions. This logic would be quite apart from the Breeze validation logic that you register in metadata ... the validation rules implemented by Breeze inside each entity's EntityAspect.
Ultimately, I would have Breeze validations too ... probably entity validations on the parent User entity type ... so that I could guard against an actual attempt to save an invalid UserRole combination.
But such Breeze validation rules wouldn't kick in until you tried to save. While the user is working with "item VMs" (the UserRoleVms), the validation rules would be defined and implemented separately by the ViewVM in good old vanilla JavaScript.
Such is my thinking at the moment.
Following Ward's advice, I have:
-added the following code for forcing User entity's state to be modified whenever a UserRoleVM is added or removed:
$scope.user.entityAspect.setModified();
-added a custom validator for validating the UserRoles collection on the User entity:
function notEmptyCollectionValidator() {
var name = "notEmptyCollectionValidator";
var validator = new breeze.Validator(name, function (value) {
if (!value || value.length === 0) {
return false;
} else {
return true;
}
});
return validator;
}
breeze.Validator.registerFactory(notEmptyCollectionValidator, 'notEmptyCollectionValidator');
var entityType = metadataStore.getEntityType('User');
entityType.getProperty('userRoles').validators.push(breeze.config.functionRegistry['Validator.notEmptyCollectionValidator']());
Now when I hit the save button, the validation occurs on the userRoles collection. If no userRole was selected, I get a validation error and I show a * next to the control in th UI.
Obviously, that does not work for OnChange validation. I don't know yet how I'm going to achieve that.
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.
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.
I'm trying to create a POCO proxy for an entity already known to be in the database--similar to the nHibernate Session.Load().
I'd like to set the ID value and then if any other properties are accessed on the entity, the other properties are lazy-loaded--again, similar to the nHibernate Session.Load()
I've tried creating a proxy with ObjectContext.CreateObject(), setting the ID value, and attaching it to the context. But the properties don't lazy-load when accessed. The navigation properties, however, will lazy-load just fine.
I'd like this functionality for cases where I need an entity and know the ID, but don't want to force an extra database hit to load the entity. I also want to ensure that the properties can be loaded in the event that they are needed.
Is this possible with Entity Framework 4?
No it is not possible. The reason is that Entity framework supports only lazy loading of navigation properties. You cannot lazy load scalar or complex properties. You must load the entity from database to get them filled.
This happens in ASP.NET MVC 2, .NET 4 (EF 4). My Address entity has a reference to the Post reference. Zip is the primary key of the Post entity. Another property in Post entity is CityName. In my views I allow users to change the CityName for the address which automatically (via jquery) loads up the corresponding Zip and stores it inside a hidden field.
When posted, both values are posted fine and binded to the Address's Post reference. But UpdateModel() fails to update them. It says that the Zip is part of the entity's Entity Key and cannot be changed.
I would gladly load up the Post entity by the new Zip and manually assign it to the existing Address but for all other properties I stall want to rely on UpdateModel().
How can I achieve that? One would think that in EF4 stuff like this has been resolved..
By default the entity framework generated classes put restrictions on changing primary key values. This is good. You shouldn't change a PK for any reason at all. Changing PKs outside of add scenarios has pretty huge ramifications for state tracking and the general health of your system.
To solve this problem you want to tell UpdateModel not to update your primary keys using the exclude parameter.
This is a little out there but I have a customer object coming back to my controller. I want to just reconnect this object back to the database, is it even possible? I know there is a datacontext.customers.insertonsubmit(customer), but is there the equivalent datacontext.customers.updateonsubmit(customer)???
This is what I don't like about LINQ-to-SQL.
It generally works fine if you're querying and updating in the same scope, but if you get an object, cache it, and then try to update it later, you can't.
Here's what the documentation says:
Use the Attach methods with entities that have been created in one DataContext, and serialized to a client, and then deserialized back with the intention to perform an update or delete operation. Because the new DataContext has no way of tracking what the original values were for a disconnected entity, the client is responsible for supplying those values. In this version of Attach, the entity is assumed to be in its original value state. After calling this method, you can then update its fields, for example with additional data sent from the client.
Do not try to Attach an entity that has not been detached through serialization. Entities that have not been serialized still maintain associations with deferred loaders that can cause unexpected results if the entity becomes tracked by a second data context.
A little ambiguous IMHO, specifically about exactly what it means by "serialized" and "deserialized".
Also, interestingly enough, here's what it says about the DataContext object:
In general, a DataContext instance is
designed to last for one "unit of
work" however your application defines
that term. A DataContext is
lightweight and is not expensive to
create. A typical LINQ to SQL
application creates DataContext
instances at method scope or as a
member of short-lived classes that
represent a logical set of related
database operations.
So, DataContexts are intended to be tightly scoped - and yet to use Attach(), you have to use the same DataContext that queried the object. I'm assuming/hoping we're all completely misunderstanding what Attach() is really intended to be used for.
What I've had to do in situations like this is re-query the object I needed to update to get a fresh copy, and then do the update.
The customer that you post from the form will not have entity keys so may not attach well, also you may not have every field of the customer available on the form so all of it's fields may not be set.
I would recommend using the TryUpdateModel method, in your action you'll have to get the customer from the database again and update it with the form's post variables.
public ActionResult MySaveAction(int id, FormCollection form)
{
Customer updateCustomer = _Repository.GetCustomer(id);
TryUpdateModel(updateCustomer, "Customer", form);
_Repository.Save(updateCustomer);
}
You will have to add in all your own exception handling and validation of course, but that's the general idea.
You want to use the attach method on the customers table on the data context.
datacontext.customers.Attach(customer);
to reconnect it to the data context. Then you can use SubmitChanges() to update the values in the database.
EDIT: This only works with entities that have been detached from the original data context through serialization. If you don't mind the extra call to the database, you can use the idiomatic method in ASP.NET MVC of retrieving the object again and applying your changes via UpdateModel or TryUpdateModel as #Odd suggests.