Many applications need only one EntityManager. Some applications should have more than one EntityManager. This topic explains when multiple managers are advantageous and how to create multiple managers when you need them.
Many applications will never need more than one EntityManager. All application views can share the same EntityManager and its cache of entities. See "Share an EntityManager" for guidance on a single manager approach.
Your application may have several "islands of functionality" that operate semi-autonomously. Each island is its own domain with its own domain model and may even have its own remote service and database. Despite their functional independence, the islands "cooperate" under the umbrella of an apparently unified application. The user can hop from island to island via a top level structure such as a toolbar, accompanied by information that smoothes the transition. Seamless transitions are a sign of a successful federated application.
In this architecture, each "island" typically has its own EntityManager with its own cache of domain model entities. You could define a distinct datacontext for each island and follow the single manager approach for each datacontext.
The single manager is fine when every view - whether of the whole app or within an island - should see the same entities in exactly the same state.
Suppose the application displays an alphabetical list of customers in a Customer List View. There are many customers, most of them scrolled off screen.
The user clicks on "Acme" and is shown a Customer Detail View. The user changes the customer name from "Acme" to "Worldwide Acme". She does not save ... not yet. Instead she switches back to the Customer List View. "Acme" no longer appears on screen. Where did it go? Panic!
The application has only one EntityManager. All views share the same customer instances. The changed customer name was instantly reflected in all views. The name now begins with "W" instead of "A" and has sorted to the bottom of the customer list ... off screen somewhere. Is that OK?
Some people don't think so. They want to isolate pending changes until they are saved. Well the same customer instance cannot be both "Acme" and "Worldwide Acme". Breeze forbids two entities in the same cache with the same key.
The most reasonable alternative is to create two EntityManagers.
The Customer List View is always bound to entities in one manager, the main manager, that holds unmodified customer entities.
When the user selects "Acme", the application creates a Customer Detail View associated with a second EntityManager. That second EntityManager is the "sandbox" manager. The application makes a copy of the "Acme" entity from the main manager (using the exportEntities method) and "pastes" it into the sandbox EntityManager (using the latter's importEntities method). Then the Customer Detail View binds to the copied "Acme" customer in the sandbox manager.
The two "Acme" customer instances in each manager have the same key but they are distinct instances in separate managers and their values may now diverge. When the customer in Customer Detail View becomes "Worldwide Acme", the customer in the Customer List View remains "Acme".
The customer in Customer Detail View has been "sandboxed"; we say that the Customer Detail View is a "sandbox editor". Every change to the sandboxed entity stays in the sandbox editor. We can make all kinds of changes here without affecting the rest of the application. If we don't like them, we can cancel them and discard both the Customer Detail View and the sandbox EntityManager. If we like the changes, we'll save them.
It should not surprise you that after saving "Worldwide Acme", the Customer List View still shows "Acme". The main EntityManager doesn't know about the customer name change in the sandbox manager. It would learn about the name change if it re-queried the database. But that may not happen for a long time and the application shouldn't require a refresh from the database when the updated customer is available locally. Therefore, most applications will propagate the changed entity back to the original EntityManager, typically by sending a message within the application. The main manager might hear that message and import the updated customer from the sandboxed manager. The Customer List View can now display "Worldwide Acme".
We'll show you how to create the second EntityManager after we consider one more scenario.
In many apps - whole or island - there is a single workflow. The user reviews a list of Customers, picks one, reviews that Customer's orders, perhaps adds a new order or edits an existing order. The user moves linerarly through the domain, drilling in, stepping back out ... but always moving along one path at a time. A single EntityManager works well for such apps; some will prefer two managers: a master permanent manager and a second, transient manager for sandbox editing. That's all they'll need ... one or two managers.
But some businesses can't operate that way. The application user must be able to juggle several workflows at once, each moving at its own pace on an independent course. For example, the user may be building Customer Acme's new order for widgets when a call comes in for a high priority change to Customer Beta's order for gizmos. Acme's order-in-progress is not ready to be saved (it won't even pass validation). Fortunately the user can set Acme's order aside and switch to Customer Beta. Beta's gizmos order is revised and the user is about to save it when the supervisor interrupts with an urgent (immediate) demand to issue a refund to Customer Gamma for defective doodads shipped last week. It's that kind of day. The user issues Gamma's refund, then saves the updated to Beta's gizmos, and finally resumes building Acme's widget order.
We have multiple workflows in flight. The Beta and Gamma requests came out of nowhere and who knows how many more interruptions will delay the completion of Acme's order. A single EntityManager is probably not a good idea for this application. We had to save updates for the Beta and Gamma customers. But these two updates should be separate saves in separate transactions. If the app naively called saveChanges() on a single EntityManager, it would try to save Acme's, Beta's and Gamma's changes in a single transaction ... wrong ... which would fail anyway because Acme's order can't pass validation.
You could be clever and cherry-pick three separate baskets of entities (Acme's, Beta's, Gamma's) for three separate saveChanges() calls; in Breeze you can pass a list of entities to saveChanges(). We advise against this approach. The complexity of it always overwhelms the best intentions.
We recommend instead that you use a separate EntityManager instance for each mini-workflow.
When you need multiple managers you generally want to create them the same way. Most of the time (but not always!) they should be configured to communicate with the same remote service and to rely on the same domain model definitions.
We think an EntityManager factory is a good way to achieve this goal. We favor creating a singleton application service called the "EntityManagerProvider". This component offers a createManager method which the application calls whenever it needs a new EntityManager instance. Here is a "simple" example of this approach
app.entityManagerProvider = (function (model) { // ... configure the application for Breeze ... var masterManager = new breeze.EntityManager(applicationServiceName); // configure the metadataStore with entity type extensions model.configureMetadata(masterManager.metadataStore); var entityManagerProvider { masterManager: masterManager, // you may prefer to hide this createManager: createManager, initialize: initialize, refresh: refresh, // ... specialized manager factories, imports, exports, events, ... } return entityManagerProvider; function createManager() { var manager = masterManager.createEmptyCopy(); // same configuration; no entities in cache. // ... copy in some entities (e.g.,picklists) from masterManager return manager; } function initialize() { // load the masterManager with lookup entities and any other startup data // incidentally loads the metadataStore with metadata from the service // return a promise } function refresh() { // refresh masterManager cached entities // typically a subset of the initialize function } })(app.model);
We like to create a master EntityManager, the masterManager, as a template for minting the "derived" managers that we'll need throughout the user session. We create the masterManager with the route to the application's remote service (the "applicationServiceName"). Review the EntityManager API for other configuration options.
Most applications at this level of sophistication also extend the entity model with special client-side properties and behavior. This sample code assumes you've written a module dedicated to this purpose called model and it has a configureMetadata method. We're telling it to extend the masterManager's MetadataStore which will be shared by all of the dervived EntityManagers.
The createManager method returns a copy of the masterManager minus the entities. That copy may be all you need or want from the masterManager. But remember, each manager is its own microcosm. Entity navigation (e.g., Order.Status, Order.Customer, Customer.Orders) is possible only among entities within the same EntityManager. You won't be able to navigate from an Order in a derived manager back to a Status in the masterManager. Therefore, it's often convenient to populate the new manager with some of the picklist data from the masterManager.
You'll know which supporting entities to copy based on the needs of the "sandbox" or subsidiary workflow. In time you may add custom "createManager" methods, tailored to suit the different types of sandboxes and workflows in your application.
The initialize function is optional but highly recommended. Most applications acquire some data from the server, such as picklist data for comboboxes, before they open up the UI to user input. The masterManager is a good place to hold this data. The application bootstrapper - the component that jumpstarts the application upon launch - is the likely caller of this initiialize function.
You may choose to keep the masterManager private, hiding it from all other application modules. We tend to expose the masterManager but treat it as a read-only repository of master data. We forbid direct changes to entities in its cache by other application modules. Master entity data may only be touched by
This code sample is intended to inspire you. You may be able to use it "as is". You'll probably have to add features we omitted for brevity such as the importing and exporting of entities.
We always recommend encapsulating an EntityManager within a datacontext. Every new sandbox and workflow should have its own datacontext instance. Each datacontext wraps its own private EntityManager.
You might define a DataContextService to manage the creation, coordination, and destruction of the many DataContext instances. That service might hold the only reference to the entityManagerProvider, calling upon it to create new managers for its new DataContext instances.
We've got sandboxes and mini-workflows. We've got a DataContextService using an EntityManagerProvider to create multiple EntityManagers to stuff inside the multiple DataContexts that provide isolated entity caches for these sandboxes and mini-workflows. After a save, a datacontext sends a message announcing which entities it saved. Other datacontexts hear the message and import the saved entities into their caches.
Can we make this any more complicated?
Seriously, you do not need this machinery on the first day. Or the second. You may not need it ever. We suggest that you re-read "Share an EntityManager". Start there ... and stay there ... until the business requirements demand more sophistication. Then ... and only then .. should you introduce the advanced techniques described here.
These techniques are sound. They've proven themselves in many applications over many years. They'll be waiting for you when you need them.