Child navigation properties missing in imported entities in custom initializer - breeze

I have a custom entity definition like:
var Card = function () {};
var cardInitializer = function (card) {
// card.fields is defined in the metadata.
// card._cfields is an in-memory only field
// that breeze will not, and should not, track.
// Thus it is being added in the initializer
card._cfields = card.fields.slice();
};
When the data loads from the server everything is fine. The card.fields array has the corresponding data.
EDITED: Added more info and code of how manager is being set up
But when the data is round-tripped in local storage via .exportEntities and importEntities, the child data defined in the metadata, represented by the property card.fields in this example, is not loaded (the Array has length 0) during the initializer call, though it is subsequently available on the entity after load has completed.
Here is how the manager is being initialized:
var metadataStore = new breeze.MetadataStore();
metadataStore.importMetadata(options.metadata);
var queryOptions = new breeze.QueryOptions( {
fetchStrategy: breeze.FetchStrategy.FromLocalCache
});
var dataService = new breeze.DataService({
serviceName: "none",
hasServerMetadata: false
});
manager = new breeze.EntityManager({
dataService: dataService,
metadataStore: metadataStore,
queryOptions: queryOptions
});
entityExtensions.registerExtensions(manager, breeze);
var entities = localStorage[storage];
if(entities && entities !== 'null'){
manager.importEntities(entities);
}

Wow. You ask for free support from the harried developer of a free OSS product that you presumably value and then you shit on him because you think he was being flippant? And downgrade his answer.
Could you have responded more generously. Perhaps you might recognize that your question was a bit unclear. I guess that occurred to you because you edited your question such that I can see what you're driving at.
Two suggestions for next time. (1) Be nice. (2) Provide a running code sample that illustrates your issue.
I'll meet you half way. I wrote a plunker that I believe demonstrates your complaint.
It shows that the navigation properties may not be wired up when importEntities calls an initializer even though the related entities are in cache.
They do appear to be wired up during query result processing when the initializer is called.
I cannot explain why they are different in this respect. I will ask.
My personal preference is to be consistent and to have the entities wired up. But it may be that there are good reasons why we don't do that or why it is indeterminate even when processing query results. I'll try to get an answer as I said.
Meanwhile, you'll have to work around this ... which you can do by processing the values returned from the import:
var imported = em2.importEntities(exported);
FWIW, the documentation is silent on this question.

Look at the "Extending Entities" documentation topic again.
You will see that, by design, breeze does not know about any properties created in an initializer and therefore ignores such properties during serialization such as entity export. This is a feature not a limitation.
If you want breeze to "know" about an unmapped property you must define it in the entity constructor (Card)... even if you later populate it in the initialized function.
Again, best to look at the docs and at examples before setting out on your own.

Related

use ToListAsync() with navigation property

I am using ASP.NET MVC with Entity Framework.
I have an entity called "AllUserData".
I have a second entity called "Genres". Each row in the table is a genre.
These two entities have a one-to-many relationship. So in the class definition of the "AllUserData" class, I have
public virtual ICollection PreferredGenres { get; set; }
I am able to successfully read the genres preferred by each user using
AllUserData aud = db.AllUserData.Single(b => b.UserId == currentUserId);
var chosengenres = aud.PreferredGenres.ToList()
However I cannot use
AllUserData aud = db.AllUserData.Single(b => b.UserId == currentUserId);
var chosengenres = await aud.PreferredGenres.ToListAsync()
Visual Studio says "ICollection does not contain a definition for 'ToListAsync' and the best extension method overload 'QueryableExtensions.ToListAsync(IQueryable)' requires a receiver of type 'IQueryable'.
Why is this happening? The only difference between the two was that in one case I used ToList() and in the other I used ToListAsync().
Can async methods not be used with navigation properties? In a real life application there are many cases where there are relationships between various entities; can asynchronous methods not be used when accessing properties using these relationships? Is there some way around this? I'd rather do things asynchronously if possible.
Your navigation property would have been declared as an ICollection, standard for Entity properties -- the compiler won't be pleased.
However, you can get an IQueryable with AsQueryable or with another Select() (if that makes sense for your needs) between the navigation collection and the ToListAsync().
ToList() can operate on IEnumerable (which includes ICollection and IQueryable). ToListAsync() has only been made to work with IQueryable, hence the message from VS. The "why" may lie in the implementation details.
Ok, it seems entity framework doesn't allow for calling the navigation property asynchronously in this manner.
What worked for me is earger loading the navigation property in the beginning. (Thanks to user daf for sending me in the correct direction).
So instead of
AllUserData aud = await db.AllUserData.SingleAsync(b => b.UserId == currentUserId);
var usergenres = await aud.PreferredGenres.ToListAsync();
which does not work, I can instead do this :
AllUserData aud = await db.AllUserData.Include(p=>p.PreferredGenres).SingleAsync(b => b.UserId == currentUserId);
var usergenres = aud.PreferredGenres.ToList();
This way the first query asks to pull all necessary information from the database through eager loading with .Include(), and does so asynchronously due to the user of .SingleAsync(). The next statement uses .ToList(), but shouldn't make a second trip to the database since the data is already eager loaded, so it doesn't matter that it is async. The whole operation is now async.
I also realized this same question has been asked previously on StackOverflow; it is a little difficult to find depending on what search terms you used: Entity Framework Designer First get navigation property as Tasks. The selected answer provides 4 different solutions, out of which the first one is similar to what I am using now.
It also appears possible to designate navigation properties as async when defining the entity class- see the section titled "Async Lazy Loading" on this page: here The sample snippet demonstrates how to do this for a single navigation property; I don't know if it can be done for an ICollection navigation property as well, which is what I need, but I didn't do any further digging either.

