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

<!–
/* Font Definitions */
@font-face
{font-family:”Cambria Math”;
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:1;
mso-generic-font-family:roman;
mso-font-format:other;
mso-font-pitch:variable;
mso-font-signature:0 0 0 0 0 0;}
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;
mso-font-charset:0;
mso-generic-font-family:swiss;
mso-font-pitch:variable;
mso-font-signature:-1610611985 1073750139 0 0 159 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-unhide:no;
mso-style-qformat:yes;
mso-style-parent:”";
margin-top:0in;
margin-right:0in;
margin-bottom:10.0pt;
margin-left:0in;
line-height:115%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:”Calibri”,”sans-serif”;
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:Calibri;
mso-fareast-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:”Times New Roman”;
mso-bidi-theme-font:minor-bidi;}
.MsoChpDefault
{mso-style-type:export-only;
mso-default-props:yes;
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:Calibri;
mso-fareast-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:”Times New Roman”;
mso-bidi-theme-font:minor-bidi;}
.MsoPapDefault
{mso-style-type:export-only;
margin-bottom:10.0pt;
line-height:115%;}
@page Section1
{size:8.5in 11.0in;
margin:1.0in 1.0in 1.0in 1.0in;
mso-header-margin:.5in;
mso-footer-margin:.5in;
mso-paper-source:0;}
div.Section1
{page:Section1;}
–>

/* Style Definitions */
table.MsoNormalTable
{mso-style-name:”Table Normal”;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-qformat:yes;
mso-style-parent:”";
mso-padding-alt:0in 5.4pt 0in 5.4pt;
mso-para-margin-top:0in;
mso-para-margin-right:0in;
mso-para-margin-bottom:10.0pt;
mso-para-margin-left:0in;
line-height:115%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:”Calibri”,”sans-serif”;
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:”Times New Roman”;
mso-fareast-theme-font:minor-fareast;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;}

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.

 

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://weblogs.asp.net/fbouma Frans Bouma

    “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)

  • http://www.backlinkschecker.ws/backlinks/buylinks.html Purchase High PR Backlink

    This is improtent article.