I extended my server entity with some properties in the client side .
When getting data from the query I really see these properties in the result filled with the proper values .
When I change a value of an extended properties the manager doesn't track this change .
When I call manager.rejectChanges() no action is happen , I debugged the code and I see in the entityAspect.entityState ("Unchaged") although I modified the property.
If I modify a property comes from the server entity every thing is ok.
Here is my Product entity in the server :
public class Product
{
public string Code {get;set;}
}
I extended the product in the client side with some others :
var Product = function () {
this.kind = ko.observable();
};
breeze.metadataStore.registerEntityTypeCtor("Product", Product);
After the query I get both field (Code , Kind) , if I change Code , entity state is modified , I can call manager.rejectChanges and its takes effect, but if I change kind nothing happen , the entity state is "Unchaged".
Any idea why this happen ?
Thanks in advance ...
By "extended" I assume you mean "unmapped" properties which are typically defined in a custom constructor as described in "Extending Entities"
"Unmapped" properties do not map to permanently stored values on the server. Therefore, changes to unmapped properties do not affect EntityState and they are not sent to the server.
Note that the server can supply the value of an unmapped property in the payload of a query and Breeze will set the unmapped property accordingly. This is a way to calculate non-persisted values on the server and transmit them to the entity on the client.
On the client an unmapped property behaves in other respects like a mapped property:
conforms to the syntax of the model library (e.g., it becomes an observable in KO models)
serialized when exported
validation rules apply
raises propertyChange when the value is changed
entity remembers the property's "original value"
rejectChanges() reverts the property to that original value
Related
I'm trying to specify a subset of data to be returned from a database query by Web API 2.
In particular, for this query, I first turn lazy loading on:
db.Configuration.LazyLoadingEnabled = true;
This is because there are potentially infinite levels of children. For example:
Parent: {"name":"Jon","children":[{"name":"Dave","children":["name":"Ed"...
Each person in the above sequence can also have a biography. In the database, the books also have related tables for, let's say, authors, reviewers etc.
As far as I know I can add data annotations to the model to specify which fields to return:
[Key]
= specifies the key which will be returned
[DataMember]
= specifies a property which will be returned
[JsonIgnore]
[IgnoreDataMember]
= specifies a property which will not be returned
[JsonObject(IsReference = true)]
= specifies that the object is being references from another object and therefore related objects should not be loaded
I'm struggling to load the related biographies. The id for the biography is returning, but the biography objects are null. From the parent object, I have annotated both the nullable int and the virtual object references to biography with [DataMember]. In the biography object, I have then specified the id with [Key] and the name with [DataMember] and all other properties with [JsonIgnore]
[IgnoreDataMember]. However the biographies are not being loaded. The db query is returning the items loaded, but they are then being nulled by web api, I assume because of some circular reference in the chain somewhere.
There are about 50 tables linked in some way, do I need to go through them all and add data annotations to everyone - even if I have used an ignore annotation to break the chain? Hoping for a simple solution, but any solution appreciated!
It seems to be working fine that [DataMember] will load a HashSet of related data, which gets instantiated in the constructor, but related databases objects (which are not instantiated in the constructor) do not get loaded.
It seems that the update statement doesn't turn lazy loading on:
db.Configuration.LazyLoadingEnabled = true;
The related items would only be returned if I went into debug mode and loaded the related data when hovering over the object (seems strange), but basically it was staying in lazy loading = false mode.
My solution has been to turn lazy loading on globally and to use data annotations as described above to avoid circular references.
We are parsing the SaveBundle on the server and returning a custom SaveResult. We want to be able to notify the client of additional changed entities as a result of processing the SaveBundle.
For example we have a SaveBundle from the client containing 1 entity to be deleted which when we parse and process on the server we actually delete 2 entities.
As far as we can tell the SaveResult does not contain any properties that would allow us to indicate an entity was 'deleted', rather than say 'modified'.
Is there a way to return additional entity changes through the SaveResult? Or is the only solution to refresh the data by resubmitting a Breeze query client side after the save changes?
I 'think' that if you return the deleted entities with their foreign keys set to null or empty (in the case of non-nullable guids etc.) in the SaveResult then Breeze client-side will detect this and mark them as deleted
I couldn't find anything explicitly in the documentation or the source about this though
here is your answer:
var result = context.SaveChanges(saveBundle);
//create your own EntityInfo object and fill it with the the entity and it's state
var entityInfo = new EntityInfo();
//...
//add it to the result
result.Entities.Add(entityInfo);
//return the result
return result;
Breeze client will then treat that entity like any other entity returned from you normal save proc.
Hope this helps
In a previous question, it was stated that:
"On the client an unmapped property behaves in other respects like a mapped property"
"rejectChanges() reverts the property to that original value"
I'm experiencing the same issue described in that question: EntityManager.rejectChanges() doesn't revert unmapped properties to the original value, while EntityAspect.rejectChanges() does.
In the responses to that question, it was suggested that this was probably due to a coding error. I've made a plunker demonstrating the issue. Is there an error in my code that is causing this?
Edit - Updated Test Case:
test("reject changes reverts an unmapped property - only unmapped property changed", 1, function () {
var store = cloneModuleMetadataStore();
var originalTime = new Date(2013, 0, 1);
var Customer = function () {
this.lastTouched = originalTime;
};
store.registerEntityTypeCtor("Customer", Customer);
var manager = newEm(store);
// create a fake customer
var cust = manager.createEntity("Customer", { CompanyName: "Acme" },
EntityState.Unchanged);
var touched = cust.lastTouched();
// we change only the unmapped property (uncomment the next line and the test will pass)
//cust.CompanyName("Beta");
cust.lastTouched(new Date(touched.getTime() + 60000));
//cust.entityAspect.rejectChanges(); // roll back name change
manager.rejectChanges(); // would have same effect. Obviously less granular
ok(originalTime === cust.lastTouched(),
"'lastTouched' unmapped property should be rolled back. Started as {0}; now is {1}"
.format(originalTime, cust.lastTouched()));
});
you can see that in this environment, the test passes with entityAspect.rejectChanges(), but fails with manager.rejectChanges(). if a mapped property is changed along with the unmapped property, the test passes.
Updated answer 2/2/2014
Ok, what you have discovered is actually by design. And.. thanks for the test above, ( it makes understanding the issue much easier).
The issue here is that changes to unmapped properties do NOT change the EntityState of the entity. This decision was made because these changes do not actually ever need to be persisted to the server ( because there is nowhere to put them).
The second issue is that when calling EntityManager.rejectChanges we only process entities that have an Added, Modified or Deleted EntityState. Since an entity whose ONLY change is to an unmapped property does not fall into this category, the entity level rejectChanges call is never made.
There are several workarounds.
1) Call EntityAspect.setModified() after any change to an unmapped property. You can try this on the test above to see that it works. ( A slightly more complicated version of this is to use the EntityManager events to do this automatically).
2) Change any mapped property whenever you change an unmapped one.
3) Write your own EntityManager.rejectChanges that calls EntityAspect.rejectChanges on every entity in the EntityManager instead of just the 'changed' ones. This does have perf implications so I don't really recommend it unless you have a very small cache.
Please feel free to suggest an alternative that makes sense to you. We have considered adding settings to allow you to configure the treatment of unmapped properties. ( among these is whether an unmapped property change will change the entity state).
I can't repro this... and reviewing the code, the EntityManager.rejectChanges simply calls into the EntityAspect.rejectChanges for all entities within the manager.
So there are a couple of possibilities
1) The EntityAspect that you are NOT seeing rejectChanges work properly with is not actually "attached" to the EntityManager.
2) You are not actually comparing the behavior of "rejectChanges" on the SAME entity in both cases.
Take a look at the test cases within the DocCode sample in the Breeze zip. These tests require no UI and are typically very short. If you can paste a simple test here that fails in that environment, I will take a look. Having a UI involved often clouds the picture.
Using a custom EFContextProvider, I want to check which properties have been modified on an entity before it saves, so that I can implement:
Security: The client has permission to change only certain properties of an entity.
Auditing: Whenever certain properties are changed, the change needs to be logged.
There are suggestions on SO to use OriginalValuesMap to determine the modified properties, see here and here. If the original value differs from the new value, the property has been modified. However, these original values are supplied by the client, and thus can be forged to match the new values, bypassing this check.
The first SO question I linked suggests this is not an issue, because if the original values are forged in such a way, those properties won't be saved anyway:
For any other "unchanged" property, which we are not using in any way, we don't need to worry if it has been tampered with because, even if it has, the tampered value will not be persisted to the database
This is untrue however, as long as all modified properties on the entity have their original values forged. For example, the following code will bypass server-side security checks based on OriginalValuesMap and still save to the database:
manager.fetchEntityByKey('Employee', 42).then(function (result) {
var employee = result.entity;
employee.Salary(1000000); // do you think HR will notice?
delete employee.entityAspect.originalValues.Salary;
return manager.saveChanges();
});
When Breeze .NET receives the entity, it adds the entity to an Entity Framework context in Modified state, and with no properties marked as modified, Entity Framework's behaviour is to save all the supplied property values to the database.
IMO this is a security bug in EFContextProvider.HandleModified, where it overrides the EF entity state to Modified (there is even a comment in that method warning not to do so). In any case, what is the correct way to determine which properties have changed and are about to be saved?
In your Context intercept Save and check if it is legal save or not. For the sake of explanation, let's say you want to save entity of type RestrictedClass and you defined table RestrictedClasses which imitates table in your database.
public override int SaveChanges()
{
foreach (
var entry in
this.ChangeTracker.Entries()
.Where((e => (e.State == (EntityState) Breeze.WebApi.EntityState.Modified))))
{
if (entry.Entity.GetType() == typeof(RestrictedClass))
{
var entity = entry.Entity as RestrictedClass;
var originalEntities = RestrictedClasses.Where(e => e.Id = entity.Id).toList();
if (originalEntities.Count == 0) continue; // user is trying to add, illegal since it says it's modified, you do different check for EntityState.Added
var originalEntity = originalEntities[0]; // there should be only one, unique ID
//.... now you check differences between entity and originalEntity and decide whether it's legal or not based on user role.
When I use criteria queries, the result contains array list of lazy initialized objects. that is, the list has values with handler org.codehaus.groovy.grails.orm.hibernate.proxy.GroovyAwareJavassistLazyInitializer.
This prevent me from doing any array operation (minus, remove etc) in it. When I use, GORM methods, I get array list of actual object types. How can I get the actual objects in criteria query?
The code is listed below.
availableTypes = Type.withCriteria() {
'in'("roleFrom", from)
'in'("roleTo", to)
}
availableTypes (an array list) has one value , but not actual object but value with a handler of GroovyAwareJavassistLazyInitializer
availableTypes (an array list) has values with type Type
availableTypes = Type.findByRoleFrom(from)
---------- Update ----------
I did further troubleshooting, and this is what I found. Probably the above description might be misleading, but I kept it in case it helps.
When using findAllBy for the first time, I get proxy objects rather than the actual instance. Then, I invoke the method through an ajax call, the actual instance is loaded (anything to do with cache loading??). When I refresh the page, it again loads the proxy
def typeFrom = Type.findAllByParty(partyFrom)
there is another use of findAllBy in the same method, which always returns actual instances.
def relFrom = Relation.findAllByParty(partyFrom)
When compared the two classes, the attribute 'party' of class Roles is part of a 1-m relation. like
class Role {
RoleType roleType
LocalDate validFrom
LocalDate validTo
static belongsTo = [party : Party ]
...
}
I know if I do statement like Party.findAll(), the role instances would be proxy till they access. But, when using gorm directly on the class (Role), why I am getting the proxy objects ???
thanks for the help.
thanks.
Turns out are a couple of possible solutions which I came across but didn't try, such as
Overloading the equals method so that the proxy and the domain
object use a primary key instead of the hashCode for equality
Using a join query so that you get actual instances back and not proxies
GrailsHibernateUtil.unwrapProxy(o)
HibernateProxyHelper.getClassWithoutInitializingProxy(object)
One solution that worked for me was to specify lazy loading to be false in the domain object mapping.
History of this problem seems to be discussed here: GRAILS-4614
See also: eager load