What Property IsDirty

What Property IsDirty

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


Bluejay posted on Tuesday, May 09, 2006

Good day all,

I need the ability to determine not only if an object is dirty but, if it is, what property is dirty. I am not aware of any way to accomplish this based upon the stock 2.0 framework.

So, my question is a) am I right in stating that the framework does not include this and b) has anyone else done this and if so how?

Right now I am toying with the idea of adding a list, that will hold the dirty properties, to the base class. Then, in the "PropertyHasChanged" method updating that list. I would then expose this list as a property to consumers of the object. However, that may be a bit clunky and doesn't seem like the most elegant way of doing this.

Any ideas would be appreciated.

Thanks!

Dan

Jav replied on Tuesday, May 09, 2006

Dan,

That sounds like a good plan for what you want to accomplish.  A lot of us are using PropertyHasChanged to do a lot of things.  Petar's Observer came into existence to handle that very event when he first got started with it.  You may want to consider using that and then, instead of updating the "list" in PropertyHasChanged, you can do it by responding to Notification from the Observer anywhere in your program.

Jav

RockfordLhotka replied on Wednesday, May 10, 2006

You are correct, the framework doesn't track the list of changed properties.

But your approach is what I'd do were I to add the concept :)

You can do this without altering CSLA btw. You can just create your own intermediate base class to extend BusinessBase<T> - something like

[Serializable]
public abstract class MyBase<T> : BusinessBase<T>

You should be able to override either PropertyHasChanged or MarkDirty and then keep a list of properties that have been changed - which you can then expose through some method or property.

Then your businiess objects will inherit from your new base class rather than BusinessBase<T> and they should get your new feature.

Bluejay replied on Wednesday, May 10, 2006

Rocky and Jav,

Thanks for taking the time to respond to my question. I really appreciate the advice both of you provided.

Thanks again!

dstarkey replied on Tuesday, June 20, 2006

Hi

I am running into the exact same problem.  Have you tried implementing your solution yet?  I was also wondering if any of these items would in fact be invalid, because of businerules etc..

In that case when would you process or determine that these are no longer valid changed properties?  Very new to CSLA so any help is greatly appreciated.

Thanks

Don

dstarkey replied on Tuesday, June 20, 2006

Hello:

One more question, how would you capture the value of the changed property within the PropertyHasChanged(string propetyName) method???

Thank you so much for your help.  Really need a hand with this.

thanks again

Don

ajj3085 replied on Tuesday, June 20, 2006

Don,

Haven't implemented the solution, but it certainly sounds like it should work.

As far as validation rules, the PropertyHasChanged method should be called in the property setter; one of the things PropertyHasChanged does is check validation rules for that property. 

Finally, do you need to know the old value, or the new value?  The new value would be in the property backer field.  If you need to keep the old value, you'll likely have to code something in the properties setter to remember the old value (or values, if you want a complete history).

HTH
Andy

dstarkey replied on Tuesday, June 20, 2006

Hi Andy,

Thanks for the reply.  In our case I would have to know both values, we are using a two stage commit process and some updates will be invalidated.  In addition, we will have to store history of these transactions for legal purposes. 

I created an abstract class that inherits from business base as suggested.  The problem is that I only have the name of the property thats changed.  I added an additional method - PropertyHasChanged that accepts three parms, propertyName, originalValue, and currentValue.  But am I losing out on the functionality of BusinessBase if I go this route.   For instance would I have to have a similar method in the BusinessBase class???  Any help is appreciated, just looking  for a good solution to this problem.

Thanks

Don

dstarkey replied on Tuesday, June 20, 2006

Can you provide a sample of using the property backer field??

Thanks

Don

ajj3085 replied on Tuesday, June 20, 2006

dstarkey:
Thanks for the reply.  In our case I would have to know both values, we are using a two stage commit process and some updates will be invalidated.  In addition, we will have to store history of these transactions for legal purposes.


Ahh.. well, the auditing can be taken care of at the db level.  Your apUpdatePerson (for example) would update the Person table, and then also always insert the new values into a PersonAudit table, which could have the additional fields of AuditId, UserId, DateCreated, etc.  Person always holds the latest data, and PersonAudit holds a history of how the row changed over time (including the newest data).

Of course this answer is not knowing how the two stage commit works or what you mean by 'some updates will be invalidated.'

dstarkey:
I created an abstract class that inherits from business base as suggested.  The problem is that I only have the name of the property thats changed.  I added an additional method - PropertyHasChanged that accepts three parms, propertyName, originalValue, and currentValue.  But am I losing out on the functionality of BusinessBase if I go this route.   For instance would I have to have a similar method in the BusinessBase class???  Any help is appreciated, just looking  for a good solution to this problem.

