How to handle transactions across root objects

How to handle transactions across root objects

Old forum URL: forums.lhotka.net/forums/t/3313.aspx


alef posted on Thursday, August 02, 2007

I was looking at the project tracker example. Suppose we change the property Id for the Role object so that it is an identity value to make my question a little bit more difficult.

And suppose now we want to let the end-user create a new role and change e.g. an existing resource line in the grid to this newly created role. And this must all be done in one transaction. I've included already the code but I have still the following problem :

When new roles are created and this newly created roles are used in projects how is it possible to update the projects (so we need the new identity values given by the database for the roles and change the property role in the ProjectResource objects with this new Identity values)?

 

  [Serializable]
  public class TransactionCommand : CommandBase, ICloneable
  {

    #region Authorization Methods

    public static bool CanExecuteCommand()
    {
      // TODO: customize to check user role
      //return ApplicationContext.User.IsInRole("");
      return true;
    }

    #endregion

    #region Client-side Code

    private Roles _roles;
    private Project _project;

    public Roles Roles
    {
      get
      {
        return _roles;
      }
    }

    public Project Project
    {
      get
      {
        return _project;
      }
    }


    #endregion

    #region Factory Methods
    public static TransactionCommand SaveProjectTogetherWithRoles(Project Project, Roles Roles)
    {
      TransactionCommand cmd = new TransactionCommand(Project, Roles);
      //execute clone of object
      TransactionCommand clonedCommand = ((ICloneable)cmd).Clone() as TransactionCommand;
      return DataPortal.Execute<TransactionCommand>(clonedCommand);
    }

    private TransactionCommand()
    { /* require use of factory methods */ }

    private TransactionCommand(Project Project, Roles Roles)
    {
      _project = Project;
      _roles = Roles;
    }

    #endregion

    #region Server-side Code

    [Transactional(TransactionalTypes.TransactionScope)]
    protected override void DataPortal_Execute()
    {
      if (_roles != null)
        _roles = _roles.Save();
      if (_project != null)
        _project = _project.Save();
    }

    #endregion

    #region ICloneable

    object ICloneable.Clone()
    {

      using (MemoryStream buffer = new MemoryStream())
      {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(buffer, this);
        buffer.Position = 0;
        object temp = formatter.Deserialize(buffer);
        return temp;
      }

    }

    #endregion

  }

 

 

triplea replied on Thursday, August 02, 2007

That depends a bit on what you are trying to achieve. These roles you are creating I assume are not specific to the project you are editing. So conceptually they are disconencted from the project you are editing, even if they are on the same form.
You should either:
 1. Not allow creating roles in the same form you are editing a project but from a different admin screen wherein you can edit them and handle their saving in a single transaction.
 or
 2. If you want to provide the functionality of editing roles and a project in the same form, when save is pressed, first save the Roles and then the Project. Even if the Project transaction fails, you still have the Roles but that's fine since you need them anyway.

Having the Roles as a list within your Project class would not be the way to go.

JoeFallon1 replied on Thursday, August 02, 2007

Skipping over your example, the title is:

 How to handle transactions across root objects.

Rocky has answered this many times - please do a search of the forum.

I beleive he recommends using a Command Object to start the tr and then call save on both BOs.

I have also posted a possible solution in the past.

Joe

 

alef replied on Thursday, August 02, 2007

Hello Joe,

This recommendation for the Command Object I've followed. My code I've written is based on your solution. I've find many posts from you. But the difficulty about my question is that the transaction is across root objects but there is a dependency. First I need to save the roles, recuperate the identity values for the roles from the database and then do the saving of project with his projectresources.

So it is the following part of the code which is not correct. How can I transfer the identity value for the roles to the project?

[Transactional(TransactionalTypes.TransactionScope)]

protected override void DataPortal_Execute()

{

if (_roles != null)

_roles  = _roles.Save();

if (_project!= null)

_project= _project.Save();

}

RockfordLhotka replied on Thursday, August 02, 2007

