Breezejs EntityManager MetadataStore and fetchEntityByKey
Asked Answered
E

2

5

I have a SPA application (durandaljs), and I have a specific route where I map the "id" of the entity that I want to fetch.

The template is "/#/todoDetail/:id".

For example, "/#/todoDetail/232" or "/#/todoDetail/19".

On the activate function of viewmodel, I get the route info so I can grab the id. Then I create a new instance of breezejs EntityManager to get the entity with the given id.

The problem is when I call manager.fetchEntityByKey("Todos", id), the EntityManager doesn't have yet the metadata from the server, so it throwing exception "Unable to locate an 'Type' by the name: Todos".

It only works if first I execute a query against the store (manager.executeQuery), prior to calling fetchEntityByKey.

Is this an expected behavior or a bug ? Is there any way to auto-fecth the metadata during instantiation of EntityManager ?

note: I believe it's hard to use a shared EntityManager in my case, because I want to allow the user directly type the route on the browser.

EDIT: As a temporary workaround, I'm doing this:

BreezeService.prototype.get = function (id, callback) {
    var self = this;

    function queryFailed(error) {
        app.showMessage(error.message);
        callback({});
    }

    /* first checking if metadatastore was already loaded */

    if (self.manager.metadataStore.isEmpty()) {
        return self.manager.fetchMetadata()
        .then(function (rawMetadata) {
            return executeQuery();
        }).fail(queryFailed);
    } else {
        return executeQuery();
    }

    /* Now I can fetch */
    function executeQuery() {
        return self.manager.fetchEntityByKey(self.entityType, id, true)
                        .then(callback)
                        .fail(queryFailed);
    }
};
Emery answered 10/2, 2013 at 23:0 Comment(0)
H
7

You've learned about fetchMetadata. That's important. If you application can begin without issuing a query, you have to use fetchMetadata and wait for it to return before you can perform any operations directly on the cache (e.g., checking for an entity by key in the cache before falling back to a database query).

But I sense something else going on because you mentioned multiple managers. By default a new manager doesn't know the metadata from any other manager. But did you know that you can share a single metadataStore among managers? You can.

What I often do (and you'll see it in the metadata tests in the DocCode sample), is get a metadataStore for the application, write an EntityManager factory function that creates new managers with that metadataStore, and then use the factory whenever I'm making new managers ... as you seem to be doing when you spin up a ViewModel to review the TodoDetail.

Heaume answered 11/2, 2013 at 6:49 Comment(2)
good sugestion, so I will create entityManagers with factories, sharing globally metadata between them. By the way, is there any chances to improve breeze in order to add this facility, like a global instance of metadatastore ? Or then force entitymanager to query metadata even if I want just fetchEntityByKey, without running a full query against the service?Emery
Not sure what you mean. I'm not that keen on Breeze-defined default MetadataStore but that's something you could easily create for yourself. You can tell a MetadataStore to fetchMetadata (that's async of course) quite apart from a query (I mentioned this in my answer). You also might look at EntityManager.createEmptyCopy. You can call that on your master EM to make new empty EMs that are fully configured; the rest of your factory method would import from master just those entities (e.g., reference lists) that you want in the copy.Heaume
A
2

Coming from a Silverlight background where I used a lot of WCF RIA Services combined with Caliburn Micro, I used this approach for integrating Breeze with Durandal.

I created a sub folder called services in the App folder of the application. In that folder I created a javascript file called datacontext.js. Here is a subset of my datacontext:

define(function (require) {

    var breeze = require('lib/breeze'); // path to breeze
    var app = require('durandal/app');  // path to durandal

    breeze.NamingConvention.camelCase.setAsDefault();

    // service name is route to the Web API controller
    var serviceName = 'api/TeamData',

    // manager is the service gateway and cache holder
    manager = new breeze.EntityManager(serviceName),

    store = manager.metadataStore;

    function queryFailed(error) {
        app.showMessage("Query failed: " + error.message);
    }

    // constructor overrides here

    // included one example query here
    return datacontext = {
        getSponsors: function (queryCompleted) {
            var query = breeze.EntityQuery.from("Sponsors");
            return manager
                .executeQuery(query)
                .then(queryCompleted)
                .fail(queryFailed)
        }
    };
}

Then in your durandal view models you can just require the services/datacontext. For example, here is part of a sample view model from my app:

define(function (require) {

    var datacontext = require('services/datacontext');

    var ctor = function () {
        this.displayName = 'Sponsors',
        this.sponsors = ko.observable(false)
    };

    ctor.prototype.activate = function () {
        var that = this;
        return datacontext.getSponsors(function (data) { that.sponsors(data.results) });
    }

    return ctor;
});

This will allow you to not worry about initializing the metadata store in every view model since it is all done in one place.

Allseed answered 11/2, 2013 at 17:20 Comment(3)
I also have a background on Silverlight/Caliburn.Micro and WCF Ria Services. Actually, if you are using Bundles from MVC, you can include breeze.min.js to the bundled scripts, and you'll not need to call require('path/to/breeze'). See my durandal video. My problem is that I want metadata without doing a query first. Thanks!Emery
I guess it worked for me because I was using a query to get the single entity. var query = breeze.EntityQuery.from("AthleteDetails").where("Id", "==", id);Allseed
yes, worked because if you use EntityQuery, you are running a query that will fetch metadata if the manager has not fetched before... the result of query will be an array of entities, so you need to access it by the zero index like result.entities[0]. It's not the case of fetchEntityByKey() that always returns a single entity or null, and can optionally returns entity from cache if desired, but it will not fetch metadata for the manager, and will throw exception if I not fetch manually, like I did in the question.Emery

© 2022 - 2024 — McMap. All rights reserved.