I'd really recommend having the stored procedures handle change tracking like I described above.  Your BO need not know anything about the auditing and your don't need to keep adding extra code to save the audit information.  Also, even if someone circumvents your BO's and executes your procs directly, you still have history information, because the auditing is done in the proc.

Does this help at all?
Andy

RockfordLhotka replied on Wednesday, June 21, 2006

I can't see how you could do auditing outside the database with any credibility. Only during the transactional update process do you _really_ know what the old and new values are going to be in the database. Everything in your objects is a "best guess".
 
There's a thought model for thinking about types of data. I first encountered it when listening to Pat Helland speak at an architect conference. Here's a summary:
 
1) Resource data - mutable data owned by your application - basically the data in your application's database that is edited/maintained by your application
 
2) Reference data - immutuable, versioned, data - data that, if changed, becomes a new version. Think of a price list valid from 1/1 to 6/15, then it changes and you have another price list from 6/15 onward. But the old price list is still there, and is ummutable. Or think of a daily newspaper - once printed it is immutable and versioned.
 
3) Activity data - mutable data being acted upon by your application - basically work-in-progress data - this is basically "resource" data that isn't in the database, along with any transient data needed by the application, but which is never really persisted in the resource database
 
4) Request/response data - data flowing over the wire to/from the database, or two/from a service
 
Using this model, you can get some good clarity on many topics. For instance, in the case of auditing it goes like this:
 
a) user wants to edit some resource data
 
b) application sends request data to the database (a SQL query)
 
c) database finds appropriate resource data
 
d) database sends response data to app (SQL response); note that the Resource data was converted to "Response" data as part of this process
 
e) App puts response data into business objects; note that this convers the Response data into Activity data
 
f) user interacts with activity data in objects and decides to save
 
g) activity data is sent as a request to database; note the Activity data is converted to Request data
 
h) database persists the data; note that this converts Request data to Resource data
 
i) as part of (h), the database can also create audit records - which are Reference data (immutable, effectively versioned)
 
j) database returns success/fail info; this is Response data
 
This thought model is very useful in many cases, and provides a lot of structure to almost any operation involving the use of data. It is always valuable to determine what "kind" of data you have at any given point in time, because each of the four types has very different characteristics.
 
Rocky

dstarkey replied on Wednesday, June 21, 2006

It's not a simple matter of having the database sproc generate the audit trail.  Let me explain my use case a little better.  The database sproc generating the audit trail will not work because the data is actually located on two disparate database systems.  Here is the scenario:

Employee maint program where employee object is built primarily from a pervasive database that uses  a btrieve system and generic stored procedures (stone age, cannot process multiple result sets, and a host of other issues etc.....).  So employee object is built, and edited. 

Now on the save some fields can be written directly to the database, while others would have to be either approved or denied by an external company coordinator.  So this turns into a multiple phase commit.  These records [completed and pending] are inserted into a sql server database where they will either be ok'd and sent to the pervasive database as an update record, or be denied where the customer will be notified that his transaction was denied because of "reason deemed necessary by application coordinator".  To complicate things further no set business rule  exists for accepting or rejecting these transactions.  As a matter of fact similar transactions could be handled differently dependind on the circumstance.

I have to devise a system that will have good performance throughout this entire procedure.  I designed an employeeChange class that has the property name, old and current values as well as the time stamp and who originally submitted the change.  The final process is where the Sql table containing the transaction record is updated with a new time stamp field along with the id of the coordinator who ok'd or denied the update.  So if I haven't totally lost you I could use some help.  Thanks to everyone paricipating on this thread, it has already helped a great deal.

The employee object is very complex, having collections of paychecks, roles, benefits, deductions, security and company collections of objects.  Because of all these object, I implemented Lazy Loading on some of the high processing objects, such as generating the paycheck collection of objects, thoughts on this are welcome as well.  HELP PLEASE! Any and all suggestions are welcome

Thanks

Don

DansDreams replied on Wednesday, June 21, 2006

First, Rocky that post was very interesting.  I've done some reading of a work "Developing Time-Oriented Database Applications in SQL" by Richard Snodgrass which is basically a pragmatic system for designing databases to support mainly the requirement for resource vs. reference data.  I have asked the question myself "how difficult/cumbersome/sucky/etc. would it be to store versioned rows in every table in the entire database given I would never again need to wonder who had changed what."  Intriguing stuff.

Secondly, I wholeheartedly endorse the concept of an inherited class from the CSLA base BO classes and having your final BOs inheret from them.  Take it from someone who hacked the crap out of a previous CSLA version and has vowed to never do it again.  :)

Finally, Don I'm a bit concerned about the requirement to store the "old" value of all the BO properties, since as Rocky has stated that ultimately is only 100% meaningful at the database level because of concurrency issues.  Three LastName change requests could be pending for the same employee, so of what value is the second one's "original/old" value state?  How would it ever be used?  I might be convinced it could be of value to have the BO display the current DB value as of the time it was loaded as well as the proposed changed value.

