Domain Model - Collections/Transactions

A

aa7im

I am trying to design a good domain model for a system I am working on
but I am having a hard time trying to figure out how to deal with
relations and transaction...

For example if I create an Invoice object and then add LineItem objects
to the invoice all in memory. How do I get everything to commit to the
database in the right order.

Invoice invoice = new Invoice();
invoice.OrderDate = DateTime.Now;
invoice.Comments = "Thank you for your order";
invoice.ToAddress = "Joe Blow 123 Drive Somewhere,CA.";

InvoiceItem item = new InvoiceItem();
item.Name = "Large Widget";
item.Price = 20.00;
item.Quantity = 5;

invoice.AddItem(item);

item = new InvoiceItem();
item.Name = "Small Widget";
item.Price = 15.00;
item.Quantity = 2;

invoice.AddItem(item);


invoice.Save();



One way is to implement by Save() method like this:

public void Save()
{

MapperClass.SaveInvoice(this);

foreach(InvoiceItem item in Invoice.InvoiceItems)
{
MapperClass.SaveItem(item,this.InvoiceID);
}
}



The problem is that when looking at an existing Invoice object items
like: invoice.InvoiceItems it will return a collection of InvoiceItems
from the database (lazy loaded). I need users of the object model to
be able to insert, update, and delete objects in this collection and
when Save is called figure out what to do...

I can't decide how and where to track the state of objects related the
invoice object. For example when an object is removed from a
collection I still need sitting around somewhere so when I commit to
the database I know to actually remove from the DB. If was completely
removed from the collection entirely I wouldn't have any to know that
it was removed.

Where and how to I track the state of the related objects?

As properties on all my entity objects (I set the properties when items
are added and remove from collections)?

Should my invoice object maintain seperate internal arrays of objects
that need to be removed and inserted for each related object?

I have read about using a "Unit of Work" object, is this a solution to
the problem?


Thanks
 
F

Frans Bouma [C# MVP]

Hey Josh :)
I am trying to design a good domain model for a system I am working on
but I am having a hard time trying to figure out how to deal with
relations and transaction...

For example if I create an Invoice object and then add LineItem objects
to the invoice all in memory. How do I get everything to commit to the
database in the right order.

That's the task of the O/R mapper you're using. Simply assign the
objects to eachother, like invoiceItem.Invoice = invoice; or:
invoice.InvoiceItems.Add(item);
and in the end call invoice.Save();
The problem is that when looking at an existing Invoice object items
like: invoice.InvoiceItems it will return a collection of InvoiceItems
from the database (lazy loaded). I need users of the object model to
be able to insert, update, and delete objects in this collection and
when Save is called figure out what to do...

Objects need state tracking, i.e.: you need to be able to determine
that an entity's data has been altered.
I can't decide how and where to track the state of objects related the
invoice object. For example when an object is removed from a
collection I still need sitting around somewhere so when I commit to
the database I know to actually remove from the DB. If was completely
removed from the collection entirely I wouldn't have any to know that
it was removed.

Where and how to I track the state of the related objects?

This is a tough question to answer. The problem is that there is no
real solution, only a solution within a 'context'. If an InvoiceItem
changes, is the Invoice item also changed ? If the answer is yes, then
every entity change will affect the complete graph of entities in
memory. If the answer is no, you need local state tracking, i.e. for
each related entity you need a hashtable for each version of your
entity. (or other construct, as long as you can store a version of an
entity or entity collection in a construct).

The problem with state tracking in the way you want it is: "What if the
user clicks 'Cancel'?". You have these entities, you open a gui form,
the user makes a lot of changes, but then decides not to go through with
it and clicks cancel. The entities were for example databound to the
controls and are after that useless, because their state is changed.
What to do? With change tracking, you could roll back to a version prior
to the gui form being opened, but that gives problems as well.
Should my invoice object maintain seperate internal arrays of objects
that need to be removed and inserted for each related object?

Often developers keep track of changes on collections with separate
collections indeed, though just for deleted entities from collections,
i.e.: removed entities from collections. You can do two things:
- add the removed entity to a collection and delete that collection in
one go at the end
- simply log a delete action for that particular entity.
I have read about using a "Unit of Work" object, is this a solution to
the problem?

UnitOfWork, as we also support it, is a tracker for changes which are
performed. You can see it as a grouping mechanism of actions performed
on the entities in the scope of the unitofwork. The scope of the
unitofwork can be: all entities read/created with the mapper (generally
the case) or the entities added to the unitofwork. You perform the
actions on the entities and at the end commit the unitofwork. As all
actions are logged in the unitofwork, this will be your only action.

Frans.
 
A

aa7im

Thanks Frans!

