Jeremy D. Miller -- The Shade Tree Developer

Sponsors

The Lounge

Wicked Cool Jobs

Syndication

News

Advertisement

Persistence Patterns: Cascading Updates

This content was cut from my most recent MSDN article on Persistence Patterns for length and possibly quality.  What the heck, it's an important topic that throws some people off and it's also something that many people who are new to ORM's frequently don't understand.  I'm also misusing the term "reachability" a bit here, so substitute "cascading save" wherever that term pops in.

 

Normal 0 false false false EN-US X-NONE X-NONE

Many persistence tools support cascading updates and deletes through an object graph.  This is both a powerful tool for improved productivity and a source of confusion and errors.

To explain this concept, let’s imagine that we’re building a new invoicing system that has an Invoice class like this one:

    // The Invoice class is purposely simplified

    // for this example

    public class Invoice

    {

        public IList<InvoiceLine> Lines { get; set; }

        public Customer Customer { get; set; }

    }

The Invoice class has a list of InvoiceLine objects and a “many-to-one” reference to a Customer object.  When our new invoicing system receives a message directing it to enter a new Invoice, our system creates a new Invoice object, all the related InvoiceLine objects, and assigns the proper existing Customer object to our new Invoice.  That process is shown in the CreateInvoice() method of the InvoiceService class shown below:

    public class InvoiceService

    {

        private readonly IUnitOfWork _unitOfWork;

 

        public InvoiceService(IUnitOfWork unitOfWork)

        {

            _unitOfWork = unitOfWork;

        }

 

        public NewInvoiceConfirmation CreateInvoice(NewInvoiceMessage message)

        {

            // Read in the NewInvoiceMessage object, create

            // the children InvoiceLine objects,

            // and attach an existing Customer object

            Invoice invoice = buildInvoiceHeader(message);

            invoice.Lines = buildInvoiceLines(message);

            invoice.Customer = lookupExistingCustomer(message);

 

            // Persist the new invoice

            _unitOfWork.MarkNew(invoice);

 

            // Commit the transaction

            _unitOfWork.Commit();

 

            return buildConfirmation(invoice);

        }

    }

In order to finish the new Invoice transaction, we need to save the header information for the Invoice object, the reference from the new Invoice to the existing Customer, and each of the individual InvoiceLine objects.  However, in the code above for the InvoiceService.CreateInvoice() method we only marked the top Invoice object as a new object in our Unit of Work.  That’s because we’re depending on our persistence tool to use a cascading update policy to walk the Invoice structure and save all the new InvoiceLine objects at the same time.

In my last article I talked about using Object Relational Mapping to create a persistence solution by configuring a mapping between the object model and the database structure.  For the Invoice class in our new invoicing system, I would configure the mapping from Invoice like this (using Fluent NHibernate):

    public class InvoiceMap : ClassMap<Invoice>

    {

        public InvoiceMap()

        {

            // The Cascade.All() directive forces NHibernate

            // to save the InvoiceLine children of an Invoice

            // object whenever the Invoice object is saved

            HasMany<InvoiceLine>(x => x.Lines).Cascade.All();

 

            // The Cascade.None() directive tells NHibernate

            // NOT to save the Customer object when

            // the Invoice is saved. 

            //

            // The link from Invoice to Customer will be persisted

            References(x => x.Customer).Cascade.None();

        }

    }

In this example, we’ve made the InvoiceLine children of an Invoice object “reachable” when a new Invoice object is saved.  In the case of our new invoicing system, I’m assuming that new Invoices can only be created for existing Customer objects.  Because the Customer object isn’t modified by creating a new Invoice, we certainly don’t want the Customer object saved (or even checked by our persistence tool for possible modifications) when a new Invoice object is saved. 

Because of the potential for errors due to incorrect assumptions about how cascading updates are configured, my strong advice is to use a battery of integration tests against your service layer to ensure that data is persisted correctly.  Tightening up the rules and policies for cascading inserts, saves, and deletes is a common way to optimize the performance of a system that uses Object Relational Mapping for persistence.  Again, this optimization process will be much safer if it is supported by automated tests that can catch regression errors caused by changing “reachability” rules.

Lastly, almost every business application of any size that we build will contain hierarchical information.  Reachability is another great example of how an ORM persistence tool can save development time versus old fashioned “hand-rolled” data access code.  In this case, NHibernate is dealing with running the insert, update, or delete statements in the correct order and dealing with moving the new autonumber Id's from the parent to the children records.  Believe it or not, I do know how to write that ADO.Net code from scratch and even how to write sproc's to do the data access, but call me lazy and shiftless 'cause I'd rather just go _unitOfWork.Save( myNewInvoice ) or _unitOfWork.Delete( myOldInvoice ) and get on with the next feature.

 


Posted Sat, May 30 2009 5:34 PM by Jeremy D. Miller

[Advertisement]

Comments

DotNetShoutout wrote Persistence Patterns: Cascading Updates - Jeremy D. Miller - CodeBetter.Com
on Sun, May 31 2009 1:50 AM

Thank you for submitting this cool story - Trackback from DotNetShoutout

Frans Bouma wrote re: Persistence Patterns: Cascading Updates
on Sun, May 31 2009 6:04 AM

"Because the Customer object isn’t modified by creating a new Invoice, we certainly don’t want the Customer object saved (or even checked by our persistence tool for possible modifications) when a new Invoice object is saved.  "

Isn't that just a necessity for snapshot-based O/R mappers? (which check data of original and passed in). O/R mappers which do change tracking on the fly simply check a flag in an entity class instance and if it's false, they move on. However, there's a catch: order is not new and not touched, and you assign it to a new customer. (just for the example). This gives a new FK value in the target row. However 'order' isn't new nor changed. The o/r mapper still has to take this into account.

However I don't see the reason for your requirement for a large battery of tests for cascading saves. Isn't that something the o/r mapper should be taken care of? I mean: if the o/r mapper can't do a normal topological sort on a graph where it check if an entity is changed or WILL be changed due to an Fk sync, why bother with such an o/r mapper? (i.o.w.: these cascading update mapping settings should be unnecessary)

#.think.in wrote #.think.in infoDose #31 (24th May - 1st June)
on Mon, Jun 1 2009 9:13 AM

#.think.in infoDose #31 (24th May - 1st June)

Add a Comment

(required)  
(optional)
(required)  
Remember Me?
Devlicio.us