At least in ProjectTracker, the Roles object invalidates the RoleList cache, forcing it to reload itself from the database.

This happens in the _roles.Save() call, so by the time _project.Save() is called the RoleList is known to be invalid.

If you call ValidationRulesCheckRules() in your Save() method, that would force revalidation of the Role property based on the new role list.

protected override Project Save()
{
  ValidationRules.CheckRules();
  return base.Save();
}

The real complication here, is that this is all running on the application server. The client doesn't know that the roles were saved!!

So your command object needs to call RoleList.InvalidateCache() after it returns to the client to make sure the updates occur correctly. If you follow the template for a command object from the book, you'll see where there's a place to run client-side code after the DP_Execute() method is complete, and that's where you'd make this call.

alef replied on Thursday, August 02, 2007

Hello Rocky,

Maybe I didn't express me very well but I was trying to do something different with your projecttracker application.

You can download the sample here : http://users.skynet.be/firstlook/ProjectTracker20cs.zip

I did the following changes :

a) changes in the database

the table Roles : field Id is now an identity column

changed the SP addRole to take into account the identity column

b) changes in ProjectTracker.Library

I changed the authorization for the roles and role objects so that also the user pm can add, delete and modify these objects.

I changed the role class so that if the user is creating a new role the Id is -1. The second new role will have an Id = -2 and so on,...

These dummy numbers must finally be replaced with the numbers that the database is given for the identity column.

I added the class TransactionCommand. The purpose for this is to save the changes done to the roles and a project together, in a transaction.

c) changes in PTWin

The combobox for the Role in the usercontrol ProjectEdit is bound to Roles.GetRoles()

and not anymore to RoleList.GetList().

I added two buttons to the Usercontrol ProjectEdit:

Button "Add New Role" is to let the user add a new role in an inputbox without going to another screen and I don't want that this new role is immediately saved to the database. So if the user clicks on the Cancel button in the ProjectEdit form nothing is been saved to the database. So neither the project, neither newly added roles are saved when the user clicks Cancel.

Button "Commit added roles and project in one transaction" which must save the changes to the database.

Steps to reproduce :

1) log-in with user pm

2) edit a project

3) add a new role via the button "Add New Role" eg Tester

This newly added role will be visible in the combobox because the combobox is bound to Roles.GetRoles() and not anymore to RoleList.GetList().

4) change the role for the first resource to this newly created role Tester

5) Ckick the apply button and you'll get the following error :

The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_Assignments_Roles"

The conflict occurred in table "dbo.Roles", column "Id"

 

The problem is that ProjectResource has still a role = -1 (this is the newly created Tester role)

and this role = -1 doesn't exist in the database.

I'm thinking about the following solution :

Create a temporary dictionary with the corresponding values in the database for the newly created roles.

eg   -1 (id propery for the newly created role Tester)   ==>    7 (this is the value in the database)

Then when saving the project loop through the projectresources collection and replace all the items which has a negative role id with the value from the dictionary.

Do you have some cleaner way to solve this?

 

So the whole purpose is to provide the functionality of editing roles and a project in the same form (as triplea also mentioned) and if the user chooses to click on the cancel button nothing is saved to the database. So if the user is creating a new role and changing the resource to this new role and finally the user chooses to cancel, this role may not exist in the database.

The code for the two buttons are :

private void addNewRoleButton_Click(object sender, EventArgs e)

{

InputBoxResult inputboxRole = InputBox.Show("Add a new role"

, "Add new role", "", 100, 0);

if (inputboxRole.ReturnCode == DialogResult.OK)

{

Role _role = _roles.AddNew();

_role.Name = inputboxRole.Text;

}

}

private void commitRolesAndProject_Click(object sender, EventArgs e)

