New Posts New Posts RSS Feed: Conditional Object Saving?
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Conditional Object Saving?

 Post Reply Post Reply
Author
chuckc View Drop Down
Groupie
Groupie


Joined: 27-Feb-2010
Posts: 54
Post Options Post Options   Quote chuckc Quote  Post ReplyReply Direct Link To This Post Topic: Conditional Object Saving?
    Posted: 21-Apr-2010 at 8:50pm

Consider a typical WPF application where you display a screen with contact information.  On the screen are multiple textbox controls bound to, for example, Contact.FirstName, Contact.LastName and Contact.Company.Name.  The Name field on Company is required, but the Company property/FK relation is nullable.  The user supplies input to the LastName and FirstName fields, and leaves the Company.Name field empty, since this particular contact has no company affiliation.

How would you recommend handling this situation to allow Contact to save, while recognizing the Company should not be saved (in fact, cannot be saved since a value for Name has not been provided)?

I show my very standard Create methods for Contact and Company below.

 In the Contact class:

 public static Contact Create(IbEm.EntityManager manager)

        {

            Contact contact = manager.CreateEntity<Contact>();

            contact.ContactId = Guid.NewGuid();

            contact.EntityAspect.AddToManager();

 

            contact.Company = Company.Create(contact.EntityManager);

 

            return contact;

        }

 

In the Company class:

 

        public static Company Create(IbEm.EntityManager manager)

        {

            Company company = manager.CreateEntity<Company>();

            company.CompanyId = Guid.NewGuid();

 

            company.EntityAspect.AddToManager();

            return company;

        }

 

If we create a Company object as part of the Contact creation, how do we know not to save it when it has no Name value?  I have considered calling Company.EntityAspect. AcceptChanges() as part of my Contact Create method and implementing logic in a Saving event handler to call SetAdded as necessary, but that creates a problem since Contact.CompanyId now has a value with no corresponding Company record.

I would expect this type of situation to arise frequently when dealing with non-trivial binding scenarios, so I am looking for a broadly applicable technique.

Thanks for your help.

Back to Top
WardBell View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 31-Mar-2009
Location: Emeryville, CA,
Posts: 338
Post Options Post Options   Quote WardBell Quote  Post ReplyReply Direct Link To This Post Posted: 22-Apr-2010 at 2:32pm
Great question!
 
My answer, as you will see, is never create the company unless you mean it.
 
If, as here, a Contact is not required to have a Company, don't create one until (and unless) you are certain that the Contact should have a Company.
 
That means you should not expose the Contact.Company entity to binding!
 
As you've discovered, you get into all kinds of trouble trying to manage whether you do or do not have a Company. Avoid this altogether.
 
Instead write yourself a "ContactCompany" class in support of this editor. Note that ContactCompany is NOT an entity. It isn't part of your domain model either. It is a helper class for this particular corner of your UI.
 
Let me assume that you are using the MVVM (Model / View / ViewModel) pattern for your editor.
 
The ViewModel (VM) would have a Contact and a ContactCompany property. When your VM acquires the Contact (prior to presenting it to the user), it instantiates a ContactCompany object and populates it according to whether the Contact has ... or does not have ... a Company.
 
Your view binds to both the VM.Contact and the VM.ContactCompany properties. The view widgets bind to these objects as appropriate.
 
Important: the "Company" related widgets bind to the ContactCompany, NOT to Contact.Company!
 
When the user does something that would trigger you to "commit" the input, the VM asks the ContactCompany to commit. The ContactCompany.Commit might do one of the following:
 
1) If the Contact.Company pre-existed, update it based on values entered by the user that are now in ContactCompany 
 
2) If there was no Contact.Company entity before and the user entered valid Company info, your ContactCompany object creates a new Company (but see note below), populates it, and adds the new Company to the Contact.
 
3) If the user did nothing to indicate that there should be a Contact.Company ... do nothing. No residual entity effects to worry about.
 
4) If there was no pre-existing Contact.Company and the user had entered some company info, causing there to now be a new Company (not yet saved) per #2 .... and then the user erases the company name ... your ContactCompany will have to clean up. Most likely it will detach the new Company it created from the Contact ... and then delete that Company.
 
5) If there was a pre-existing Contact.Company and the user erases the ContactCompany Name and the Contact.Company existed ... well you have to decide what that means :-). 
 
