Print Page | Close Window

Entity manager cache not working

Printed From: IdeaBlade
Category: Breeze
Forum Name: Community Forum
Forum Discription: Build rich JavaScript apps using techniques you already know
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=3739
Printed Date: 16-Apr-2024 at 4:11am


Topic: Entity manager cache not working
Posted By: mgkoko
Subject: Entity manager cache not working
Date Posted: 24-Oct-2012 at 3:23am
Hi there,

I've been developing a SPA based on John Papa's example of code camper. I came across with breeze.js and I really want to use this rather than building my own client db context. So far it is OK, but like in code camper example I want to load some data from dataprimer and que the results as needed from local cache. but everytime I tried executeQueryLocally I got 'entityType is null' error. When I check getEntities() to see if I got the results in cached it returns blank but if I check data.results then there are results. my code is

define('dataservice.categories',
    ['jquery', 'underscore', 'ko', 'config', 'utils','breeze', 'store'],
    function ($, _, ko, config, utils, breeze, store) {

        var entityModel = breeze.entityModel,
            core = breeze.core;

        // configure Breeze for Knockout and Web API
        core.config.setProperties({
            trackingImplementation: entityModel.entityTracking_ko,
            remoteAccessImplementation: entityModel.remoteAccess_webApi
        });

        // service name is route to the Web API controller
        var serviceName = '/api/breeze';


        // manager is the service gateway and cache holder
        var manager = new entityModel.EntityManager(serviceName);

        var getData = function () {
            var query = entityModel.EntityQuery.from("Categories");
            manager.executeQuery(query).then(function (data) {
                alert(data.results.length);  //this returns 45 results
                alert(manager.getEntities().length); // this returns 0 results. WHY????
            });
        },
        getByParentUrl = function (url, results) {
            var query = entityModel.EntityQuery.from("Categories");
            query = query.where("Parent.FriendlyUrl", "==", null);
            manager.executeQueryLocally(query); //I think this is the line where I got 'entityType is null' error
        }

        return {
            getData: getData,
            getByParentUrl: getByParentUrl
        }

    });

Please let me know if you have any question
Thank you,



Replies:
Posted By: jtraband
Date Posted: 24-Oct-2012 at 1:14pm
Ok, I was unable to reproduce the first part of your error (shown below). I do not have a copy of the "CodeCamper" database but I've run the same test with our test db ( a version of Northwind) and manager.getEntities returns an array that it the same length as data.results.length.  So I'm not sure what's going on there.

 var query = entityModel.EntityQuery.from("Categories");
 manager.executeQuery(query).then(function (data) {
        alert(data.results.length);  //this returns 45 results
        alert(manager.getEntities().length); // this returns 0 results. WHY????
 });

As for "executeQueryLocally", it can only execute if the entityManager cache already has data in it.  I cannot tell from your example, but is it possible that you are calling getByParentUrl before any call to getData has completed.   Your getData method is asynchronous whereas your getByParentUrl is not, so you need to make sure that your getData call completes before calling getParentUrl.

If you continue to have issues, can you post a copy of your server side controller?




Posted By: pawel
Date Posted: 25-Oct-2012 at 6:53am
As a side note - if you are building a solution based on CodeCamper with Breeze, then remember to remove json camelCasing formatting. Remove the following lines from Alcance.Web.GlobalConfig.CustomizeConfig()
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();

If you leave this configuration Breeze will be unable to populate your objects with data.


Posted By: svk
Date Posted: 04-Nov-2012 at 8:04am
Using a very simple test application, I am experiencing the same problem -  manager.getEntities().length always returns 0 while data.results.length returns the actual number of entities.  Below are the components of the test app.  Please let me know if I can provide any more information or if you have any ideas.  Thanks!

Index.chtml looks like this:
<!DOCTYPE html>
<html>
<head>
    <title>Breeze Test</title>
</head>
<body>
Test
<script src="/Scripts/jquery-1.8.2.js"></script>
<script src="/Scripts/knockout-2.2.0.debug.js"></script>
<script src="/Scripts/q.js"></script>
<script src="/Scripts/breeze.debug.js"></script>
<script src="/Scripts/require.js"></script>