{

using (StatusBusy busy = new StatusBusy("Saving..."))

{

this.projectBindingSource.RaiseListChangedEvents = false;

this.resourcesBindingSource.RaiseListChangedEvents = false;

// do the save

this.projectBindingSource.EndEdit();

this.resourcesBindingSource.EndEdit();

try

{

TransactionCommand.SaveProjectTogetherWithRoles(_project, _roles);

}

catch (Csla.DataPortalException ex)

{

MessageBox.Show(ex.BusinessException.ToString(),

"Error saving", MessageBoxButtons.OK,

MessageBoxIcon.Exclamation);

}

catch (Exception ex)

{

MessageBox.Show(ex.ToString(),

"Error Saving", MessageBoxButtons.OK,

MessageBoxIcon.Exclamation);

}

finally

{

this.projectBindingSource.RaiseListChangedEvents = true;

this.resourcesBindingSource.RaiseListChangedEvents = true;

this.projectBindingSource.ResetBindings(false);

this.resourcesBindingSource.ResetBindings(false);

}

}

 

Alef

 

RockfordLhotka replied on Thursday, August 02, 2007

Yeah, you have designed yourself into an interesting situation, no doubt there.

I don't have any immediate thoughts on a solution, other than perhaps dropping the use of RoleList entirely, and using the role data directly from Roles. That would at least allow you to implement events on the Role objects so they could notify listeners when they've changed - and your ProjectResource object would be such a listener.

Might or might not work, but it is interesting to consider.

Good luck! Smile [:)]

alef replied on Friday, August 03, 2007

Hi Rocky,

I've to admit in your example I want to do some strange design. But this design I want to use for example to create a new product. So the screen to create a product has a lot of comboboxen (Name Value pairs) eg Color, Mark, Model, ...and there this behavior is quite normal. If the user clicks on the cancel button the product may not be saved in the database neither newly created color, mark or model. So this is the reason I want to have a proper solution for situations like this.

I've worked out your idea with the events and it solves the problem but the code is not clean. You can find the code here : http://users.skynet.be/firstlook/ProjectTracker20cs.zip

I'll describe what I've changed:

1) PTWin : class ProjectEdit

public ProjectEdit(Project project)

{

....

_roles = Roles.GetRoles();

Csla.ApplicationContext.LocalContext["roles"] = _roles;

...

}

I'm using ApplicationContext so I can get also access to the same collection _roles in ProjectTracker.Library. This piece of code is bothering me, but I don't know how to do this in a clean way.

I can not call Roles.GetRoles() in ProjectTracker.Library because it will give me another collection because it will be retrieved again for the database. So it will be a different collection and it will not pointing to the same collection as in the GUI, so the PropertyChanged will not be catched. And also the newly created roles in GUI will not be visible in ProjectTracker.Library if I call Roles.GetRoles() in ProjectTracker.Library.

This ApplicationContext is something new and is not explained in your book, so maybe I'm not using this in the right way because I don't understand very well this ApplicationContext.

But anyway I'm not satisfied with this solution because now the business must read also this ApplicationContext and has to know about the LocalContext "roles". But normally the BO doesn't know that it has to use this. Have you any idea how to solve this in a clean way?

2) class ProjectResource

public int Role

{

            ....

set

{

CanWriteProperty(true);

if (!_role.Equals(value))

{

_role = value;

PropertyHasChanged();

//Roles tmpRoles = Roles.GetRoles(); //this I can not do because it will give another collection then the one used in the GUI

Roles tmpRoles =(Roles)ApplicationContext.LocalContext["roles"];  //this will give me the same collection that is used in the GUI

Role tmpRole = tmpRoles.GetRoleById(_role);

tmpRole.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(tmpRole_PropertyChanged);

}

}

}

 

void tmpRole_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)

{

if (e.PropertyName == "Id")

{

_role = ((Role)sender).Id; //store the new identity value retrieved from the database in _role

}

}

 

3) class TransactionCommand

The cloning I have put in comment because otherwise I'll not catch the PropertyChanged event. But will it still work if we deploy the application in 3 tiers, because then the cloning will be done automatically? Have you some idea how to solve this?

public static TransactionCommand SaveProjectTogetherWithRoles(Project Project, Roles Roles)