What is the right way to append entity from one manager to another in breeze?

What is the right way to append entity from one manager to another?
Straight forward attempts result in overwriting entities or in warning about the same entity key...
var entity = em1.getEntities()[0];
em1.deattachEntity(entity);
// assume em2 already has entities
em2.attachEntity(entity);
I believe there should be some in-build functionality for appending entries to another non empty manager or for generating an unique key for selected manager.
Any suggestions?
UPDATE:
I did read the documentation and tried to do it via exportEntities and exportEntities:
var entity = em1.getEntities()[0];
var export = em1.exportEntities([entity], false);
//here em2 already has entity with the same key as entity that I want to add
var import = em2.importEntities(export, { mergeStrategy: breeze.MergeStrategy.Disallowed });
This gives me an error: A MergeStrategy of 'Disallowed' prevents Picture:#Macaw.Whitelabel.WebAPI.Models--1 from being merged
I really don't understand how to append entities....
UPDATE2:
I did discover that manually assigning id of the attached entity solves the problem and error disappears.
Is there a way to make it not manually but using breeze?

breeze rejectChanges issue with unmapped properties

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.

Extending entities on a per view basis with breeze.js in SPA

Trying to figure out how to extend entities that I query from breeze.js on a per-view basis in a single page application. Right now breeze is acting as the gate-keeper when it comes to extending (a.k.a materializing) them and I’m wondering what other options are available to allow me to do this. I initially started with knockout’s mapping plugin but found that it refused to handle child collections for some reason so I moved to using breeze’s constructor function and initializer methodology. The problem with this is that you can only define one custom "model" for an entity. I am looking for approaches that would allow a custom "model" of an entity on a per-view basis. I’ve already ruled out multiple managers. Querying meta-data multiple times is a huge unnecessary hit just to get this working.
This diagram visualizes what I’m trying to achieve. Both View 1 and View 2 ultimately query Entity B and both views require their own specific customization of the "model" of Entity B. Since View 1 loads first it’s custom "model" of Entity B "wins" and View 2 doesn’t have the opportunity to customize it. When View 2 eventually runs it’s query, any entities of type B that were already loaded by View 1 will have the custom "model" that View 1 defined which will make View 2 explode during binding. Any entities not already loaded by View 1 will now have View 2's custom "model" which would eventually crash View 1 if it could even get that far down the road. See this post.
My thought was to manually create my own custom "model" for each view that has an Entity observable and I could then iterate over every entity returned from a breeze query and new up this custom "model" and pass in the current item, assigning it to the Entity property. I don't really want to do this because I now have I'll have tons of iteration code everywhere and I'd much rather use knockout's mapping plugin. Pseudo code:
function view1EntityBModel(entity) {
var self = this;
self.Entity = ko.observable(entity);
self.myCustomProperty = ko.observable();
...
}
function view2EntityBModel(entity) {
var self = this;
self.Entity = ko.observable(entity);
self.isExpanded = ko.observable(false);
...
}
I was wondering if there are any other solutions available to achieve this same goal?
Or even better does anyone know how to make the knockout mapping plugin working on child collections?
I think the problem here is that by the time the mapping plugin gets a-hold of the breeze data the Children collection has already been converted into an observable array and the mapping plugin doesn't know that it needs to "call" the Children() property in order to get back a list.
var categoryMapper = {
create: function (options) {
return new categoryModel(options.data);
},
Children: { // this doesn't fire for the children
create: function (options) {
return new categoryModel(options.data);
}
}
}
function categoryModel(data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
}
Guessing that you've moved on by now, but thought I'd offer a recommendation for others in a similar position.
Our solution to a similar situation borrows from the breeze.js TempHire sample solution which implements a client side repository/uow pattern. The solution uses an EntityMananagerProvider to manage multiple EntityManagers. The EntityMananagerProvider makes a single call for metadata, which is then used to create new child EntityManagers - satisfying your concern regarding multiple metadata calls. You can then use custom models/uow/repositories to extend the child manager for specific views.

Explicitly retrieving metdataStore

I have a view model that will always create a new entity (Score); it doesn't need to wait for nor query the repository to know this.
I (currently) want to create the new entity as the page loads and use it to populate the (KnockoutJS) view model.
I believe that the entity manager lazily populates the metadata and have spoofed the behavior I want by making an unnecessary query purely to force the metadata population. The API docs don't cover this:
http://www.breezejs.com/sites/all/apidocs/classes/EntityManager.html#property_metadataStore
Question
Is there a way to force the manager to populate the metadata without issuing a redundant query?
Here's the spoofed code flattened to show its intent:
manager.executeQuery(redundantQuery).then(function(data) {
var viewScore = manager.metadataStore.getEntityType("Score").createEntity();
viewScore.ID(breeze.core.getUuid());
viewScore.Value(57);
ko.applyBindings(viewScore, $ViewScore);
}).fail(function(e){
...
})
I'd be happy with:
manager.metadataStore.then(function() {
})...
Okay, lesson learned.... try it before asking.
This actually works:
manager.metadataStore.fetchMetadata(manager.dataService).then(function () {
var viewScore = manager.metadataStore.getEntityType("Score").createEntity();
viewScore.ID(breeze.core.getUuid());
viewScore.Value(57);
ko.applyBindings(viewScore, $ViewScore);
}).fail(function (e) {
...
});
My only remaining question is why is it necessary to pass the manager.dataService?
A single MetadataStore can store metadata for multiple DataServices. The manager.dataService is simply the 'default' DataService for an EntityManager, there may be others.

Resources