If you allow it ... and this means that the Company should be deleted (are you sure?) ... then ContactCompany detaches the Company and "deletes it" (which means that it will be deleted from the database when you save).
 
6) After #5, (a) the user changes her mind and wants to keep the company or (b)  the user starts entering new company information .... you have another decision to make. 
 
You might decide to undo the deletion of the previous Company and apply the user's changes to this resurrected Company.
 
Or you might decide to proceed as in #2 ... in which case you have an old company slated for deletion (#5) and a new company ready for insertion (#2).
 
The complexity represented in #6 can be eliminated if you save the user's work when the user commits any of #1 - #5.
--
 
As you see, I'm asking you to encapsulate all of these UI decisions inside your ContactCompany class. None of them have to do with the domain model per se; they aren't "business logic". They are the logic of how your UI works. You are gathering user input ... learning what the user wants you to do.
 
Of course the user's wishes expressed through that UI logic will result in changes to the domain model; but that is different from logic intrinsic to the model.
 
The distinction between changes to the model and logic in the model is crucial. I hope it helps you to see why you want to delay creating/deleting/updating the Contact's Company entity until the user's intentions are clear.
 
--
 
I also recommend that the logic for creating new Contact and Company entities be kept out of the UI. This logic belongs either in the entities themselves (as you have shown in your static factory methods) or in methods of a separate factory class. Entity creation is a concern of the Domain Model, not the UI. 
 
More generally, I firmly believe that NO CLASS IN THE UI LAYER should ever refer directly to the EntityManager and certainly never ask it to create something. I strongly encourage you to move queries and saves and maybe more complexe entity creation into a helper class such as a Repository or a "DataService" class.


Edited by WardBell - 22-Apr-2010 at 2:36pm
Back to Top
chuckc View Drop Down
Groupie
Groupie


Joined: 27-Feb-2010
Posts: 54
Post Options Post Options   Quote chuckc Quote  Post ReplyReply Direct Link To This Post Posted: 25-Apr-2010 at 11:56am

Thanks for your thoughtful reply.

I agree wholeheartedly that the logic for creating and managing Entities should not be in the UI;  my hope is to include it in the entities themselves.  A Repository is great for keeping a clean separation of domain and data access, but is not really what I was hoping to learn from my question.  Also, the repository mediated solution you advocate does not address how to handle some minor variations on the scenarios you describe.  For example, unless I remove the relationship between Contact and Company, Company it is still exposed as a property on Contact and potentially subject to direct manipulation elsewhere in the application, such as clearing its Name property.  I need to handle those kinds of scenarios too.
 
My architecture creates a new EntityMangager in each ViewModel, leveraging IEditable to handle undo/cancel type of activities you mention in your item 6) above.  Items 1 – 5 I want to handle in the entities themselves, possibly in an override of EndEditCore. 
 
BTW, I’ve noticed that EndEditCore is automatically invoked when EntityManager.SaveChanges is called.  This is convenient for me, though somewhat unexpected;  is this intentional?
 
Below is an approach that tests successfully – I’d appreciate any feedback.  Note that I injected a custom EntityBase class whose sole purpose is to track New state.  I was unable to find a way to track “dirty” independent of “new” with the existing EntityState features – am I missing something?
 

Basically what I am doing is setting a Company to “new” and “not dirty” when initially created, but then breaking the Contact/Company relationship at Contact save time if it is still new and unchanged.  I plan to deal with the scenario of clearing an existing Company name, etc. through validation rules.  This will employ significant logic involving other aspects of Contact/Company not discussed here.  And regardless of any wrapper layers, I believe that kind of validation checking belongs directly in the model objects, as the Validation subsystem seems to provide for.

Contact.cs

namespace DomainModel

{

    public partial class Contact : EntityBase

    {

        public static Contact Create(iCatalystEntityManager manager)

        {

            Contact contact = manager.CreateEntity<Contact>();

            contact.ContactId = Guid.NewGuid();

            contact.EntityAspect.AddToManager();

 

            contact.Company = Company.Create(manager);

 

            return contact;

        }

 

        /// <summary>

        /// Called upon SaveChanges regardless of whether BeginEdit was called previously or not.

        /// </summary>