{

TransactionCommand cmd = new TransactionCommand(Project, Roles);

//execute clone of object

//TransactionCommand clonedCommand = ((ICloneable)cmd).Clone() as TransactionCommand;

//return DataPortal.Execute<TransactionCommand>(clonedCommand);

return DataPortal.Execute<TransactionCommand>(cmd);

}

 

Thank you very much for the help and hopefully we can find a solution together and maybe this a good item to speak about in a next dnrTV show because I think a lot of people will be in the same situation as me. I'm working at Fortis Investments and I have to put in place a new architecture for about 20 developers. Before .NET we created our own framework in VB6 and we have lots of applications which are in a situation like this. But now I want to study first your framework to see if it can easily deal with situations like this before using it at our company.

With kind regards

Alef

 

 

alef replied on Sunday, August 05, 2007

Hi Rocky,

Have you already had the time to look at the changes I've implemented?

Many thanks

Alef

RockfordLhotka replied on Sunday, August 05, 2007

To be honest, I don't have the time to look through it all in detail - I'm simply juggling too many things right now.

But on further reflection, I think we're making this much harder than it really is.

All you have is a simple mapping issue.

You have a list, A, that contains some temporary values. It transforms into A' when you save it.

You have another list, X, that uses values from A. Before you can save X, you need to map its A values to A' values. Once that's done, X uses values from A' and can be safely saved.

So all you need is a ListMapper that accepts A and A' as arguments, and then you can ask it to resolve an old key into a new key. That's easy code to write.

Then you need a FixUpChildren object that loops through X and replaces all A values with A' values by using Listmapper.

At a high level, your DP_E() method would look like this:

RoleList oldList = _roleList.Clone();
_roleList = _roleList.Save();
ListMapper map = new ListMapper(oldList, _roleList);
FixUpChildren(_project.Resources, map);
_project = _project.Save();

A = oldList
A' = _roleList
X = _project.Resources

Seems like a very simple, direct answer to the problem.

alef replied on Monday, August 06, 2007

Hello Rocky,

I've worked out the idea you have which seems to be a clever idea. You have absolutely right, it is just a mapping issue.  We are almost getting there but not yet 100%. If you read further on you'll see why.

My first thought was it will not work because the two lists oldList and newList will not have the same index because the newList can have less items then the oldList (eg the deleted items will not be anymore in the newList). And I can not use the GetIdValue to match an item from the oldList to the newList because this GetIdValue relies just on the primary key which is different between the two collections.

But this thought is wrong because if you delete items they don't exist anymore in the oldList, you have placed them in a separate collection. So the index of the oldList and newList are in sync and the ListMapper function can do his work. Correct me here if I'm wrong.

 

I've discovered the following problem. Suppose you create a new role X, assign this new role X to a ProjectResource object whose value is Y, then delete role Y. When we call

_roles = _roles.Save();

it will fail and results in the following error :

The DELETE statement conflicted with the REFERENCE constraint "FK_Assignments_Roles". The conflict occurred in database PTRACKER.MDF", table "dbo.Assignments", column 'Role'.

 The problem here is the framework wants to delete role Y but ProjectResource is still pointing to this role Y. (image that only this ProjectResource is pointing to this role Y otherwise the error thrown is correct).

It is only in the call

_project = _project.Save();

that in the database the role will be updated to X, and only then it will be possible to delete the role Y.

Do you have some idea how to solve this?

 

I'll describe the changes I've done in the class TransactionCommand

public static TransactionCommand SaveProjectTogetherWithRoles(Project Project, Roles Roles)

{

TransactionCommand cmd = new TransactionCommand(Project, Roles);

//execute clone of object

//TransactionCommand clonedCommand = ((ICloneable)cmd).Clone() as TransactionCommand; //Alef

//return DataPortal.Execute<TransactionCommand>(clonedCommand); //Alef

return DataPortal.Execute<TransactionCommand>(cmd);

}

Is this cloning of the command necessary or not?

 

 

[Transactional(TransactionalTypes.TransactionScope)]

