MedryxORM Part 2 — Server Queries, Client Queries and Named Queries
In my last post, I talked about a system I have built for managing domain objects on the client side, and making communication with services that communicate with the server more opaque to the average developer.
In this post, I would like to further describe this system, which for the time being I have called MedryxORM. Aside from the “free” lazy loading and relationship management you get with this system, you get a simplified method of handling queries. While there is a wrapper that allows for usual “fetch” access, there are several advanced features you get with the ORM.
First, lets look at what is different from a usual fetch and a fetch done from the ORM. Since the ORM manages multiple domain object classes (Managed Classes), you must specify the “root” class of your query. This is analogous to the “FROM” statement in SQL. The results are always a list of Managed Entities of the root class of your query.
Using the native api, you run a query as follows:
entityManager.query("Patient", {query:{lastName:"Smi*"}, sort:["lastName", "firstName"]});
and using the dojo ReadApi you would do:
entityManagerStore.fetch({query:{dischargeDate:null, lastName:"Smi*"}, sort:["lastName", "firstName"], queryOptions:{entityClass:"Patient"});
(I’ve left out some of the details for brevity).
So far, nothing to earth-shattering. But the ORM gives you a couple of neat features that can simplify your queries. Queries can be either client-side or server-side. A client-side query is always a subquery of a server-side query that has already been executed. So you can do filtering and sorting of the server-side query without having to re-query your server or marshall more data back and forth. This is quite useful for most queries — up to a point. If you have an enormous number of records that make server-side paging necessary (i.e., your server query does not return the entire set of records), then client queries don’t work. But this is really an exception to many circumstances. In my world, I will usually have on the order of a couple hundred patients and a couple dozen physicians as my major queries. I can easily load all that data into the client on a single call. But your results may vary.
So how do you set this up? In a further attempt to keep end-developer (i.e. page developer and perhaps widget-developer) usage as simple as possble, we have created a concept of “Registered Queries”. This is similar to PreparedStatements in Java or Stored Procedures in SQL. Or, perhaps even more closly, this resembles NamedQueries in Hibernate.
A Registered Query is simply a query that is created when the Entity Manager is first configured. Its just another field in the mapping of an entity. You register a query in one of two ways:
entityManager.registerQuery("Patient", "AllPatients", {query:{dischargeDate:null}, sort:["lastName", "firstName"]});
or you can include a “queries” property on your mapping:
dojo.declare("model.Patient", null, { firstName:null, lastName:null, //snip... queries:[ {name:"AllPatients", parameters: {query:{dischargeDate:null}, sort:["lastName", "firstName"]}} ] });
Once you have done this, then executing your query becomes even easier:
entityManager.query("Patient", {query:{lastName:"Smi*"}}, "AllPatients");
This will first load all patients from the server with dischargeDate = null, and then filter them on the client to all those whose lastName starts with “Smi”. Suppose you then want to filter on a different name, or sort in a different order? Just run query() again, with your new parameters. This also enables easy client-side paging.
To use the ReadApi, you add a registeredQueryName parameter in either your keywordArgs or query args or query options, and it will map correctly to the EntityManager’s query method.
entityManager.fetch({query:{lastName:"Smi*"}, queryOptions:{entityClass:"Patient", root:"AllPatients"}});
Since the EntityManager is built on the concept of sparsely-populated objects that get their more complete representation lazily or eagerly if you know you will be using them, all query operations also support a “properties” queryOption. This is similar to the properties valuein loadDeferred described in the previous post. You provide a list of properties that you know you will be using. If this is a server-query, then this is passed to the server during the request. However, if this is a client query, and the properties passed are different than those in the server query, then the results of the query are iterated, and each object that is missing a required property has those properties eagerly fetched from the server. This is transparent to the end user. (In fact, if your sub-query contains fields that are not yet loaded from the server, then this loads those in one request prior to running the sub-query). This functionality all assumes that your server can handle a query where the identifier field is passed an array of id’s to load.