For the next release (we don’t yet have a specific date, but it should be within January) we have some fairly big changes coming. These open up some very powerful new capabilities, but there are also some small changes to existing functionality, so we thought it would be helpful to forewarn you.
One of the biggest changes is the introduction of paging. We have this working very nicely now, in house. Return any large collection and Naked Objects will now display a singlepage, and with standard navigation buttons for paging forwards and backwards. The default page size is 20 objects, but you can annotate any given method with a [PageSize(x)] attribute if you wish.
Perhaps more significant is the fact that Naked Objects will be able to recognise IEnumerable<T> and IQueryable<T> (where T is a domain entity type) – not just ICollections. If your method returns an ICollection<T> or an IEnumerable<T> then the entire collection will be retrieved (eacg page refresh) and only the current page viewed. However, if your method returns specifically an IQueryable<T> then the paging mechanism will only retrieve from the database the objects displayed on the current page – which is obviously much more efficient. For this reason, [i]from the next release, we will be strongly recommending that you make all query methods return IQueryable<T>.[/i] This is much more useful anyway as it is easier to write query methods that filter other queries – you can always ToList() them if you need programmatic access to the objects. However, this is optional.
The important change is to the feature that we just released last month: actions contributed to collections. From the next release, such actions will [i]only [/i]be contributed to IQueryable<T> results – [i] they will not be contributed to ICollections[/i]. This to is help reduce the risk of side effects, caused by the ’stateless’ nature of Naked Objects MVC.
For example, let’s say, in the present model, that you had an action contributed to a collection of Payment objects, defined as Print(ICollection<Payment> payments). This is fine if you are invoking it on a collection of payments that you had just retrieved via a query. Now, say, you invoked a method on a Supplier called GeneratePaymentsForAllOutstandingInvoices, returning a collection of Payments. That returned collection would currently offer ‘Print’ as a contributed action. The problem is that, at present, due to the stateless design, Print would first re-invoke GeneratePaymentsForAllOutstandingInvoices, thereby potentially duplicating the payments. (Obviously you can and should guard against duplicated payments in the logic of that method – but it serves as a simple illustration). We don’t want to facilitate accidental side effects like this.
(It has previously been suggested that we might provide an attribute to indicate whether a particular method was [i]idempotent [/i]or not – and this would solve the issue. But many programmers are rightly wary of the risks with that approach.
We have therefore decided to adopt and recommend the following convention:
If a method that returns a collection of objects is a query or other idempotent method then return an IQueryable<T>. As previously stated, for query methods this has big performance advantages. Naked Objects then infers that this method can be safely re-invoked and will contribute appropriate actions to the IQueryable. If your method is not a query or otherwise idempotent, then return an IEnumerable<T> or an ICollection<T> – which is probably more convenient anyway since the collection won’t have arisen directly from a query itself. Then Naked Objects will assume that it is not safe to re-invoke the method and so will not contribute actions. We will be making this distinction clearer at the user interface.
A second benefit from this change, however, is that we can now support ‘covariance’. At present, if you have a Contributed Action that takes a Customer as a parameter then it will also be contributed to sub-classes of Customer (e.g. Store and Individual in AdventureWorks), but if you have a contributed action that takes ICollection<Customer> as a parameter, then this will not be contributed to an ICollection<Store> because ICollection<Store> is not a sub-type of ICollection<Customer>. However in the new model (next release) a contributed action that takes IQueryable<Customer> will also be contributed to an IQueryable<Store> because we have been able to make this covariant.