Extending Entities

While Breeze can create an entity based exclusively on its metadata description, you can add additional properties and behaviors as needed to support client-side requirements. For example, you may want to add a method that is useful when presenting data to the user. This topic explains how to extend the entity definition with members that are not defined in metadata.

Most of the topic examples are based on tests in the entityExtensionTests module of DocCode.

Brute force extension

Breeze entities are either created or “materialized”. They are typically created by calling the createEntity method on an EntityManager.

var cust = manager.createEntity('Customer');

Internally that method first acquires an EntityType information object from a MetadataStore and then calls the type's createEntity method [1], e.g.,

  // assume that manager's metadata was previously fetched from the server
  var store = manager.metadataStore;
  var customerType = store.getEntityType("Customer");
  var cust = customerType.createEntity();

Ok, we have a Customer object as defined by metadata. But in our application, every Customer should have a bindable isBeingEdited KnockOut observable property. That is a client application property; it's unknown on the server and it's not in the metadata. We're writing in JavaScript so we can extend a customer object simply by patching it with the new property:

  cust.isBeingEdited = ko.observable(false);

That’s not so bad. We'll probably do this a few more times so we encapsulate all of this plumbing into a customerFactory method.

Unfortunately, that only covers the entity creation use case. Breeze also materializes entities from query results. Consider this query:

  var query = breeze.EntityQuery.for("Customers")
                    .expand("Orders").where(...);

When executed, the query will return Customer entities that may or may not already be in the client cache. We can't use the customerFactory - it's only good for creating new entities. So we post-process the materialized customer entities in the query callback:

manager.executeQuery(query)
    .then(function (data) {
        var customers = data.results;
        customers.forEach(function (c) {
            if (c.isBeingEdited === undefined) { 
                c.isBeingEdited = ko.observable(false); }
        });
    })
    .fail(handleFail);

Notice that I had to check for the presence of the isBeingEdited property. A customer in the query-results set may already be in cache in which case it already has the isBeingEdited property.

How about the related orders that accompanied the customers in the payload thanks to the “expand” clause? We'll have to extend them in the callback logic too.

What if we materialize customers and orders by importing them into the manager (see the Export/Import topic). Fortunately, Breeze raises events when entities are imported and we can add event handlers that patch in the isBeingEdited property.

All of this entity patching is turning into a mess. Imagine trying to patch every entity after every query and import, wherever it occurs in the application.

It isn’t just a mess. It feels wrong. The isBeingEdited property should be part of the Customer definition, not something we tack on as an afterthought. We should be able to make it part of the Customer definition … and we can.

Get the metadataStore

We'll extend the Customer definition by adding information to the Customer's EntityType in the client-side MetadataStore.  We'll do this early in the application, before it makes a single call to the backend service.

We can get a MetadataStore from an EntityManager instance [2] like so:

  var serviceName = "..."; // route to Web API controller such as 'breeze/todos/'
  var manager = new breeze.EntityManager(serviceName);
  var store = manager.metadataStore;

Now we have a store variable that holds the canonical MetadataStore for our application. It's empty at the moment. We'll fill it in as we go.

Custom constructors

A natural place to extend an entity definition is in its constructor. We generally don't need a constructor; Breeze can use its own default constructor to make instances of any entity type. But we can define our own custom constructor if we wish and It’s probably a good idea to do so for our isBeingEdited property example. Let’s write that constructor.

  var Customer = function() {
      this.isBeingEdited = ko.observable(false);
  };

Notice that we only defined this one extension property as a Knockout observable [3]. We didn't mention the other properties because we know that Breeze will supply them later ... once it learns about them from server-supplied metadata [4].

We must register our custom constructor with the application MetadataStore before the application either queries for customers or creates a new Customer. Any customers created or materialized before we register will not benefit from our constructor.

Ideally, you register immediately after defining the metadataStore, e.g.,

  // we defined the store above
  store.registerEntityTypeCtor("Customer", Customer);