I was wondering if you would come across this post. I have been using
your product on alot large-scale applications and it has been working
great. I have not had to worry about implementing all my above issues
becuase they are all solved by your product. However the company I am
currently doing a project for will not use any code-generation tools
(not internally built) which means I am having to create a framework to
essentially accomplish the same things your product already provides
(big waste of time).

Ok... here are the issues I am running into:

Invoice invoice = new Invoice();

InvoiceItem item = new InvoiceItem();
item.IsNew = true; //This property is actually set internally in the
constructor
item.Name = "Large Widget";
item.Price = 20.00;
item.Quantity = 5;

invoice.InvoiceItems.Add(item);

Ok... the confusing part with this is that the "InvoiceItem" object has
a Save() method on it. If I were to execute the Save() method on the
invoiceItem object it would fail because the Invoice object has not
been saved yet (no invoiceID available). So the Save() would only be
used in cases where you want to commit changes to a "Modified"
InvoiceItem object.

The other issue is differentiating between a "new object" and an object
being "new to the collection". In the save method for the invoice
object I must save the invoice then walk through the InvoiceItem
collection looking for "new relations" and then save the object if it
is dirty then create the relation.

Will this work?

//Save method for Invoice
public void Save()
{
InvoiceMapper.Save(invoice);

foreach(InvoiceItem item in invoice.InvoiceItems)
{
if(item.Invoice == null)
item.Invoice = this;
if(item.IsDirty)
item.Save();
}
}
 
F

Frans Bouma [C# MVP]

aa7im said:
I was wondering if you would come across this post. I have been using
your product on alot large-scale applications and it has been working
great. I have not had to worry about implementing all my above issues
becuase they are all solved by your product. However the company I am
currently doing a project for will not use any code-generation tools
(not internally built) which means I am having to create a framework to
essentially accomplish the same things your product already provides
(big waste of time).

... some people...
Hang in there, buddy!
Ok... here are the issues I am running into:

Invoice invoice = new Invoice();

InvoiceItem item = new InvoiceItem();
item.IsNew = true; //This property is actually set internally in the
constructor
item.Name = "Large Widget";
item.Price = 20.00;
item.Quantity = 5;

invoice.InvoiceItems.Add(item);

Ok... the confusing part with this is that the "InvoiceItem" object has
a Save() method on it. If I were to execute the Save() method on the
invoiceItem object it would fail because the Invoice object has not
been saved yet (no invoiceID available). So the Save() would only be
used in cases where you want to commit changes to a "Modified"
InvoiceItem object.

This is a complex issue, it took me a lot of time and pilot projects to
get this right.

In the situation of a Save() method inside an entity, an entity has to
know internally what its depending entities are and what its dependent
entities are. In your example, invoice knows that invoiceItems are
depending entities.

What I do is this: when you add an entity to a collection which is part
of an entity (like you do in invoice.InvoiceItems.Add(item); ) or when
you do: item.Invoice = invoice;, I set up synchronization information
between the two entities. This sync information is used when an entity
is saved and its PK has to be synchronized with related entities, like
when the invoice is saved, its PK has to be written in the FK fields in
all items related to that invoice item. (it doesn't matter where this
info is stored, as long as the engine saving the entity can access it).

When you save entity E, the save routine has to check if the save
routine is recursive. Sometimes you don't want to save recursively, but
say it is the default. Then you first start a transaction. Then you
check which entities are dependent from the POV of the entity to save.
In the case of an invoiceItem, that's its invoice object. This
information has to be stored somewhere, for example inside the
invoiceItem object. (it is sometimes better to store this info inside
the entity itself, so in remoting scenario's you don't lose information
and it can be used completely disconnected from the O/R mapper core).

The save routine sees there are dependent objects, so it adds these to
the transaction and simply calls the save routine of these objects
first. When an entity is saved correctly, it will fire an event, and the
handler of that event handles the synchronization action. When the call
comes back to the original save routine, all dependent objects are
saved, the FK's are synced and the invoiceItem object is ready to be saved.

After an entity is saved you can decide to save the depending objects
before returning. Track the objects in the transaction so you don't get
duplicate visits to the save Save() method of the same entity in the
case of a loop in your object graph.

This is no simple action, it's complex code which is likely to be
scattered all over the place, simply because you have your Save()
routine in the entity itself.
The other issue is differentiating between a "new object" and an object
being "new to the collection". In the save method for the invoice
object I must save the invoice then walk through the InvoiceItem
collection looking for "new relations" and then save the object if it
is dirty then create the relation.

It is wise to implement a Save() like method on a collection as well.
That routine simply checks if an entity is 'dirty' (or new) and calls
its Save() method.

Frans.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top