4
votes

Problem

I got a "TypeError: object is not a function" error when EntityManager.saveChanges. The error arises before breeze sends anything to the server.

When I read the (long) stack trace, I see that the error is thrown inside a Breeze method called InitializeEntityPrototype.prototype.getProperty.

There are other places you might encounter this error; this just happens to be the place I discovered it today.

Cause

Note that my application uses Knockout (ko). That means my entity model is managed by the breeze "ko" model adapter which expects every data property to be a ko.observable (an observable or an observableArray). That means that your property values are implemented as functions, not primitive data types, arrays, or objects.

Breeze initializes my entities with observable properties so I don't have to do it myself. But it is up to me preserve those observables when I set entity models in my code.

One of the most common mistakes in ko programming is to set the property rather than call the property setter.

todo.Description("foo");  // Correct ... call the ko.observable assignment function
todo.Description = "foo"; // WRONG ... wipes out the observable function !!!

The moment I make the mistake of assigning "foo" to todo.Description, the observable function is gone ... and so is Breeze's ability to monitor changes to that property.

My data bound HTML controls may appear to continue working. Knockout doesn't have to bind to an observable; it will happily bind to a primitive data value. But now this becomes a one-time, read-only binding. The property ceases to be observable. Subsequent changes to the property will not be propagated to the screen.

In other words, my mistaken assignment results in a silent error in UI behavior.

But my mistake is not silent when Breeze attempts to process the property. Breeze assumes that an entity data property is a ko function. The Breeze ko model adapter doesn't bother to check ... it just calls what it presumes to be a function. Here's the (internal) entity getProperty implementation:

proto.getProperty = function (propertyName) {
  return this[propertyName]();
};

You can see why that is going to throw an exception if this[propertyName] is anything other than a function.

Sadly for me, the error is likely to be thrown somewhere deep in the bowels of a Breeze operation. The error message "object is not a function" (or something similar) could be about anything. I'm unlikely to make the connection.

This S.O. question is a reminder to look for this particular cause.

What to do?

It's clearly my fault. Now I have to find where I assigned the entity property instead of calling the function ... and fix that.

My search depends upon knowing the property name. The present error message doesn't tell me the name. So I've got to breakpoint the code and catch the error as it is thrown.

A "simple" expedient is to temporarily monkey-patch this method in breeze.debug.js with a try/catch version.

  1. open breeze.debug.js in an editor
  2. find "return this[propertyName]();"
  3. change it to this:

    proto.getProperty = function(propertyName) {
      try {
        return this[propertyName]();
      } catch (err) {
        debugger;
        err.message = propertyName + ' is not a ko function; did you wipe it out by assignment?\n' + 
                      (err.message || '');
        throw err;
      }
    }
    
  4. run the app again with the Developer Tools open

It should stop at the debugger line where you'll learn the property name and the entity involved.

Should Breeze provide a richer error message?

It would be nice if Breeze composed this message for me. In fact, the team is looking into doing that.

The main obstacle is performance. The getProperty and setProperty methods are on a hot path. They get called a lot, especially during query result materialization. The Breeze team is leery of extra logic in this sensitive area. We don't want everyone to pay a big price to catch developer error, at least not in a production (minified) Breeze library.

Let's give the Breeze team time to figure this out.

1
You sir deserve more up-votes. You saved my ... you know what.Nagri

1 Answers

3
votes

Your question is well-written, touches on a common issue, and may well be helpful to others searching for this specific problem. However, it still is probably asking for opinions.

In any case...

Me and my colleagues call the problem from your situation "The Knockout Tax": some programming inconvenience you have to deal with in return for getting to work with a great MVVM library. A different instance of the same problem occurs when you data-bind="visible: !myObservable" and forget to execute it as a function to get the value.

To answer the question: no, I don't think Breeze should handle this problem, at least not with a richer error message. If anything, I think there are three different options if you're in the question's situation:

  1. Use Typescript for your own code. If Description is a KnockoutObservable<string> then the compiler will complain if you assign a regular string to it.
  2. In your own code or as a patch for the appropriate framework: make the properties for observables read only. Along these lines. However, this method relies on browser support...
  3. Switch to another MVVM framework that doesn't have this "tax". AFAIK for example Angular doesn't have this "function"-like setup that Knockout has; it uses regular properties.

But that's just my 2 cts - reading my own answer I do feel this ("should situation X be handled by framework Y?") is all a matter of opinion.