"NoDb" - Breeze without a database

The NoDb sample demonstrates a BreezeJS client working with an ASP.NET Web API service backed by an in-memory "database". No Entity Framework, No SQL Server.

BreezeJS is a pure JavaScript library. If your dataservice talks JSON over HTTP, it can work with BreezeJS.

You’ll find “NoDb” among the samples in the full download.

Most Breeze samples take the easy path on the server with ASP.NET Web API, Breeze.NET helpers, and Entity Framework and SQL Server. You write significantly less code that way and your client benefits from the metadata generated on the server.

This sample does away with EntityFramework and has no database at all. The data reside in memory for the lifetime of the server. The service does not generate metadata about the model. The BreezeJS client gets its metadata from a MetadataStore constructed by hand in JavaScript. Aside from this wrinkle, the NoDb client application is almost identical to a hypothetical version that talked to a more typical Breeze server backed by EF and SQL server.

If you can't use Entity Framework or you need to work with a non-relational data store, this NoDb sample is for you.

Running the app

The NoDb sample is based on the ASP MVC SPA Template application. It presents one or more "Todo Lists", each with "Todo Items". Here's a snapshot of the app when it is first launched, pre-filled with sample Todo data.

The Todo list title at the top is editable; click in it to change it. The "x" symbol in the upper right deletes the Todo list and all of its items.

Each Todo item is editable. The checkbox marks the item as "done". Click in the text to edit the title. The "x" to the right of the title deletes the item.

Create a new item by entering title text at the bottom of the list where it says "Type here to add".

The app saves after every change. It saves when you add a new Todo list or item, check a box, or click an "x' symbol to delete.