In other words "Sally Jones has requested that the LastName of employee 34526 be changed to 'Smith' and the current DB value is 'Smyth'" seems infinitely more useful than "Sally Jones has requested that the LastName of employee 34526 be changed to 'Smith' and the DB value when the change was requested three weeks ago was 'Smyth'", given that the value could have been changed numerous times in the DB since then.  The "current DB value" option provides for the user concluding "oh, the name in the DB already is changed to Smith".  At the very least if the value as of the change request is required I would think you'd also have to show all changes since then.

Just a couple of cents worth for you to consider.

dstarkey replied on Wednesday, June 21, 2006

Thanks for the reply.  It is in fact going to store the current db value and the proposed value.  Last name is an example, but suppose someone is changing pay rate.  This escalates this scenario quite a bit.  I certainly would like to know who authorized joe smiths $50000 pay increase.  And be able to take this transaction log to a legal counsel if the change was unwarranted.

DansDreams replied on Wednesday, June 21, 2006

dstarkey:
Thanks for the reply.  It is in fact going to store the current db value and the proposed value.  Last name is an example, but suppose someone is changing pay rate.  This escalates this scenario quite a bit.  I certainly would like to know who authorized joe smiths $50000 pay increase.  And be able to take this transaction log to a legal counsel if the change was unwarranted.

Ah, but there's an important distinction there I think - now you're talking about the actual delta itself which is indeed derived from comparing the DB value at the time the property was changed but is also in fact subtly yet significantly different than storing that DB value.

Again I suggest that knowing all parts of "Jane Doe requested Bob Smith's salary be raised by $50,000 to $75,000" are indeed tremendously significant, but that takes on a rather different significance if Sally Smith's request that it be raised to $45,000 was already approved and committed.  I just think pragmatically speaking this mechanism as I understand you to describe will be a concurrency nightmare.

ajj3085 replied on Wednesday, June 21, 2006

dstarkey:
Employee maint program where employee object is built primarily from a pervasive database that uses  a btrieve system and generic stored procedures (stone age, cannot process multiple result sets, and a host of other issues etc.....).  So employee object is built, and edited.


I see.. so the database doesn't allow you to create custom stored procedures.  I hate to suggest this, but does it support triggers on table modifications?  If so, you could build a trigger which does the necessary audit trail.

dstarkey:
Now on the save some fields can be written directly to the database, while others would have to be either approved or denied by an external company coordinator.  So this turns into a multiple phase commit.  These records [completed and pending] are inserted into a sql server database where they will either be ok'd and sent to the pervasive database as an update record, or be denied where the customer will be notified that his transaction was denied because of "reason deemed necessary by application coordinator".  To complicate things further no set business rule  exists for accepting or rejecting these transactions.  As a matter of fact similar transactions could be handled differently dependind on the circumstance.

Uck.  And I thought my current issues were challenging. Smile [:)]  By 'no set business rules' I am thinking that the accept or reject is made on some other employees whim, so you just need to carry out the action?  Hmm. 

The first part of the challenge is that some fields don't need approval.  So I would recommend trying to seperate out the editing of those fields from the more sensitive ones.  Also, put the senstive fields into a seperate table all together, so that you're only working with row level rollbacks.  You'd probably have a business object which contains the new data which needs to be approved, and has a flag; Undecided, Approved, Denied (and possibly denied comments).  None of the other fields can be chagned (from your description thus far).  Note tht this object should be distinct from your employee editing object.  Anyway, the manager either approves or denies, and then you save that information back to the Sql  Server database. 

The final part would be a windows service, who's only responsibility is to scan the table containing the transactions which must be approved, and applies any that have not yet been applied.  This can be done via yet another object.  Likely, your DataPortal_Fetch would load just from Sql Server, and your data portal update would update both Sql Server and the pervasive db.  You'll have to use DTC here to keep both database updates atomic. (So use Trasnaction.TransactionScope on this method).

That is the route I would take, from the information you've posted so far.  I think the problem is that you're trying to have your Employee editable object do too much..  you should probably have 2 more other classes which handle these other details.

HTH
Andy

dstarkey replied on Wednesday, June 21, 2006

Thanks for the reply, and you were very much on target.  Yuck is right!  And you were correct in the database not using triggers, so that is out of the question.  As you surmised some of the fields are more sensitive in nature.  I am intrigued by the windows service - how would you further break down the base employee object to handle my use case?

ajj3085 replied on Wednesday, June 21, 2006

Well, first off, this is no longer an auditing scenario, so lets forget all the comments I made on how I would recommend building an audit trail. Smile [:)]