protected override void DataPortal_Execute()

{

if (_roles != null)

{

Roles oldList = _roles.Clone();

_roles = _roles.Save();

Dictionary<int, int> keyMapping = ListMapper(oldList,_roles);

FixUpChildren(_project.Resources, keyMapping);

}

if (_project != null)

_project = _project.Save();

}

private void FixUpChildren(ProjectResources projectResources, Dictionary<int, int> keyMapping)

{

foreach (ProjectResource projectResource in projectResources)

{

if (projectResource.IsDirty)

{

int newId;

if (keyMapping.TryGetValue(projectResource.Role, out newId))

{

projectResource.Role = newId;

}

}

}

}

 

public Dictionary<int, int> ListMapper(Roles _oldRoles, Roles _newRoles)

{

Dictionary<int, int> keyMapping = new Dictionary<int, int>();

for(int i = 0 ; i < _oldRoles.Count ;i++)

{

if (_oldRolesIdea [I].IsNew )

{

int oldKey = _oldRolesIdea [I].Id;

int newKey = _newRolesIdea [I].Id;

keyMapping.Add(oldKey, newKey);

}

}

return keyMapping;

}

 

RockfordLhotka replied on Monday, August 06, 2007

Typically in cases like this the values are unique too, and so you can use the value to do the mapping – you don’t need both sets of keys – you can simply match the value for the old key with the value in the new list to find the new key.

 

If that’s not the case, then you can still solve the problem by just keeping the old key as a property in your RoleEdit child objects in the editable list.

 

Regarding deleting: this is desired behavior. Clearly Project can not have value Y if it has been deleted!! I don’t know what you expect would happen here, but I would expect failure.

 

On the other hand, that scenario should never happen because the Project should have been validating that property against the current list to start with, and so it could NOT have a value of Y because Y isn’t in the current list.

 

Rocky

 

 

From: alef [mailto:cslanet@lhotka.net]
Sent: Monday, August 06, 2007 7:54 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] How to handle transactions across root objects

 

Hello Rocky,

I've worked out the idea you have which seems to be a clever idea. You have absolutely right, it is just a mapping issue.  We are almost getting there but not yet 100%. If you read further on you'll see why.

My first thought was it will not work because the two lists oldList and newList will not have the same index because the newList can have less items then the oldList (eg the deleted items will not be anymore in the newList). And I can not use the GetIdValue to match an item from the oldList to the newList because this GetIdValue relies just on the primary key which is different between the two collections.

But this thought is wrong because if you delete items they don't exist anymore in the oldList, you have placed them in a separate collection. So the index of the oldList and newList are in sync and the ListMapper function can do his work. Correct me here if I'm wrong.

 

I've discovered the following problem. Suppose you create a new role X, assign this new role X to a ProjectResource object whose value is Y, then delete role Y. When we call

_roles = _roles.Save();

it will fail and results in the following error :

The DELETE statement conflicted with the REFERENCE constraint "FK_Assignments_Roles". The conflict occurred in database PTRACKER.MDF", table "dbo.Assignments", column 'Role'.

 The problem here is the framework wants to delete role Y but ProjectResource is still pointing to this role Y. (image that only this ProjectResource is pointing to this role Y otherwise the error thrown is correct).

It is only in the call

_project = _project.Save();

that in the database the role will be updated to X, and only then it will be possible to delete the role Y.

Do you have some idea how to solve this?

 

I'll describe the changes I've done in the class TransactionCommand

public static TransactionCommand SaveProjectTogetherWithRoles(Project Project, Roles Roles)

{

TransactionCommand cmd = new TransactionCommand(Project, Roles);

//execute clone of object

//TransactionCommand clonedCommand = ((ICloneable)cmd).Clone() as TransactionCommand; //Alef

//return DataPortal.Execute<TransactionCommand>(clonedCommand); //Alef

return DataPortal.Execute<TransactionCommand>(cmd);

}

Is this cloning of the command necessary or not?

 

 

[Transactional(TransactionalTypes.TransactionScope)]