There is some validation (we'll talk about that). If you create a validation error by typing an item title that is too long, you'll see an error message like this.

The errant value stays on screen for a second and then reverts.

Inside NoDb

Now we'll take a tour of the NoDb code. We will focus almost exclusively on what makes this application different from a Breeze app that uses Entity Framework (EF), a database, and the Breeze.NET helper components.

Review the other samples and the documentation to learn about mainstream Breeze features such as query and save. We'll begin with the server.

Server

If you're familiar with the ASP MVC SPA Template application, you'll notice some things are missing.

  • No Entity Framework. Although the Breeze NuGet package installs EF, we deleted the reference to it from the project.

  • No database. Open the "Web.config". No connection strings, no providers. Nada. The "database" is simulated by an in-memory data structure defined in the Models/TodoContext class.

  • No authentication. Authentication is essential in a real application; it's a distraction from our present purpose. We removed the user registration, login, and authentication enforcement. The server no longer filters Todo Lists by user; the TodoList class no longer has a UserId property.

  • No MVC. This sample was built from the ASP.NET Empty Web Application template. We have nothing against MVC. We just don't need it for a pure SPA application because we're only serving a single, simple page.

  • No Razor engine and no Bundling. These are cool capabilities. But we're trying to get closer to the metal in this sample. So we server "index.html", not "index.cshtml". CSS and JavaScript files are loaded with script tags rather than ASP.NET bundles. Feel free to restore Razor and bundling ... which you can do without restoring MVC.

Server stack

The sample author wrote five classes in support of TodoNoDb clients.

Front to back they are.

TodoController ASP.NET Web API controller handling client requests
TodoRepository Helper mediating between the controller and the data access layer
TodoContext TodoContext is the data access and in-memory database combined. It inherits from the Breeze.NET ContextProvider which helps with parsing request data and preparing response data for BreezeJS clients.
TodoList The root entity representing a collection of TodoItems
TodoItem Something to do.

Let's learn a bit more about each class.

Please keep in mind at all times that this is a sample employing an in-memory data structure in lieu of a database. The code is not production quality. It takes advantage of Breeze.NET classes such as EntityInfo and the saved change-set paradigm. It is intended to be suggestive. Nothing about BreezeJS prevents you from taking a completely different approach with no dependence on Breeze.NET components.

TodoController

Client requests arrive and depart through the TodoController, a simple ASP.NET Web API controller.

The class is adorned with the BreezeController attribute which conveniently purges all formatters, adds a JSON formatter configured for Breeze clients, and adds  Breeze.NET IQueryable support. The NoDb app doesn't issue queries - it just retrieves all TodoLists in a single request - but it could and you can experiment with OData query URLs in a tool like Fiddler.

TodoController is similar to other Breeze controllers in that it responds to actions specified in the request URL. But it only offers two actions: (1) TodoLists which returns an IQueryable of the only type the client asks about and (1) SaveChanges which receives a standard BreezeJS JSON change-set bundle and forwards them to an instance of the TodoRepository. Clearly missing is the Metadata action typical of other Breeze controllers. This service does not generate metadata for the client and respond with a "404 Not Found" if asked for metadata.

The simplicity of this controller is in keeping with recommendations for Web API controllers in general and Breeze controllers in particular: it dispatches all serious work to helper classes such as the TodoRepository.

TodoRepository

Note: this class was called TodoContextProvider in earlier releases of the NoDb sample.

The TodoRepository mediates between the controller and the data access layer, simplifying interactions with that layer by following a routine common to many other apps.

This repository deliberately parallels the ContextProviders you'll find in our other samples. Unlike them, it does not inherit from EFContextProvider and it knows nothing about Entity Framework. It inherits instead from the Breeze.NET ContextProvider, an abstract base class that offers a basic harness and pipeline for processing BreezeJS client requests.

This repository has three responsibilities:

  1. Surface query methods to the Controller,
  2. Generate JSON metadata for the client,
  3. Save a change-set of data from the client.

The first two are trivial. The TodoLists query - the only query - presents the entire, in-memory TodoList collection as an IQueryable; the controller exposes that queryable as an HTTP GET action. This service doesn't generate metadata. The abstract base class demands an implementation; we give one that returns null.

We don't need a base class to accomplish so little. But the more complex save cycle justifies our implementation choice.

The NoDb client app saves one or more entities in the customary BreezeJS manner: by calling EntityManager.saveChanges. Breeze bundles the pending changes into a JSON data structure (called a "change-set") and POSTs that structure to the controller.

The controller receives it as a JSON.NET JObject which we could parse and process ourselves. Instead, we assign that chore to the Breeze.NET ContextProvider.

The provider transforms the change-set into a dictionary of EntityInfos, keyed by entity type. Each dictionary entry is a list of EntityInfos. An EntityInfo is a Breeze.NET type describing one entity to save. Here's its public API:

public class EntityInfo {
  public object Entity { get; internal set; }
  public EntityState EntityState { get; internal set; }
  public Dictionary<string, object> OriginalValuesMap { get; internal set; }
}

The Entity is the object to save (e.g., a TodoList) with all properties set to their current and proposed values.

The EntityState is an enum indicating the save operation to perform: insert ("Added"), update ("Modified"), or delete ("Deleted").

The OriginalValuesMap contains property-name/original-value pairs for each of the properties that have changed; only "Modified" entities have mapped values.

Caution: remember that this map comes from  the client which can never be fully trusted. Use the map for guidance. Ignore it and re-fetch the original from the database for comparison with the proposed values if this entity type carries any sensitive information.

Pre-save validation is the next step in the save process. This provider overrides the BeforeSaveEntity method where it confirms that every TodoItem has a parent TodoList. This single check suffices for now; there are other validations performed downstream.

Next comes the save itself. The base ContextProvider knows when to save but not how to save so we override the abstract SaveChangesCore and delegate to the TodoContext.SaveChanges method. That method should save the entities, update their values, and return a key map of temporary-to-permanent keys. The base provider repackages that key map and the updated entity map into a SaveResult. We return the SaveResult to the calling controller which bakes it into the HTTP response. The save process is complete. Failures along the way (e.g., a failed validation) should be thrown as exceptions for the controller (or the Web API) to catch and forward to the client.

It bears repeating that the reliance of this TodoRepository on the Breeze ContextProvider is a matter of convenience. We could have replaced all Breeze.NET components with custom code. In our judgment doing so would only cloud with complexity what should be a simple point: a Breeze client can work with a server that does not use Entity Framework and does not suppy metadata.

TodoContext

The TodoContext class defines the in-memory data structure that serves as the "database" for this sample. That data structure is simply a dictionary of TodoList objects.

TodoContext is a Singleton whose Instance property returns a "data access" object that both contains and manipulates that dictionary. Its API is thread safe, thanks to locking code in the implementation.

The API is two methods: (1) a TodoLists readonly property that returns the TodoLists dictionary and (b) a SaveChanges method for updating that dictionary. Only SaveChanges merits further comment.

SaveChanges receives a dictionary of EntityInfos, keyed by entity type. There could be at most two entries in this particular dictionary because NoDb has only the TodoList and TodoItem types.

As detailed above, an EntityInfo holds both an instance of an entity type and the name of the operation to perform. That seems sufficient to update the in-memory TodoList dictionary, but first we take two other preparatory steps: (1) key generation and (2) validation.

Key generation

If we're adding a new entity and its key is store generated, its current key is a temporary negative integer that was set by the client. A real database would give us a permanent key when we inserted the entity data. This being a fake database, we generate the postitive intenger, permanent key with the IdGenerator class defined at the bottom of the file. We replace the key in this entity and also update every other entity in the change-set which had the temporary key value in its foreign key property. The AddTodoItem method illustrates both procedures. We prepare a list of KeyMappings that correlate the temporary keys with their replacement permanant keys. We'll send this back to the client where BreezeJS will perform similar substitutions (AKA, "id fix-up").

Validation

We shouldn't assume that the client sent us valid data. The add and update methods call each entity's Validate function; a more sophisticated application might validate the delete operation as well.

If the validate method returns an empty string, the entity passed validation and we can continue. If not, the string contains one or more validation error messages which become a larger ValidationError exception  message. This exception is then thrown and the upstream calling components pass the exception back to the client. You can see this in action by entering a TodoList title greater than 20 characters. The 20 character limit is enforced on the server,  not the client, so a bad TodoList title update will be communicated to the server and trigger this exception. The following screen shot shows the consequences; notice that the server exception message is phrased differently than the client-side validation error shown above.

The rest of the TodoContext class is mundane implementation which you can read at your leisure.

TodoItem and TodoList entity classes

NoDb is a two entity model application. Both entities are defined as POCO ("Plain old C# classes") in the Models folder.

Each has a few auto-properties followed by a Validate method to ensure data integrity. Notice the lack of System.ComponentModel.DataAnnotations; there is no Entity Framework to interpret the attributes and apply the corresponding validation rules. We'll do that ourselves, by hand, with these Validate methods.

Each has a "navigation property" to access the other. TodoList.Todos returns a Todo list's child items; TodoItem.Todo returns the item's parent Todo list. Note that the TodoItem has a foreign key, TodoListId. That's how the association is implemented in this particular sample; it's not a Breeze requirement per se although Breeze life is a big more complicated and less fun without them FKs.

TodoList is a self-contained object graph. It actually contains its own Todo items, something you might expect from a NoSQL data store but not from a relational database.

The TodoList class also sports methods for adding, updating, and replacing its nested Todo items.

That's all we have to say about the server. What are the implications for the client.

The Client

The NoDb client looks like a typical Breeze client. We'd see no outward signs of difference were we to switch to a more conventional Breeze server backed by Entity Framework and SQL Server. The View and ViewModel would be identical. The DataContext would be identical ... save for four lines of configuration.

datacontext.js

The NoDb server doesn't supply entity metadata to the client. The client is on its own in that department.

The first essential step is to prevent the client from asking for metadata. We can't simply create an EntityManager and start querying with it as we usually do:

var manager = new breeze.EntityManager("api/todos"); // Uh oh ... trouble ahead!
var query = EntityQuery.for("TodoList");

// first query implicitly asks for metadata
manager.executeQuery(query)
       .then(querySucceeded)
       .fail(queryFailed); // 404 - Resource not found

It's going to bomb with a 404 on the first query. Instead, we prepare a BreezeJS DataService object whose hasServerMetadata flag is set false. Then we create a new EntityManager with this configuration and we are good to go:

var dataService = new breeze.DataService({
    serviceName: "api/Todo",
    hasServerMetadata: false // don't ask the server for metadata
});

var manager = new breeze.EntityManager({ dataService: dataService });

// ... proceed with queries and saves

Of course Breeze can't do its job without the metadata that describe the model entities and their relationships. This metadata we must build by hand.

model.js

Look at the bottom of the web page, Index.html

<!-- App libraries -->
<script src="Scripts/app/todo.bindings.js"></script>
<script src="Scripts/app/todo.datacontext.js"></script>
<script src="Scripts/app/todo.model.js"></script>
<script src="Scripts/app/todo.viewmodel.js"></script>

There's a model.js script sitting between the datacontext.js, which creates the application's EntityManager, and the viewmodel.js that launches the user experience.

In that small window of time, model.js can define the application metadata. Many Breeze applications have a model.js script, positioned just like this one, to supplement the server-supplied metadata with client-side logic. Such a script typically extends the EntityTypes with constructors, initializing functions, properties, validations, and behaviors in support of application workflow and presentation.

The model.js in NoDb creates the EntityTypes first and then extends them. The following few lines explain the approach:

// The empty metadataStore to which we add types
var store = datacontext.metadataStore;
addTodoItemType();
addTodoListType();

An EntityManager get its metadata from an instance of the MetadataStore class. The datacontext exposed the application manager's metadataStore for model.js to grab. And it starts adding types to the store.

Let's inspect the addTodoItemType method to see how we define types in metadata.

EntityType

The first step is to create an EntityType object for the "TodoItem":

var et = new breeze.EntityType({
    shortName: "TodoItem",
    namespace: "NoDb.Models",
    autoGeneratedKeyType: AutoGeneratedKeyType.Identity
});

The shortName and namespace combine to exactly match the full name of the type on the server, "NoDb.Models.TodoItem". In BreezeJS, the client and server know the type by the same full name although you can usually refer to the client-side type by its short name, "TodoItem".

Every entity requires a unique key. Does the client or the server define the key for a newly created TodoItem? The AutoGeneratedKeyType.Identity enum tells Breeze that the server will create the permanent key. Breeze generates temporary keys for new entities until they are saved. After saving those entities successfully, Breeze replaces the temporary keys (both primary and foreign keys) with their permanent values.

DataProperty

Now that we know the key management strategy, we add a key property to the type object:

et.addProperty(new breeze.DataProperty({
    name: "todoItemId",
    dataType: breeze.DataType.Int32,
    isNullable: false,
    isPartOfKey: true
}));

A DataProperty describes a property of the entity type that returns a simple value. The constructor takes a hashmap of key/value settings. You must specify your settings in this constructor hashmap; you can read the properties of a DataProperty later but you can't change them. Omitted settings receive default values. See the API documentation for details.

An entity typically has only one key property so the default is false and we had to set it true for this property. As the setting name implies (isPartOfKey), Breeze supports composite keys.

NamingConvention

The client-side property name must correspond to the server-side property name. Unlike the entity type name, it need not match exactly. As we see here, the key property is named "todoItemId" (camelCase) on the client and "TodoItemId" (PascalCase) on the server.

The two names are strictly related by a NamingConvention. By default the client and server property names match exactly. The author of NoDb preferred camel case on the client and set the default convention accordingly in the datacontext.js just before creating the EntityManager; later would have been too late.

breeze.NamingConvention.camelCase.setAsDefault();

Validated properties

Let's add the title property:

 et.addProperty(prop = new breeze.DataProperty({
    name: "title",
    dataType: breeze.DataType.String,
    isNullable: false
 }));
     
 // Add client-side validation to 'title' 
 et.addValidator(
    breeze.Validator.required(), prop);
 et.addValidator(
    breeze.Validator.maxLength({ maxLength: 30 }),prop);

The property definition holds no surprises. But we followed that definition by adding a couple of validation rules called "Validators". We could have added them later (validators are among the few aspects of a property that can be changed later). We added them here as a matter of style.

Navigation properties

A TodoItem has a parent TodoList. A TodoList has zero or more child TodoItems. They are bound by an association. Breeze supports associations with navigation properties defined in metadata.

Let's add a property to "navigate" from a TodoItem to its parent TodoList.

et.addProperty(new breeze.NavigationProperty({
    name: "todoList",
    entityTypeName: "TodoList",
    isScalar: true,  // returns a single parent TodoList
    foreignKeyNames: ["todoListId"],
    associationName: "TodoList_Items"
}));

It is conventional, on both client and server, to name a navigation property after the type it returns. This property returns a single value (isScalar: true) so we call it "todoList".

Breeze relies on foreign keys for navigating among entities in cache. This navigation property's supporting foreign key is "todoListId" (we skipped it here but you'll find it defined in the code sample).