<script src="/Scripts/app/main.js"></script>

</body>
</html>

Note: knockout is loaded but not used.  The results are the same even when the test is expanded and a ko array is populated.  The array is filled with entities but manager.getEntities() returns 0 so executeQueryLocally doesn't work.  


main.js boots the app and looks like this:
(function () {

    configRequire();
    boot();

    function configRequire() {
        requirejs.config({
            paths: {'breeze': 'Scripts/breeze.debug'}
        });
    }

    function boot() {

        require(['breeze'], function (breeze) {
            var core = breeze.core,
                entityModel = breeze.entityModel;

            core.config.setProperties({
                trackingImplementation: entityModel.entityTracking_ko,
                remoteAccessImplementation: entityModel.remoteAccess_webApi
            });

            var serviceName = 'api/breezetest';
            var manager = new entityModel.EntityManager(serviceName);

            var query = new entityModel.EntityQuery().from("Items");

            manager.executeQuery(query)
                .then(
                    function (data) {
                        alert("Got " + data.results.length + " data rows");
                        alert("Got " + manager.getEntities().length + " entities");
                    })
        });

    }
})();

controller:
namespace BreezeTest.Controllers
{
    public class BreezeTestController : ApiController
    {
        readonly EFContextProvider<BreezeTestDbContext> _contextProvider = new EFContextProvider<BreezeTestDbContext>();

        [AcceptVerbs("GET"), ActionName("Metadata")]
        public string Metadata()
        {
            return _contextProvider.Metadata();
        }

        [AcceptVerbs("GET"), ActionName("Items")]
        public IQueryable<Item> Items()
        {
            return _contextProvider.Context.Items;
        }
    }
}

metadata:
{"conceptualModels":{"schema":{"namespace":"BreezeTest.Data","alias":"Self","d4p1:UseStrongSpatialTypes":"false","xmlns:d4p1":"http://schemas.microsoft.com/ado/2009/02/edm/annotation","xmlns":"http://schemas.microsoft.com/ado/2009/11/edm","entityType":{"name":"Item","key":{"propertyRef":{"name":"Id"}},"property":[{"name":"Id","type":"Edm.Int32","nullable":"false","d4p1:StoreGeneratedPattern":"Identity"},{"name":"Name","type":"Edm.String","fixedLength":"false","maxLength":"Max","unicode":"true","nullable":"true"},{"name":"Description","type":"Edm.String","fixedLength":"false","maxLength":"Max","unicode":"true","nullable":"true"}]},"entityContainer":{"name":"BreezeTestDbContext","entitySet":{"name":"Items","entityType":"Self.Item"}}}}}

model:
namespace BreezeTest.Models
{
    public class Item
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

    }
}

dbcontext:
namespace BreezeTest.Data
{
    public class BreezeTestDbContext : DbContext
    {
        public DbSet<Item> Items { get; set; }
    }
}


Posted By: svk
Date Posted: 06-Nov-2012 at 8:57am
Any thoughts?  Am I doing something incorrectly or might I be running up against a bug?  Thanks.


Posted By: jtraband
Date Posted: 06-Nov-2012 at 10:36am
Looking at it now.


Posted By: CCPony
Date Posted: 07-Nov-2012 at 9:35am
I'm currently experiencing this exact same problem.


Posted By: WardBell
Date Posted: 07-Nov-2012 at 9:57am
Hang in there. We'll take SVK's sample and try to repro. With luck everything we need will be there ... or at least we'll have a shared solution to kick around.


Posted By: WardBell
Date Posted: 07-Nov-2012 at 11:28am
I have a test bed that matches SVK's sample ... except it doesn't yet use RequireJS. It works fine. Will now create a second version that uses Require. Maybe that is introducing a problem ... although we use Require all over the place.


