I'm having a weird (or at least unexpected) problem with Breeze. I have an EF back-end view model to which I send some parameters and it returns some data. I use the withParameters option to do this. The back-end does a lot of Includes and projection and returns the data I want to display in a set of custom view model entities (i.e., not database entities). One of the parameters is a list of keys for which I want to display data.
The keys identify which children of the parent entities I want to retrieve, though I am retrieving a list of parents (e.g. keys [1,2] mean it should get all Parent entities with a Children list property that themselves have a ToyId property that has a value in keys and those Child entites). In other words, the structure is like Parent.Child[] and Child.ToyId and I want to get parents with children that have certain toys and those children themselves (but not other children). Both parent and child sets are large so I do this in SQL via EF (which was an adventure in itself).
Anyway, the problem happens after I select two keys and get the data and then de-select one of the keys. The first query, getting the data for two keys, works as expected. On the second executeQuery's callback, I get the same data as the previous query, meaning it's as if I never de-selected the key. I've verified that Breeze hits the back-end with the correct keys parameter value and the back-end returns just the data I want, but it seems that Breeze is ignoring the data from the back-end or performing a union on the result set from the back-end and its cached entities (for both keys) and sending that union as results into the callback instead of just what the server returned. Is this expected behavior? Unfortunately everything is written this way. We (working on this, our first project using Breeze) all assumed it would only return what the server sent when not using executeQueryLocally, so it will be a big deal to refactor. Sigh.
I tried some where predicates which didn't work and don't see how projecting on the Breeze side would help either. I thought maybe it saw the query as identical so it returned cached data as a shortcut, so I added a where('Parent.Children', 'any', 'ToyId', 'in', keys), but that didn't work, it still brings in the de-selected results.
The only way I've found to get around this is via queryManager.clear() before I make any of the queries, and I suspect doing a noTracking query might work also (albeit without actual entity objects). I thought about converting the keys parameter into a where filter and sourcing it from there instead of the keys parameter, in case that would tell Breeze to only show the back-end data.
Is there a "correct" way of getting back only the data the server sends in the callback?
("The callback" meaning the function passed into executeQuery.then(...))
As you've noticed, Breeze merges the query results into the cache before calling your callback method. This means that, in the callback, a parent entity will have all of its children that exist in the cache.
The array of entities returned by the query is available in the retrievedEntities property of the saveResult object returned in the callback. You can use this to determine which children were returned from the query.
entityManager.executeQuery(query).then(function(data) {
var parents = data.results;
var ret = data.retrievedEntities;
//... filter ret to get the children
//... update viewmodel with children
});
Related
In TypeORM, if we have a repository called repo, we can call repo.save([entity1, entity2]) to save new entities and/or update existing ones. Repository#save also returns an array of the saved entities.
Is the order of the returned entities guaranteed to match the order that was passed in?
For example, if I call repo.save([newEntity1, newEntity2, newEntity3]) (where each is a new entity without an ID), will I always get back [entity1, entity2, entity3], where each element in the output array corresponds to the respective element in the input array, but with an assigned ID?
In my testing, the order seems to match, but I want to be sure that it's guaranteed.
Repository documentation: https://typeorm.io/#/repository-api
Relevant passage:
save - Saves a given entity or array of entities. If the entity already exist in the database, it is updated. If the entity does not exist in the database, it is inserted. It saves all given entities in a single transaction (in the case of entity, manager is not transactional). Also supports partial updating since all undefined properties are skipped. Returns the saved entity/entities.
It's not clear what guarantees this provides about the order.
Having an entity in cache, that entity has related entities in the form of a top 20.
Now a user action can update the top 20 on the server, and I thus would like to redownload the entire entity. Server sends the correct data with top 20, but in Breeze, I end up with a top 40... And I can't figure out how to avoid this behavior.
Thanks for the tip
Update: I do not use odata webapi and iqueryable, as it offers too much power to clients for my app. So I don't want to use EntityQuery.fromEntityKey, which seems to do what I want. I'd prefer to keep using a "normal" query, to which I add a parameter.
Update 2: To add more clarity as to why I want to prevent merge, when I recompute the top 20, I delete all related entries in the db and recreate them, so they have new Id's. So I am now considering an update, which might actually solve my issue BUT I would still like to know if merge can be prevented.
The Breeze EntityManager caches entities by primary key. So presumably your 2nd query is returning a completely new set of entities with each query. If this is the case, and you really only want the "latest" 20, the simplest fix would be to simply empty the EntityManager cache for this entity type before each query. Something like:
var entities = myEntityManager.getEntities(myEntityType);
entities.forEach(function(e) {
myEntityManager.detachEntity(e);
// or
// e.entityAspect.setDetached();
});
I am not sure what is exactly your qustion, but if you want to have different results on entity queries you may do it by creating 2 different controllers with different action names, or just with different parameters - overloaded methods. Then in your datacontext you can have 2 different queries.
You use .withParameters() propriety on the query to call the controller method with parameters.
Then in the controller method you can query and filter with LINQ, in any way you want. This way you can have different results based on the query/controller you chose to call.
Documentation: http://www.breezejs.com/documentation/querying-depth
I'm sure I've created my own pain here, but I'm struggling to understand the correct sequence of events to manage creating new entities in my scenario.
In my model I have two objects, ObjectA and ObjectB that both inherit from BaseObject, obviously each with their own additional properties.
In my view, as most of the information is the same, I want the user to be able to just select an option as to which one to create. So they fill out SharedProperty1 and SharedProperty2 (which is a collection navigation property), select an option as to if they want an A or B object, and then fill in a final page which has the last object specific properties.
As I didn't know which entity to create until the user had selected this option, I built a an object in the viewmodel to handle this temporary data. As part of that while they are filling out SharedProperty2 (the collection), as they add new ChildObjects, I create them with entityManager.createEntity('ChildObject'). Then when they reach the end, I create either ObjectA or ObjectB entity and add the child entitites (and other properties) and then try and save.
The problem is it never saves correctly, but I get different results depending on which approach I take. So because the user could just abort the new object process, I was creating the ChildObjects with EntityState.Detached because I thought that would be easier if they got thrown away. I realised though that all the entities created in this way get the id key 0. So then I fixed the keys while I was adding the ChildEntites to the parent (either ObjectA or ObjectB), by assigning them decreasing negative numbers (ie: -1, -2, etc). This resulted in some crazy server-side behaviour with only some entities being saved to the db and complaints of conflicting foreign keys.
This also had a bad smell that I hadn't understood this correctly, and I'd made a mess of it. So now I tried just creating the entities normally (ie: without the Detached flag), and they all get their own unique keys (again breeze appears to follow -1, -2, etc), but now when I try to copy them from my temporary viewmodel collection to the parent object collection, I get the error that an entity with this key is already attached. So now I can't even build up the correct model to save.
I still think I've not understood quite correctly how to handle this, so some pointers would be deeply appreciated.
To head off what I suspect will be a question, why I didn't use RejectChanges to handle the entities being thrown away. Basically a user can add a ChildObject (object gets created by breeze entityManager, added to viewmodel collection, bound to UI), and then decide to just remove it again (currently just gets removed from viewmodel collection) before they save their data. If I used reject changes I would throw away other important entites. I think I'm now going to be a good boy and use the proper detach method if someone removes the ChildObject in the view.
If I understand your question correctly, you are trying to create some properties and then add them to a parent objects collection when saving. Correct me if I am wrong, but Breeze not only supports this, but does so very efficiently. Having come from .NET and C# it was very difficult for me to grasp how easy this can be, but this is what I would do if I were you -
var childA = ko.observable();
var childB = ko.observable();
childA(entityManager.createEntity('ChildObject')); // populate your children
childB(entityManager.createEntity('ChildObject')); // populate your children
Then you can edit them in your view, and when you are ready to save simply add them to the collection.
var save = function() {
isSaving(true);
var parent = ko.observable();
return entityManager.getParent(parent, parentId)
.then(setParents)
.fail(catchError);
function setParents() {
childA().parent(parent());
childB().parent(parent());
entityManager.saveChanges()
.then(complete)
.fail(catchError);
function complete() {
isSaving(false);
return Q.resolve(); // Don't know if you have any unresolved conflicts
}
}
};
Basically in this manner we are -
A : Creating the entities
B : Editing them without performing any changes
C : When we call save we are setting their parent navigation property. In my prior ways (be it right or wrong) I would have simply set ParentId(parentId) and let EF figure out how to navigate but (pardon the pun) this is a breeze with Breeze. We could also just as easily pass in a parent observable and not have to go get it from the manager, it just depends on whether we have it already or not.
Another way you could do this if you want to manage the entities separately is to save a single entity at a time with entityManager.saveChanges([childA]) as they are ready. Just pass in an array with a single entity that you want to save. This may be useful if you are working on multiple entities but they aren't all ready for saving and you need to navigate around your app. Unless you call cancelChanges() Breeze will just keep the entity in cache until you are ready to use it again. In this manner, just make a call for entities in the isAdded() state and you can pull 'em back in and edit again.
In the cache, I've got an Entity of type 'Mandate'.
Then I run the following code to fetch a collection of MandateHistory entities, which is then merged by breeze to the corresponding property of the Mandate entity :
function getMandatHistory(mandatId) {
var query = breeze.EntityQuery.from("MandatesHistory")
.where("Mandate.Id", "==", mandatId).orderBy("Id")
.expand("Mandate").skip(offset).take(pageSize).inlineCount(true);
return manager.executeQuery(query.using(service));
}
Note the orderBy clause is respected and the results are properly sorted by Id.
However the items in the collection property of the Mandate entity is NOT sorted. Do I have to do something special here ?
Sorting of the values returnd by collection navigation properties is NOT something that Breeze does. It will sort the results of a query, but if you want to sort, ( and keep sorted), one of the collection properties of an entity you will need to manage that yourself.
I think your best two options are either.
1) Sort before display. i.e. call a sort method on any collection right before you display it. Depending on what MVVM framework you are using, there is often a 'binding' that does exactly this.
2) Subscribe to the Breeze arrayChanged event on the array returned by your navigation property and call sort on the array anytime you see the change event. Note this can get expensive if you subscribe to a lot properties on a lot of entities.
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.