Migrating Code to UCommerce.EntitiesV2
When we first conceived of Ucommerce years ago there was no Ucommerce HQ and no real plan for what Ucommerce should become. When time came to choose a data access layer we went the RAD route and chose Subsonic for its productivity benefits. Since then Subsonic has been a blessing and a course.
A blessing because it got us off the ground rapidly as intended and it introduced some pretty cool concepts to the Ucommerce API such as LINQ based Active Record.
What really hurt was the complete lack of options for tuning queries for optimum performance. Subsonic is great for throwing together custom queries, but is rather lacking in projecting the needed result into an existing model. Basically this means lots of lots of lazy loading, which hurts performance badly due to the many tiny SQL queries being issued to the database.
Thus we decided to switch to a more full featured ORM and the natural choice at the time was NHibernate.
NHibernate has been present in Ucommerce since the very early days. In fact it was introduced in Ucommerce 1.0.2 to improve performance when querying the catalog via the XSLT API. We’ve slowly migrated bits and pieces of Ucommerce to NHibernate where it made sense over the years.
During the past two years we’ve kept Ucommerce backwards compatible with previous versions to make it simple to upgrade to new versions of Ucommerce. With version 2.0 we didn’t really have that option anymore as we reached the end of what was possible to achieve internally so we decided to migrate the final Ucommerce .NET APIs to EntitiesV2 and NHibernate.
What this means for you and existing code written against UCommerce.Entities, UCommerce.Runtime, and UCommerce.Pipelines will have to be migrated to UCommerce.EntitiesV2.
The following article describes changes required to make your code run against the new API. Completing the migration will give you several benefits:
- A clearer API to work with
- Less code to write
- Better performance
- Caching support
- Single programming model for everything
Ids vs Entities
One of the common patterns you see in Subsonic is the use of id properties to tie object together. This is not possible in NHibernate, which has a more entity focused approach.
NHibernate moves the responsibility of handling the association to the “aggregate root” or in other words to the more logical owner of the child object, e.g. the order handles order line.
var purchaseOrder = SiteContext.Current.OrderContext.GetBasket().PurchaseOrder(); var orderLine = new OrderLine(); orderLine.OrderId = purchaseOrder.OrderId; orderLine.Save();
var purchaseOrder = SiteContext.Current.OrderContext.GetBasket().PurchaseOrder; var orderLine = new OrderLine(); // Notice that the purchase order is responsible for handling the operation purchaseOrder.AddOrderLine(orderLine); purchaseOrder.Save();
Three things are worth nothing here:
- We’re not setting any ids. It’s all handled behind the scenes by NHibernate.
- The order line is added via the AddOrderLine() method, which is a very common pattern in EntitiesV2.
- The purchase order is responsible for saving both the order itself and the order line.
More often than not you don’t want to delete an entire aggregate, but just a portion of it. Subsonic and NHibernate deal with this is different ways.
var orderLine = OrderLine.All().Single(x => x.OrderLineId == orderLineId); orderLine.Delete();
var orderLine = OrderLine.All().Single(x => x.OrderLineId == myOrderLineId); var purchaseOrder = SiteContext.Current.OrderContext.GetBasket().PurchaseOrder; // Notice the RemoveOrderLine patterns. Use it whenever you can to delete. purchaseOrder.RemoveOrderLine(orderLine); purchaseOrder.Save();
As you can see the order is still responsible for handling the delete of the order line. You simply remove the order line from the order with the RemoveOrderLine() method and it will get deleted automatically when the order is saved. Because the operations are not actually carried out until the Save is issued you get the benefit of bulk operations because they will be batched together once you save.
If you don’t use the method, but go the orderLine.Delete() route instead, you’ll often see the following error message, “deleted object would be re-saved by cascade (remove deleted object from associations)[UCommerce.EntitiesV2.OrderLine#2] ==> RemoveEntity”. This happens because the loaded order has a reference to the object and would re-save it to the database.
In Subsonic the pattern for deleting an object is very straightforward: You simply go order.Delete(). The issue with Subsonic is that it has no knowledge of relationships between objects so in many cases simply deleting an order would cause a SQL error because other data would be related to the order like order lines, properties, customer, shipments, addresses, etc.. So you have to know about these related objects and the order in which to delete them. Not very user friendly.
var purchaseOrder = PurchaseOrder.All().Single(x => x.OrderId == myPurchaseOrderId); OrderAddress.Delete(x => x.OrderId == myPurchaseOrderId); Shipment.Delete(x => x.OrderId == myPurchaseOrderId); OrderProperty.Delete(x => x.OrderId == myPurchaseOrderId); OrderLine.Delete(x => x.OrderId == myPurchaseOrderId); // etc. purchaseOrder.Delete();
var purchaseOrder = PurchaseOrder.All().Single(x => x.PurchaseOrderId == myPurchaseOrderId); purchaseOrder.Delete(); // simple, no? :)
NHibernate is smart enough to know about the relationships between objects, so it’ll go ahead and delete related objects where it makes sense so you don’t have to think about it. These are called cascades and it’s the same mechanism, which ensured that our order line got saved in the previous example.
One to One Relationships
An interesting design decision with Subsonic is that it doesn’t expression one to one relationships, e.g. PurchaseOrder has one Customer. Instead Subsonic will treat all relationships as many to many, e.g. PurchaseOrder has many Customers, which makes sense from an ORM implementation point of view, but is very confusing for a developer trying to use an API built on this approach.
Of course NHibernate lets us map data exactly the way we want, which makes the API so much cleaner and simple to understand.
var purchaseOrder = SiteContext.Current.OrderContext.GetBasket().PurchaseOrder; // You have to know that there's only a single customer in the database explicitly. var customer = purchaseOrder.Customers.Single();
var purchaseOrder = SiteContext.Current.OrderContext.GetBasket().PurchaseOrder; // With NHibernate the customer is mapped properly var customer = purchaseOrder.Customer;
Many to Many Relationships
While Subsonic defines everything as a one to many relation it’s not so great a handling many to many relations. Typically these are implemented with a relation table to go between object A and B, like in the case of a category and product relationship where the same product might be present in multiple categories. Subsonic will map the go-between and require more code to handle the relationship.
// Grab a random product and category var product = Product.All().First(); var category = Category.All().First(); // Create the relationship var categoryProductRelation = new CategoryProductRelation(); categoryProductRelation.ProductId = product.ProductId; categoryProductRelation.CategoryId = category.CategoryId; categoryProductRelation.Save();
// Grab a random category and product var category = Category.All().First(); var product = Product.All().First(); // Simply add the product to the category and you're done category.AddProduct(product); category.Save();
As you can see NHibernate completely eliminates the requirement to create the relation object yourself. It will handle the intricacies of the relationship behind the scenes for you.
LINQ to Ucommerce
With the switch to NHibernate LINQ to Ucommerce will gain new options as well. The most obvious change is that you can get away with writing less code than today when doing joins. In many cases you can even get rid of the join altogether.
var query = from product in Product.All() join categoryRelation in CategoryProductRelation.All() on product.ProductId equals categoryRelation.ProductId join category in Category.All() on categoryRelation.CategoryId equals category.CategoryId where category.Name = "My Category" select product;
var query = from category in Category.All() join categoryRelation in CategoryRelation.All() on category equals categoryRelation.Category join product in Product.All() on categoryRelation.Product equals product select product; // Notice that ids are omitted from the query
As you can see there are significant changes between the Entities and EntitiesV2 APIs, but rest assured that migrating to the newer API is very straightforward. Ucommerce Admin, which is a pretty significant codebase written entirely against the Entities API was migrated to EntitiesV2 in three days straight including testing. The benefits we’ve seen from the migration are significant. UIs are more responsive, code required to do a certain operation is clearer, and more often than not there’s less of it.