Posted By: WardBell
Date Posted: 07-Nov-2012 at 12:02pm
Ok everyone ... I cannot reproduce your issues. But I can give you two solutions, one with Require and one without, that follow the path prescribed by svk. I've attached both.

If you want to press this issue productively, I suggest that you use either of these solutions as a baseline for your reproduction of the error.


Posted By: CCPony
Date Posted: 07-Nov-2012 at 12:21pm
I'm new around here. Ward, I don't see your attachments. Where are they? Thanks.


Posted By: WardBell
Date Posted: 07-Nov-2012 at 12:29pm
I'm old around here and I don't see them either. Some problem with the forum attachment mechanism. 

For now, you can get them at
http://www.breezejs.com/sites/all/packages/CacheNotWorkingForum3739.zip - http://www.breezejs.com/sites/all/packages/CacheNotWorkingForum3739.zip  
http://www.breezejs.com/sites/all/packages/CacheNotWorkingForumWithRequire3739.zip - http://www.breezejs.com/sites/all/packages/CacheNotWorkingForumWithRequire3739.zip

Until we get forum attachment straightened out, you'll have to tell us where to get the repro solutions that you create.


Posted By: WardBell
Date Posted: 07-Nov-2012 at 2:10pm
Now that I know how to attach a file, I present you with these same zip files attached to the forum post itself.

Version without require:  uploads/482/CacheNotWorkingForum3739.zip - uploads/482/CacheNotWorkingForum3739.zip
Version with require:  uploads/482/CacheNotWorkingForumWithRequire3739.zip - uploads/482/CacheNotWorkingForumWithRequire3739.zip

If you can reproduce the problem using either of these solutions and want us to evaluate it, you can upload your solution as I did here..

http://www.ideablade.com/forum/forum_posts.asp?TID=3774&title=attach-a-file-to-your-post - This forum topic explains how to attach and manage files


Posted By: svk
Date Posted: 07-Nov-2012 at 2:56pm
Thanks for the sample projects, Ward.  I was able to identify the problem.  Simply put, Breeze requires that the models and dbcontext be in the same namespace. 

You can repro the described behavior in your projects by moving BreezeTestDbContext & BreezeTestDatabaseInitializer into a different namespace. 

Is this something that can easily be fixed?  Thanks!


Posted By: WardBell
Date Posted: 07-Nov-2012 at 3:13pm
Yes, that is a known and documented requirement. The Model classes and the DbContext(s) must be in the same namespace;  the controller can be in a different namespace. 

We won't be trying to "fix" this any time soon. The Entity Framework tooling we use to generate the metadata only supports a single shared namespace and we don't think building our own metadata generation is a good investment at this time.

This issue ... and a workaround ... are discussed in our documentation on using the http://www.breezejs.com/documentation/entity-framework-dbcontext-0 - Entity Framework DbContext in a Breeze application.

After you've looked at that, if you still feel this is a critical shortcoming, please let us know and explain why.


Posted By: CCPony
Date Posted: 07-Nov-2012 at 5:44pm
I did some testing just came to the same conclusion. Thanks for handling so quickly.


Posted By: svk
Date Posted: 07-Nov-2012 at 5:52pm
Sorry for the oversight... I previously read through all of the documentation and I must have overlooked this.  It probably wasn't relevant at the time I read it.  This is not a show stopper for me so I am happy to move on.    thanks again.


Posted By: WardBell
Date Posted: 07-Nov-2012 at 5:59pm
Just so we're clear: classes in different projects can share the same namespace. This happens all the time; many Microsoft classes in different assemblies share the same namespace.

You may not want to do that but you certainly can. 

If you don't want to do that, the  http://www.breezejs.com/documentation/entity-framework-dbcontext-0 - Entity Framework DbContext  documentation shows a simple workaround: create a subclass of the DbContext that is in the model's namespace. The DbContext subclass doesn't have to do anything other than inherit from your base DbContext ... and shift the namespace. This, I think, is a small price to pay to trick the metadata generation tooling.

I agree with separating the projects as you have done. That can be wise as models grow. Breeze will not interfere with that separation. You just need to spit on it a little bit to get the namespaces aligned.