protected override void DataPortal_Execute()

{

if (_roles != null)

{

Roles oldList = _roles.Clone();

_roles = _roles.Save();

Dictionary<int, int> keyMapping = ListMapper(oldList,_roles);

FixUpChildren(_project.Resources, keyMapping);

}

if (_project != null)

_project = _project.Save();

}

private void FixUpChildren(ProjectResources projectResources, Dictionary<int, int> keyMapping)

{

foreach (ProjectResource projectResource in projectResources)

{

if (projectResource.IsDirty)

{

int newId;

if (keyMapping.TryGetValue(projectResource.Role, out newId))

{

projectResource.Role = newId;

}

}

}

}

 

public Dictionary<int, int> ListMapper(Roles _oldRoles, Roles _newRoles)

{

Dictionary<int, int> keyMapping = new Dictionary<int, int>();

for(int i = 0 ; i < _oldRoles.Count ;i++)

{

if (_oldRolesIdea <img src=">.IsNew )

{

int oldKey = _oldRolesIdea <img src=">.Id;

int newKey = _newRolesIdea <img src=">.Id;

keyMapping.Add(oldKey, newKey);

}

}

return keyMapping;

}

 



alef replied on Tuesday, August 07, 2007

Hello Rocky,

You have absolutely right, the name property of the role object is unique. But if we take the name property as the key in the dictionary it makes the replacement of the identity values in the children more difficult because the ProjectResource works only with Id of the Role and we don't have a link to the Role object. If the ProjectResource should have a property to the Role object then it would be a lot easier. Can you tell me for what reason you are using the Id and not the object role itself?

 

Regarding deleting: I’ll try to explain it in more detail what I'm trying to do.

The sequence of the action queries is important since we are dealing with a relational data structure of projects and roles. For example, it would not be prudent to insert a project record into the database without a corresponding and valid Roles table record. This particular case we are treating well but other cases not, I’ll give an example further on. A good rule of thumb for sequencing your action queries is as follows:

1. Update parent rows

2. Insert parent rows

3. Update child rows

4. Insert child rows

5. Delete child rows

6. Delete parent rows

 

The code in the DataPortal_Execute method of TransacionCommand will not have this sequence of action queries.

A) It first call _roles.Save() which will persists all the modifications on the roles to the database. The code in DataPortal_Update of Roles.cs looks like

        foreach (Role item in DeletedList)

        {

          item.DeleteSelf(cn);

        }

        DeletedList.Clear();

        foreach (Role item in this)

        {

          if (item.IsNew)

          {

            item.Insert(cn);

          }

          else

            item.Update(cn);

        }

(first all the deletes will be executed, then the insert and updates will be executed).

 

B) Then all the modifications done to the project will be saved.

 

So we have the following sequence: (roles are the parent, project is the child)

1.      Delete parent rows

2.      Insert/Update parent rows

3.      Save the child row which can be an update, insert or delete

 

So suppose the end-user is executing the following actions:

1.      He adds a new role Tester.

2.      He changes an existing ProjectResource by changing the role from “Software architect” to Tester. (suppose it is the only ProjectResource which has this role “Software architect”, no other ProjectResource is pointing to this role) So in this case it must be possible to delete the role “Software architect”, because this ProjectResource is not pointing anymore to this role neither other ProjectResources)

3.      Then the end-user deletes the role “Software architect”

 

So when persisting all the modifications of the user to the database the sequence of the action queries is very important:

If we follow the rule of thumb for the action queries

1.      Update parent rows

Obsolete in our example of actions that the end-user has executed

2.      Insert parent rows

                        insert role Tester

3. Update child rows

Update the role from “Software architect” to “Tester” for the ProjectResource

Suppose that this ProjectResource was the ONLY one pointing to “Software architect”. After the update statement the “Software architect” is not been used anymore and then it must be possible to delete this role in a next step. THAT IS THE REASON we need first to update the child rows (step3) before deleting the parent rows (step 6)

4. Insert child rows

            Obsolete