Now we query for customers.

  var query = breeze.EntityQuery.for("Customers");

  manager.executeQuery(query)
    .then(function (data) {
        var customers = data.results;
        customers.forEach(function (customer) {
            if (customer.isBeingEdited() === false) {/* ... */}
        });
    })
    .fail(handleFail);

Because this is the manager's first service request. Breeze implicitly fetches the metadata from the service and blends it with the type information in the manager's metadataStore. The queried customer data are materialized as Customer entities each with the full complement of data properties (e.g, Name) and with the custom isBeingEdited property.

Now that the manager's MetadataStore is fully populated, we are ready to create a Customer in the manner typical of a Breeze application [5].

  var customerType = manager.metadataStore.getEntityType("Customer");
  var cust = customerType.createEntity();

  // KO property with value (false) assigned in the constructor
  var isEditing = cust.isBeingEdited();
  var name = cust.CompanyName(); // Also fine.

Whither the unmapped property value?

The isBeingEdited property is only a property on the client-side Customer entity. The Customer on the server does not have an isBeingEdited property and the backing database does not have an isBeingEdited column in the Customer table either.

Breeze detects this fact. It adds the isBeingEdited property to the Customer metadata as an unmapped property. The values of unmapped properties are not transmitted to the service.

However, they are serialized when you export entities and they are deserialized when you import them into another EntityManager.

That’s important for mobile applications which need to preserve the local state of cached entities when the application is deactivated (AKA, “tombstoned”). Your deactivation logic may call manager.exportEntities() and write the serialized entity data to local storage. If it does, you may want the serialized Customer to retain the state of the isBeingEdited property and restore that state when you reactivate the application and re-import the Customer. Breeze will do that automatically for these unmapped properties.

If you don’t want Breeze to remember the state of this property, you should not define it in the constructor. You may prefer to define it in a post-construction initializer, a feature described later in this topic.

Add methods to the constructor

You can extend an entity with methods as well as properties. The methods could be instance methods or methods defined on the prototype. Here’s a frivolous example of the more typical prototype method:

var Customer = function () {/* ... */};

Customer.prototype.sayHi = function () {
    return "Hi, my name is " + this.CompanyName();
};

store.registerEntityTypeCtor("Customer", Customer);

var customerType = store.getEntityType("Customer");
var cust = customerType.createEntity();
cust.CompanyName("Acme");
var hi = cust.sayHi() // "Hi, my name is Acme"

Add Knockout computeds to the constructor

It takes a little extra care to add a Knockout computed observable to the constructor. You may prefer to add them to an initializer instead. We'll show you how to add them to the constructor anyway, in case that is your preference for some reason.

Suppose we want to add a fullName computed property to the Northwind Employee type. Your first attempt might be something like this:

var Employee = function () {
    this.fullName = ko.computed(
        function () {
            return this.FirstName() + " " + this.LastName();
        }, this);
};

The moment you create an Employee you’ll get an error complaining that “the object doesn’t support property or method 'FirstName'.”

This FirstName property exists on the full-fledged Employee object. But it doesn’t exist when this constructor is called and it won’t exist until Breeze adds the FirstName (and LastName and all of the other properties defined in metadata). Knockout doesn’t give Breeze time to add these properties.

Fortunately, there is an alternative computed property syntax that delays Knockout’s invocation of the fullName function:

var Employee = function () {
    // It's probably easier to define this in an Initializer; see below
    this.fullName = ko.computed(
        {
            read: function () {
                return this.FirstName() + " " + this.LastName();
            },

            // required because FirstName and LastName not yet defined
            deferEvaluation: true

        }, this);
};

The deferEvaluation: true tells Knockout to wait.

Alternatively, you can re-define the dependent properties in the constructor so that they do exist when Knockout exercises the fullName function. Yes, these properties already exist in metadata but there is no harm in that ... as we see next.

Defining mapped properties in the constructor

In our constructor examples we have only defined extended properties and methods. There is rarely reason to define properties that are already described in metadata. But you can if you want to and maybe we want to do so to simplify the fullName computed:

var Employee = function () {
    this.FirstName = ko.observable(""); // default FirstName
    this.LastName = ko.observable("");  // default LastName
    this.fullName = ko.computed(
            function () {
                return this.FirstName() + " " + this.LastName();
            }, this);
};