Your employee object would be very simple.  It gathers the data from the database(s) and lets the user change properties.  Now, if you hve a scenario were some users simply cannot change certain fields, period, you would use the AuthorizationRules and CanWriteProperty (in the property setters) to handle that.  Now, it may be the case that you actually need two employee objects; one which allows editing the non sensitive pieces (for those employees that can NEVER edit senstive stuff) and another which includes all the non senstive AND senstive pieces. 

Now an employee can edit sensitive things chooses to do so.  You load the current values (remember, no auditing code here at all... its just a basic BO).  The difference is in your Save.  When the save happens, put the data into the database which is non-sensitive, and the requested changes to senstive data in the other table.  The senstive data changes in this second table (Lets call it Change_Request) would initally be marked as No Action (meaning the manager didn't decide one way or the other yet).

The next use case is your manager that must approve or deny the changes.  I'm assuming its an all or nothing change (that is, they accept the change exactly as requested, or deny it outright) but if its not, you should be able to work in rules to accept or deny at the field level.  So you have another BO, EmployeeChangeRequest (or something along those lines).  This object will have all the senstive fields; it will load the requested value from Change_Request, and the current value from the pervasive db (if you need to display the current and requested values side by side).  Likely, you'll load a list of such objects.  Each object would have an ChangeAction, which we said starts at No Action, and can be set to Approved or Denied.  You'll likely also have a field for comments and a date / time which the action was changed.  They set the new status (approved or denied) and enter the comments.  The manager saves this, which just modifies the Sql Server Change_Request table only.  This Change_Request is more or less your log of wht happened and why.

Finally, you would have yet another object, consumed by a windows service.  Your service would get a list of all ApprovedButNotApplied (which has no settable fields, but inherits BusinessBase) objects every 30 seconds, and run through the list.  Your object could implement a method called ApplyToEmployee (which would really just call save) that would 1) Update the Change_Request table to reflect that the approved changed was applied, and 2) update the table in the other database.  You'll want to force the use of DTC here, and open both connections before you start sending commands to either one.  Basically you need to make sure that if you mark a chagne as applied, it really is applied.  DTC will ensure that you can't 1) mark a transaction as applied but the apply failed in the pervasive db for some reason and 2) updated the pervasive Db, but fail to record that fact in Sql Server, meaning your service could incorrectly reapply the change.

One other point; you'll want to make sure to force DTC when you're first creating the Change_Request and updating the employee record in pervasive as well, since you don't want any of those changes to be made independantly either.

Does that make more sense?  Your employee object should now be very simple, because its really just doing the basic load / save that your other objects likely are doing; the only 'new' part is that you're making use of two datasources instead of one, and forcing DTC transactions on updates to both data sources... but otherwise it really shouldn't be too much different than a standard BO.

One final note in closing; the reason you're running into such complexity trying to create an Employee object to handle all this is because you actually have 3 use cases here, not just one, and you're trying to make the Employee object handle all of the cases.

Use case 1 is updating the employee data (both sensitive and non senstive) and recording the requested changes
Use case 2 is the manager approving or denying the changes
Use case 3 is the applying of approved changes.

Also note that you have a  new actor; an automated system which searches for approved but unapplied changes (use case 3).

HTH
Andy

RockfordLhotka replied on Thursday, June 22, 2006

Granted that you can't always do the audit in a sproc. But still, I maintain that the only place you can reliably do an audit is in the database - or you can extend that concept to include "within the context of your database transaction". Which is another way of saying that audit must occur within the data access code.
 
This means you don't need to do it in the sproc, but you do need to do it in DataPortal_XYZ, within the context of a transaction where you are also updating the actual data. This directly implies that you have access to the real data in the database, along with the unreliable copy in memory in your object.
 
There's an exception to the transaction statement though. And that is where you want to audit _attempts_ to alter data. In that case your audit process still needs to be in DataPortal_XYZ, but must be in its own transaction. This is a relative edge case, and (unfortunately) isn't supported by CSLA .NET when using either EnterpriseServices or TransactionScope options for the Transaction attribute...
 
Rocky


From: dstarkey [mailto:cslanet@lhotka.net]
Sent: Wednesday, June 21, 2006 12:47 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: What Property IsDirty

It's not a simple matter of having the database sproc generate the audit trail.  Let me explain my use case a little better.  The database sproc generating the audit trail will not work because the data is actually located on two disparate database systems.  Here is the scenario:

rg123 replied on Tuesday, June 20, 2006

I had the same issue. I added an arraylist to the base class, and then call this method each time a property is set by calling SetProp(). There's probably some performance issues popping from the the stack, but it works for me.

    Protected Sub SetProp()
        Try
            If _propSet Is Nothing Then
                _propSet = New ArrayList
            End If
            _propSet.Add(New StackFrame(1).GetMethod().Name.Replace("set_", ""))
        Catch ex As Exception
            Throw ex
        End Try
    End Sub


Copyright (c) Marimer LLC