-- The above is in reply to CCPony who wrote the following ... which somehow has disappeared (maybe I deleted by accident?) --

Placing the data components and the model files into separate projects and referencing those projects in the main web project causes the error.

For example, if you have the following three projects:

BreezeTest.Model - contains item.cs

BreezeTest.Data - contains BreezeTestDatabaseInitializer.cs and BreezeTestDbContext.cs. This projects references BreezeTest.Model

BreezeTest.Web references both BreezeTest.Model and BreezeTest.Data

When you run BreezeTest.Web in this configuration you get: "3 items in query" and "0 entities in cache".


As per Ward's attached sample, however, when all the files in the one project, you get "3 items in query" and "3 entities in cache".

So, the problem seems to be limited when using separate projects. By the way, I used the RequireJs attachment.

Let me know if you have any other questions. 



Posted By: WardBell
Date Posted: 07-Nov-2012 at 8:22pm
Perhaps you are wondering 

(a) Why did the query return results but no entities in cache? 

(b) Why did Breeze allow the model type and DbContext namespaces to be mismatched? Why didn't it protest?

Let me assure you that Breeze behaved exactly as intended ... if not in the way you expected.

In this example, the client query was routed to the Web API controller's "Items" method. The server responded with JSON data for a type 'Item" belonging to the namespace 'Model'.  Breeze on the client doesn't know about an 'Item' type in the namespace 'Model'; it knows about an 'Item' type in the namespace 'Data'. But that's not the same type at all. Breeze simply doesn't know what type of data it received. Therefore, it cannot turn that data into 'Item' entities ... which is why there are no Items in cache.

But the server did return data and Breeze dutifully provides those data as JavaScript objects in the 'data.results' array of the query promise. In the post which started this thread, we know the server returned 45 'somethings'. They weren't entities - Breeze didn't know what they were. But you, the developer, asked for them and here they are.

Breeze does not demand that a query return entities. If the service returns data that it recognizes as entities - if the data are identified in metadata as entity types  - Breeze will turn those data into entities and will put them in cache. Otherwise it forwards them to the caller via the promise.

There is nothing wrong with the server sending arbitrary data to the client. That's actually a useful feature.  For example, suppose you have a Person entity and Person has 100 columns/properties including an image property that could be 100KB. We want to present a list of Persons, just their first and last names. We can't afford to download every property of every Person in the list. 

Well, in Breeze we can make a "projection query" - a query for selected properties - and transmit only the properties we need. Clearly the data objects that satisfy our query are not whole Person entities. They are something else, something unnamed and un-typed. But something useful nonetheless.

Breeze doesn't judge. It doesn't insist that the query return whole Person entities. It simply examines the incoming data. If they are Persons, they become entities in cache. If not, fine, pass them along to the caller.

Therefore, Breeze shouldn't protest when it receives a data object of an unrecognized type. It was prepared to recognize 'Data.Item' data; it received 'Model.Item" data; no big deal ... and no entities in cache either.

---

This isn't the entire story. Actually, Breeze examines the incoming data objects looking for entities. If the incoming data are object graphs,  Breeze traverses the graphs, looking for objects which it recognizes as entities. If it finds entities within the object graphs, it puts them in cache.

This is a super valuable feature. I use it to query and cache all of my pick-lists in a single shot. For example, I might create a service method called "Lookups" that returns a single object whose properties are arrays of Color, Status, Size, ProductType, ... you get the idea. That object is essentially a bag of lists that I'll use to populate comboboxes.

Then I make a single query to "Lookups" ... which returns this bag of lists. Now breeze doesn't recognize the bag at all. But each of the bag's properties is a a collection of objects that are described in metadata: Color is an entity type, Status is a type, Size is a type, ProductType is a type. Breeze recognizes that these nested objects are entities and puts them in cache. 

So in a single request, in a single payload, I'm able to populate the EntityManager cache with all of the little pick-lists.

That's pretty cool.



Print Page | Close Window