The associationName is semi-optional. It's required only if the entity on the other side has an "inverse" navigation property back to the TodoItem. The parent TodoList has such a property:

et.addProperty(new NavigationProperty({
    name: "todos",
    entityTypeName: "TodoItem",
    isScalar: false, // returns a collection of TodoItems
    associationName: "TodoList_Items" 
}));

Notice that the associationName is the same.

Add the type to the store

Once we've finished adding properties to the TodoItem type - really finished - we add it to the MetadataStore:

store.addEntityType(et);

Once we do this we won't be able to add, change, or remove properties from this type. We will be able to add and remove validators later. We can add a custom constructor and initializer function too ... as we do next.

Add an initializer function

We can add business logic, define default values, and create unmapped properties for any EntityType by register a custom constructor function.

All of the properties we've added were mapped, meaning they correspond to locations in permanent storage on the server and Breeze moves values between these properties and those locations when it queries and saves. Yet sometimes we want an entity to carry information that is not saved permanently and does not correspond to a location in the database.

Unmapped properties can be a good choice for this purpose. Breeze still recognizes them. Breeze will serialize them, send them to the server, and listen for their changes. But it won't try to save them and changes to unmapped properties do not affect the EntityState.

The TodoItem in this app doesn't have custom business logic and doesn't have unmapped properties so it doesn't need a custom constructor. But there is another property that every TodoItem must have, the errorMessage Knockout observable. That's where the application puts validation messages and system error messages when something is wrong with a Todo. Knockout binds the Todo's errorMessage is to the view and updates the view as error messages are added and cleared.

Breeze shouldn't know about this property. It's clearly not a mapped property. It's not unmapped either. Breeze shouldn't serialize it or send it to the server. But we'd like Breeze to add this property to every new TodoItem it makes, whether that Todo is created explicitly or implicitly as a result of a query. We can make that happen when we register an initializer function.

store.registerEntityTypeCtor("TodoItem", null, todoItemInitializer);

function todoItemInitializer(todoItem) {
   todoItem.errorMessage = ko.observable();
}

Learn more about custom constructors and initializers in the "Extending entities" topic.

We're Done!

In this sample we did away with the database, with EntityFramework, and with server-supplied metadata.