        protected override void EndEditCore()

        {

            base.EndEditCore();

 

            if (!Company.EntityAspect.HasChanges() && Company.IsNew)

            {

                // Break the Contact-Company link

                CompanyId = null;

            }

        }

    }

}

 

Company.cs

namespace DomainModel

{

    public partial class Company : EntityBase

    {

        public static Company Create(iCatalystEntityManager manager)

        {

            Company company = manager.CreateEntity<Company>();

            company.CompanyId = Guid.NewGuid();

 

            company.EntityAspect.AddToManager();

 
            // Initialize to a "new" and "not dirty" state

            company.EntityAspect.AcceptChanges();

            company.IsNew = true;

 

            return company;

        }

    }

}

 



Edited by chuckc - 25-Apr-2010 at 8:15pm
Back to Top
WardBell View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 31-Mar-2009
Location: Emeryville, CA,
Posts: 338
Post Options Post Options   Quote WardBell Quote  Post ReplyReply Direct Link To This Post Posted: 26-Apr-2010 at 1:47am
 
I am a little confused by your response. I thought I answered your question directly with an extended discussion of using a surrogate for the Company associated with the Contact ... a ContactCompany helper class.
 
The word "respository" doesn't appear until the last sentence ... and in a manner that does not pretend to speak to the matter of protecting entities from unwanted changes.  I'll take that up below.
 
I do not know if creating a new EntityManager (EM) for each VM is a good idea in your application. Certainly not something I have ever done nor would typically choose to do.
 
I'm talking specifically about the granularity of VM. I agree that an EM is a terrific sandbox for accumulating and isolating a user's changes in one workflow from the changes that user is making in another workflow taking place "at the same time". A travel agent, for example, could be working on several bookings at once, each of them progressing independently and each of which may either be saved or discarded independently.
 
--
 
Calling EndEditCore is intentional. You could call EM.SaveChanges any time. If you were in the middle of an edit and the UI has not committed its changes, we're in a tough spot. Do we commit the changes for you? We figure that's what you want us to do. So we do it.
 
But I wouldn't count on this being called by EVERY SaveChanges. In principle it need only be called if something had called BeginEdit first ... and only certain UIs do that.
 
I think you only want to perform special logic to evaluate whether this is a real Contact Company during the Save. The proper way to do that is to handle the EntityManager.Saving event.
 
You want to learn about that event. If you intend to validate entities on the client before save, you have to write a Saving handler that iterates over the entities-to-be-saved and validates them. Client-side validation is not automatic (as it is on the server) ... not as I write this although we may add this feature in future.
 
--
 
YIKES!
 
I cannot see how your Company.Create method can work. You create a new Company and then you pretend (via "AcceptChanges") that it exists in the database. If you subsequently change the company and then try to save, you will cause DevForce and EF to attempt a database UPDATE for a table row that doesn't exist. This should fail.
 
If this code actually works, it must be because of something I am not seeing.
 
Meanwhile, even if it did succeed, the Company.IsNew flag is still true. After a save, something should set it false. I don't see what does that.
 
And then there is the problem that you have not destroyed these new Companies that you are creating and then detaching from Contact. They linger on in the EntityManager, unnamed and Contact-less. Harmless now, perhaps, but looks like a landmine to me.
 
Please don't do it this way. See below.
 
--
 
EntityAspect.EntityState tells you if the entity is new (is "added") but it doesn't tell you if someone has made changes since you created the entity.
 
I've seen interest in that distinction before ... usually in the context of this same intent of yours to create a related entity but keep that entity only if the user "filled it in".
 
Suppose you don't like my ContactCompany idea. Suppose you prefer to create a Customer, defensively, and then delete it if it is never used. May I suggest another approach?
  1. Add an internal "IsNewAndUnchanged" flag to the Company; it will be false by default. Do not make it public.
  2. Set it "true" at the end of your Company.Create
  3. Set it "false" if and when there is any subsequent change to this Company entity
  4. In the EntityManager.Saving event handler, ask the Contact to prepare itself for save (e.g., validate itself)
  5. Such preparation can test its Company's IsNewAndUnchanged flag; if that is true, you call contact.Company.EntityAspect.RejectChanges() and it will go away.
  6. You should have a validation rule that prevents saving of a Company for which this flag is true without regard for whether it is the child of a Contact; chances are you don't want such a thing in your database.