5. Delete child rows

Obsolete

6. Delete parent rows

Delete the role “Software architect”

 

Do you have an idea how you can change the sequence of the action queries? In the sequence of action queries you are using we'll have an error:

The DELETE statement conflicted with the REFERENCE constraint "FK_Assignments_Roles". The conflict occurred in database PTRACKER.MDF", table "dbo.Assignments", column 'Role'.

This is because you first delete the role (but at this moment you didn't changed yet the projectresource, this projectresource is still pointing to “Software architect”) and only afterwards you are updating the projectresource to point to this new role Tester. If you should do the delete now, then the delete will be succesfull.

 

Alef

alef replied on Thursday, August 09, 2007

Hello Rocky,

 

Please, can you have a look at this?

 

Regards

Alef

RockfordLhotka replied on Thursday, August 09, 2007

I’m actually just wrapping up a lot of loose ends before I go on vacation next week, so I don’t have more time to spend on this right now.

 

As we’ve discussed this, each solution I’ve offered has revealed some new layer of requirements that were previously unknown (to me anyway). I feel like I’m playing a game of whack-a-mole. Smile [:)] I don’t have time to offer free business analysis and consulting on this forum. I love to help, and do as much as I can, but I can only go so far...

 

My last comment is this: I think you need to rethink the object model so it meets your needs. You are starting with an object model that is designed for a different pair of use cases, trying to make them fit this new use case. Clearly the object model doesn’t match your needs, so design a different object model.

 

I don’t know exactly what that new object model looks like, and as I say, I don’t have time over the next 2-3 weeks to analyze the requirements and design such an object model. I can see right now that the next requirement is that this work in a multi-user environment with concurrency, at which point your complexity shoots sky high.

 

Personally, I’d drop the database integrity rules and maintain integrity at the object level. That’d simplify the problem immensely.

 

Failing that, I’d give the DBA that insists on keeping the integrity rules a blob of XML from all the objects and tell him or her to figure out how to resolve the issue. Wink [;)]

 

And failing that, again, rethink your object model. You have designed a situation where the data update for a role must occur along with the data update for a line item. I don’t care if you are using objects, datasets or whatever, you have allowed yourself to get into an ugly situation with requirements that appears to be very difficult to resolve at the database update level.

alef replied on Friday, August 10, 2007

Hello Rocky,

I hope you'll have a good vacation and come back with a fresh mind to implement new things and maintain the existing. I appreciate very much your help.

I was thinking about the following solutions regarding the deleting (it keeps me awake this ugly situation I've put myself in) and I want it to share it. Maybe it is a light in the dark.

1) first solution

If we make the save method more granalur and split this in three methods : insert, update and delete

In place of doing the following in DataPortal_Execute() in the class TransactionCommand

_roles = _roles.Save();

_project = _project.Save();

we can do for example the following to solve the particular use casse I've designed myself in :

   _roles.Update():

   _roles.Insert();

   _project.Update();  // the Update method must first see if he has to do an update and not e.g. insert or delete

   _project.Insert(); // the method must first check if it is a new project. If it is not a new project then nothing should happen

    _project.Delete(); //idem

   _roles.Delete();

2) second solution

We could tackle the problem in a more generic way and design a solution that supports the grouping of an arbitrary amount of use cases into one unit-of-work. This unit-of-work will then use the database model to find out the sequence of insert-update-delete statements for each business entity. This solution will also provide us the all-or-nothing concept: commit either every modification or nothing at all (if an error occured somewhere in the process).

Regards

Alef

 

 

 

 

 

RockfordLhotka replied on Friday, August 10, 2007

You certainly could make Roles be more granular in how it saves itself.

 

Just override Save() and throw a notsupportedexception to prevent its use.

 

Then implement your three more granular methods, but make sure they throw an exception unless Csla.ApplicationContext.ExecutionLocation is Server (don’t want them called on the client).

 

Then your command object can safely call these methods as needed.

 

Rocky

Copyright (c) Marimer LLC