Tuesday 14 October 2008

Trying to get code snippets working is a joy with blogger

/// <summary>
/// IoC Ctor
/// </summary>
/// <param name="PoisonMessageReceiver">A receiver of private messages which can't be processed</param>
/// <param name="PrivateEntityRepository">A service to use when requesting private entities from the replicated DB</param>
/// <param name="HmsServiceFacade">A facade for interfacing with the HMS service</param>
/// <param name="PrivateEntitiesToHmsEntitiesMapper">A mapping class matching HMS entities with private entities</param>
public RxMessageReceiver(IPoisonMessageHandler PoisonMessageReceiver,
                       IPrivateEntityRepository PrivateEntityRepository,
                       IHmsServiceFacade HmsServiceFacade,
                       IPrivateEntitiesToHmsEntitiesMapper PrivateEntitiesToHmsEntitiesMapper)
{
  poisonMessageReceiver = PoisonMessageReceiver;
  privateEntityRepository = PrivateEntityRepository;
  hmsServiceFacade = HmsServiceFacade;
  privateEntitiesToHmsEntitiesMapper = PrivateEntitiesToHmsEntitiesMapper;
}

I take it back. That wasn’t so hard was it! I went with this in the end, its an extension for live writer, which takes code copied onto the clipboard from Visual Studio.

Tuesday 7 October 2008

Strongly typed dataset: IDENTITY columns, RowState and AcceptReject

I have recently hit a problem with strongly typed datasets regarding an inability to reconcile children row’s RowState with cascading on AcceptReject rules on a relationship. I want to be able to have AcceptReject rules cascade so when I remove a tranche of data from multiple tables I can remove all this data with 2 commands:

ultimateMasterRow.Delete(); 
ultimateMasterRow.AcceptChanges(); 

However, if you set up a test rig as I describe below, you will quickly see that you can't just set all relationships' AcceptReject rule to cascade get the desired functionality.

I have 2 tables in my dataset, a typical master/detail setup. The master and the detail tables are based on SQL tables with IDENTITY columns. The dataset is configured with a relationship with:

update rule - cascade

delete rule - cascade

accept/reject rule - cascade

I have configured both table adapters with:

AcceptChangesDuringUpdate = false; //don’t let adapter automatically call AcceptChanges

I populate the 2 tables in the dataset with 1 record each. The RowState of each record is:

Master record - Added

Detail record - Added

So far so good. Now to synch with the database.

I call Update only on the master record’s table adapter. The RowState of each record is now:

Master record - Modified

Detail record - Modified

The detail record has been updated with the actual PK value from the database's IDENTITY property as expected, but the detail row has now lost its correct RowState, which should still be Added. I’m prepared to accept that the master row’s RowState can be modified as it has just received the IDENTITY value back from SQL, but no way should the detail record be modified. In fact, if you go onto try to call Update on the detail table adapter, you will most likely receive an optimistic concurrency exception. If you put a SQL Profiler trace on you will see that the table adapter is attempting to do an UPDATE statement in the database, rather than an INSERT as it is under the incorrect impression that the RowState correctly reflects the synchronisation state of the row!

There is an easy solution however. Configure all your relationships with AcceptReject = none.

“Hey! How do I get the awesome functionality of a 2 command removal of all related data from the dataset”, you ask. Well, all you have to do is when you are getting ready to remove your data tranche is…temporarily turn the AcceptReject rule back to cascade to do your delete/accept!

//We will briefly change the relationships that are not set to not cascade, then revert
 
List relationsToRevertToNone = new List(); 
List relationsToRevertToNone = new List(); 
StronglyTypedDataSet ds = //Get DataSet here 
 
foreach (DataRelation rel in ds.Relations) 
{ 
    if (rel.ChildKeyConstraint.AcceptRejectRule == AcceptRejectRule.None) 
    { 
        relationsToRevertToNone.Add(rel); 
        rel.ChildKeyConstraint.AcceptRejectRule = AcceptRejectRule.Cascade; 
    } 
} 
 
UltimateParentRow.Delete(); 
UltimateParentRow.AcceptChanges(); //now cascades to all rows related to the case 
 
//back out the changes to the rule so inserts will work in future 
foreach(var relToRevert in relationsToRevertToNone) 
{ 
    relToRevert.ChildKeyConstraint.AcceptRejectRule = AcceptRejectRule.None; 
}
 

Not quite 2 lines to get the functionality and it would be better if the dataset did this for you but, well, you can get there in the end!