Observe that this flag is always false for queried and saved entities. It can only be true when you have created a Company and not touched it.
 
Don't bother with breaking the relationship or killing off this Company until you are ready to save. Forget about EndEditCore.
 
You are probably wondering how to implement step #3. How do you know if there is a subsequent change to the Company?
 
I can think of several ways but the best, I think, is to attach an event handler to the PropertyChanged event in Company and clear the flag. If you want to get fancy, your event handler can detach itself from the event once it has cleared the flag.
 
Alternatively, you could override the OnPropertyChanged method and clear the flag there (sure you'll clear it forever for every Company but the cost is negligable).
 
--
 
You wrote:
"Company it is still exposed as a property on Contact and potentially subject to direct manipulation elsewhere in the application, such as clearing its Name property.  I need to handle those kinds of scenarios too."
 
If this is your worry than you may want to hide the Contact.Company property and drag the ContactCompany helper into the Contact class. I still think this is better than incorporating all that conditional code (my 1-6) inside the Contact; it really doesn't have anything to do with Contact per se; the concerns are about plumbing of the related Company. I wouldn't want all that code gunking up my Contact entity.
 
Let's step back a second though. Are you really worried about  the "potential for direct manipulation" of related enties all over the application? You asked about how the UI could be built such that it would not require you to deal with phantom "new Company" objects. I responded to that one use case.
 
But protecting an entity against unwanted manipulation of its related entities by non-UI components? If you're really worried about that , you may be using the wrong technology. You might prefer something like Command Query Responsibility Separation (CQRS) in which you query for read-only DTOs and you always edit and save with other DTOs.
 
You'll be chucking out the baby with the bathwater in my opinion; it's a lot more work than its proponents acknowledge. But it gives you the potential for the kind of safety that you're talking about without having to play games with creating and deleting entities.
 
I don't know. I'm a little concerned that you'll over-architect this and end up with giant entity classes crowded with plumbing.
 
Please remember that you asked for my opinion ... which you are free to take or not.
Back to Top
chuckc View Drop Down
Groupie
Groupie


Joined: 27-Feb-2010
Posts: 54
Post Options Post Options   Quote chuckc Quote  Post ReplyReply Direct Link To This Post Posted: 26-Apr-2010 at 7:21am

Thanks for your feedback – and your opinions!

I lumped the ContactCompany into a ContactRepository in my mind – sorry for the confusion.

The use of a new EntityManager per ViewModel to encapsulate a UnitOfWork seems reasonable; particularly since CheckpointManager has been deprecated.  The goal is to isolate changes to data bound entities occurring in a popup dialog (for example) from the main entity cache until the user commits those changes (by selecting OK on the popup).  Of course this wouldn’t be necessary in a ViewModel that is presenting read-only data, but if it’s not a performance burden, I’d prefer to incorporate it into a view model base class.  Does that sound reasonable?

EndEditCore is indeed being called at save time (in my tests at least) without any previous call to BeginEdit, but if that isn’t guaranteed, I won’t rely on it.  I’m already iterating over the entities to be saved in an EntityManger.Saving event handler for other things, so adding a virtual PrepareSave method on my EntityBase class and calling it during save should get the job done.

Ahh – the connection between AcceptChanges and subsequent UPDATE versus INSERT actions is good to know.  My half-baked testing thus far was only focusing on various create scenarios.  And I didn’t realize that calling RejectChanges() on one entity affects other related entities (i.e. automatically clearing Contact.CompanyId when Company.EntityAspect.RejectChanges is called)  - that is much cleaner – thanks!

My focus on keeping the entity management code internal to the entities is driven partly by our development scenario.  A developer on the team could slap a list of Contacts into an editable grid and expose something like Company.Name to the user.  If I can make it behave as expected, allowing edits and throwing standard validation errors, it makes that developer’s life easier.  They’re not going to need to be concerned whether they can bind to Company or have to use ContactCompany instead, and they won’t see (or maintain) the gunk inside the entities that makes it work.  And, so far at least, I don’t see this sort of thing lead to any worse level of complexity in the entity classes than typical verification/validation.

Thanks again for your insights – much appreciated!

Back to Top
WardBell View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 31-Mar-2009
Location: Emeryville, CA,
Posts: 338
Post Options Post Options   Quote WardBell Quote  Post ReplyReply Direct Link To This Post Posted: 26-Apr-2010 at 9:13am
We are making progress!  I want to comment on other points but first an alert.
 
I would not have guessed that
 
"calling RejectChanges() on one entity affects other related entities (i.e. automatically clearing Contact.CompanyId when Company.EntityAspect.RejectChanges is called) "
 
Have you tested this? Are you sure?
 
Obviously, I did not expect this. I  thought that you had to clear the Company from Contact. Actually, it makes sense to me that we would do that for you Perhaps we're working smarter than I thought. Maybe I even wrote about this. Feeling senile today.
 
Fun to discover something about one's own product ;-)


Edited by WardBell - 26-Apr-2010 at 9:13am
Back to Top
WardBell View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 31-Mar-2009
Location: Emeryville, CA,
Posts: 338
Post Options Post Options   Quote WardBell Quote  Post ReplyReply Direct Link To This Post Posted: 26-Apr-2010 at 9:25am

A new EM in the popup VM makes sense. But what about other scenarios? Many of us divide a complex screen into multiple views, each supported by its own VM. These VMs interoperate and it is convenient (not to mention performant) when these VMs share a common UoW.

I would not put this in my ViewModel. I don't like persistence concerns in my ViewModel anyway; makes them hard to test. I inject the persistence machinery (typically a Repository) during production and supply a fake during testing.
 
You can shape your VMs so that you get a new Repository with each instantiation of some of them and you get a preexisting one for instantiation of others.
 
I caution you about building complex base classes with lots of dependencies. "Favor composition over inheritance" they say ... and they are wise to say it.
 
I see your point about pulling the Contact Company management into the Contact class. I think I would still delegate it to a ContactCompany class; to each his own.
 
And thanks for switching to the Saving handler. That's why it's there. EndEditCore is strictly related to a particular edit cycle of which there may be many before you get to the Save. You may have a rollback along the way too. I'll sleep better knowing you moved the logic.
 
Regards, W
Back to Top
smi-mark View Drop Down
DevForce MVP
DevForce MVP
Avatar

Joined: 24-Feb-2009
Location: Dallas, Texas
Posts: 343
Post Options Post Options   Quote smi-mark Quote  Post ReplyReply Direct Link To This Post Posted: 26-Apr-2010 at 10:56am
This has been a great read, with some very helpful information, as always, from Ward. I was wondering if you could expand on your notion of sharing a repository with multiple views.

I created a post in the general development section, so not to clutter this thread.
Back to Top
chuckc View Drop Down
Groupie
Groupie


Joined: 27-Feb-2010
Posts: 54
Post Options Post Options   Quote chuckc Quote  Post ReplyReply Direct Link To This Post Posted: 26-Apr-2010 at 1:28pm
Ward wrote:
 
   I would not have guessed that
 
   "calling RejectChanges() on one entity affects other related entities (i.e. automatically clearing Contact.CompanyId   
    when Company.EntityAspect.RejectChanges is called) "
 
   Have you tested this? Are you sure?
 
Yes, I have tested it, though not extensively. It works as described in two different scenarios so far. 
 
I didn't find any examples demonstrating it's use (EntityManager.RejectChanges - yes;  EntityAspect.RejectChanges - no).  Do you think it bears further investigation? 


Edited by chuckc - 26-Apr-2010 at 1:49pm
Back to Top
WardBell View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 31-Mar-2009
Location: Emeryville, CA,
Posts: 338
Post Options Post Options   Quote WardBell Quote  Post ReplyReply Direct Link To This Post Posted: 26-Apr-2010 at 1:56pm
@ChuckC - Well I'll be a monkey's uncle.
 
Of course we clear the Contact.CompanyId :-)
 
Actually it makes perfect sense. Contact.Company is watching the EntityManager cache and responds when the dependent entity disappears. It's important in this example that Contact.CompanyId is nullable. If it were not, we would not know what value to use in resetting the CompanyId; I believe we would leave it as. If you had a sentinel value (e.g., zero), you'd have to set that yourself.
 
@smi-mark - I will follow your lead to http://www.ideablade.com/forum/forum_posts.asp?TID=1752 
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down