store.registerEntityTypeCtor("Employee", Employee);

var employeeType = store.getEntityType("Employee");
var emp= employeeType.createEntity();
emp.FirstName("John");
emp.LastName("Doe");
var name = emp.fullName(); // "John Doe"

Knockout can execute the fullName computed property immediately upon construction because the FirstName and the LastName properties are defined earlier in the constructor. Breeze recognizes that both observables are mapped to properties of the service entity. Their values will be set with service data during materialization and changed values will be sent to the server during a save.

 “new-ing” a custom constructor

The Customer function is a valid JavaScript constructor; it is perfectly legal to write:

var cust = new Customer();
var isEditing = cust.isBeingEdited()
var name = cust.CompanyName(); // ERROR! CompanyName is undefined

It’s legal … but cust isn’t a full-fledged entity yet.

It's going to fail at runtime because CompanyName hasn't been defined yet. Recall that our constructor only defined the isBeingEdited property. The other Customer properties are unknown at this time.

The cust object won't be usable until we add it to the EntityManager.

manager.addEntity(cust);
var isEditing = cust.isBeingEdited();
var name = cust.CompanyName(); // CompanyName is now defined.
The act of adding the entity to the manager causes Breeze to
  • Add all properties defined in metadata for this entity
  • Rewrite fields to suit the prevailing model library [3].
  • Add entityAspect and the change tracking that goes with it.
  • Execute the post-construction initializer (if one is defined).

Calling EntityType.CreateEntity() does all of all of this for you ... even before you add it to the manager.  We strongly prefer the EntityType.CreateEntity() approach. We think the new Customer() syntax leads to confusion. The choice is yours.

The post-construction initializer

If you need to perform some action after an entity has been created or materialized, you can register that action as post-construction initializer.

Suppose we want the isBeingEdited property but we don’t want Breeze to serialize it locally. If we have to restore a deactivated Customer, we want this property to be false regardless of its prior state.

Instead of defining it in the constructor, we define it in an initializer.

var Customer = function () { /* ... */ };

var customerInitializer = function(customer) {
    customer.isBeingEdited = ko.observable(false);
};

store.registerEntityTypeCtor("Customer", Customer, customerInitializer);

Now we can create a Customer as we did before.

var customerType = store.getEntityType("Customer");
var cust = customerType.createEntity();

// KO data property with value (false) assigned in the constructor
var isEditing = cust.isBeingEdited(); // still works
var name = cust.CompanyName(); // Also fine.

The customer object is almost the same as it was when we used the constructor. The important difference is that the isBeingEdited property is no longer recorded in the metadata as an unmapped property. The Customer metadata have no record of this property and Breeze won’t serialize it. If we coded isBeingEdited as a field in the initializer, it would still be a field; Breeze would not have rewritten it as a Knockout observable.

The post-construction initializer is particularly useful when you must respond to materialized values after a query or import. Here’s an example:

var creatureInitializer = function (creature) {
    if (creature.Name() === "Godzilla") {
        app.MessageBus.publish("Alarm",
         "Run! Godzilla is here!");
    };
};

Add Knockout computeds to the initializer

We mentioned above, in a comment to the Employee's fullName KO computed property, that it is easier to define computeds in the initializer.

By the time the Employee initializer runs, Breeze will have defined all of the data properties. We don't have to delay KO's pre-mature execution of the computed by setting "deferred". Here's the fullName definition, moved to an initializer:

var employeeInitializer = function (employee) {
    employee.fullName = ko.computed(function () {
        return employee.FirstName() + " " + employee.LastName();
    });
};
store.registerEntityTypeCtor("Employee", null /*no ctor in this example*/, employeeInitializer);

Isn't that easier?

Camel Case properties

The entity property names on the client exactly match the spelling of the corresponding property names on the server. That's default Breeze behavior you can change.You can switch to camel casing ("FirstName" on the server becomes "firstName" on the client) in a single line of configuration:

NamingConvention.camelCase.setAsDefault();

You can create your own conventions as well. Learn more in the "NamingConvention" section of the Metadata topic.

Temporary key generation

When the entity key (typically an ID) is determined on the server we say that it is “store generated.” We won’t know the permanent key value of a new entity until it is saved successfully. Until then, new entity keys must have temporary values.

Not just any values either.  All entities in cache must have unique keys. That means the temporary values must be unique within a cache and they cannot collide with “real” key values coming from the server.

Breeze ships with a default temporary key generator, breeze.KeyGenerator. This is perfectly adequate for most models. But occasionally a legacy model comes along with unique key generation requirements.

Fortunately, you can create a custom key generator and register it with an EntityManager before adding entities to that manager. Make sure your generator can create unique keys for any entity type that needs a temporary key value. The basic structure of a key generator is as follows:

manager.setProperties({ keyGeneratorCtor: myKeyGenerator });

function myKeyGenerator() {
    this.generateTempKeyValue = function (entityType) {
        /* logic here */
        return nextId;
  };

The entityExtensionTests module in the DocCode Teaching Tests has a simple example. The Breeze breeze.KeyGenerator is the best source of inspiration.

Entity creation sequence

Suppose you've got a custom constructor and a custom initializer function and you create a new entity with an initial values hash object like so:

new customer = manager.createEntity('Customer', { Name: "Acme" });

What is the order of events? The "createEntity sequence ..." test in the entityExtensionTests module of DocCode provides the answer: 

  1. custom constructor
  2. initial values ("Acme"
  3. initializer function
  4. add to manager

When Breeze materialize an object through query or entity import, the sequence is

  1. constructor
  2. initializer function
  3. merge into cache

Notes:

[1] In this topic we assume that we're getting most of our metadata from the server via a Breeze Web API controller and that the application should create entities suitable for data binding with Knockout (KO). These are the Breeze dataService and modelLibrary configuration defaults. Learn about alternative Breeze configuration elsewhere in this documentation.

[2] Here we acquire the canonical application metadataStore from a single EntityManager instance. What if our application needs multiple EntityManagers? We'd want them all to share the same MetadataStore. No problem. First we create the common store

  var serviceName = "..."; // the service endpoint e.g., 'breeze/todos/'
  var store = new breeze.MetadataStore(); // define metadataStore for all managers

The we create an EntityManager factory method to create new instances that share the common store ... and our extensions to it.

  function createManager() {
      var manager = new breeze.EntityManager({
          serviceName:   serviceName, 
          metadataStore: store
  });
  // ... later in the application ...
  var manager = createManager();

[3] Our application is configured for the Knockout model library (Knockout is the Breeze default model library) so we defined the isBeingEdited property as a Knockout observable in our Customer constructor.

We could have defined it as a field ... like this:

  var Customer = function() {
      this.isBeingEdited = false; // a field ... not a KO observable!
  };

Yet when we examine a runtime Customer instance - whether created or materialized by query - we'll find that the isBeingEdited property has become a Knockout observable! Breeze re-writes the isBeingEdited field as a Knockout observable. Breeze always rewrites constructor fields to match the property syntax of your application's model library ... be it Knockout, Backbone, Angular, etc.

[4] You don't have to mention properties that are already defined in metadata coming from the service. But it's harmless if you do.

  var Customer = function() {
      this.isBeingEdited = ko.observable(false);
      this.CompanyName = ko.observable('[default name]');
  };

Here we define the CompanyName which is a data property mapped to the [CompanyName] column of the [Customer] table in the database on the server. We also specified a value for the CompanyName. This is the default value that Breeze assigns to CompanyName when you create a new Customer instance.

[5] In this example we are taking advantage of the fact that Breeze fetched metadata from the server implicitly when we queried for customers. Most applications query the server for something before they do much of anything else ... and any query will trigger metadata retrieval. However, if your application might consume metadata before the first query - if it might create a new Customer for example - , you must first fetch the metadata explicitly yourself.  Here's an example:

  manager.fetchMetadata()
         .then(startTheApp)
         .fail(terminateImmediately);