MarimerLLC / csla

A home for your business logic in any .NET application.
https://cslanet.com
MIT License
1.27k stars 406 forks source link

Consider a specific feature for calculated properties #4201

Open rockfordlhotka opened 2 months ago

rockfordlhotka commented 2 months ago
          I understand that. But shouldn't such a property _not_ change the dirty state?

A possible use case: Summary of X other properties. Sending that over the wire is unnecessary due to the sum nature, but I do want to participate in changes of that value to simplify other stuff (Instead of having X input variables I only have to care about 1 in other rules). In the new form this recalculation would always mark the BO as dirty on it's first (re)calculation. That's for a brand new object probably not a problem. X * 0 = 0 (to go with my sum example). But as soon as you load existing data you have the following scenario. All data loaded and because people might do a CheckRules before returning from the server to the client the BO is now dirty. The newly calculated value wasn't loaded. Now on the client I recalculate the value and my BO is dirty again. Without doing anything. That seems really weird to me. To prevent that with currently available tools, a developer must use LoadProperty in it's rules or property defintion. (I'm unsure here if an AddOutValue() of a rule uses the property set or Set/LoadProperty internally).

Or maybe I miss something crucial here? I personally would love to have a property declaration where I can mark the property as "not dirty relevant" so I can use it in rules etc. without interfering in changes made by users.

Originally posted by @StefanOssendorf in https://github.com/MarimerLLC/csla/issues/1628#issuecomment-2327316111

rockfordlhotka commented 2 months ago

I think this is an interesting concept worth discussing on its own, so I've elevated it to be its own issue.

It does make sense to me that a calculated value should:

  1. be NonSerialized
  2. be recalculated on deserialization (because it wasn't serialized)
  3. not mark the object as dirty when changed (?)

I have a (?) on point 3, because I can see both sides of this.

On one hand you can imagine a calculated property that is derived by calculations of other properties, and so if they haven't changed, then the calculated value wouldn't change either.

On the other hand, you can imagine a calculated property that is derived by the values of other properties plus something like a date/time or database value - something that changes independent of other object properties. As a result, any time the calculated property is recalculated it would change, even if other properties did not change.

The thing is, that second scenario is what we have today - so a normal property (perhaps marked as NonSerialized) would solve scenario 2.

But we can't really support scenario 1, because all properties feed into the IsSelfDirty behavior today.

Bowman74 commented 2 months ago

I run into scenario one all the time and unless I mistake what you are saying, I don't see it as a problem. Whenever an object is updated from the DataPortal I make sure all rules are run and then I mark the object as "clean" because I know for a fact at this point the relevant attributes are the same as what was fetched. I never serialize those calculated properties (Add them to the ignore list and are not part of my DTOs) and the only way they can create an IsDirty situation is if some other property on the same object I do care about becomes dirty first forcing a recalculation.

The only time I would see this as an issue is a long running asynchronous rule that I wouldn't be able to wait for the result before marking the object as "clean" on a DataPortal operation. But in such cases I'd look for other techniques to handle that business logic.

Doesn't MarkClean() on DataPortal operations already provide the solution in most cases?

hurcane commented 2 months ago

To throw in my two cents on this issue. All of our fetch methods have this code in the ObjectFactory class:

// Must mark old before checking rules as some rules could be dependent on new vs. old.
MarkOld(result);
CheckRules(result);
// Some rules can update values, which makes the object appear dirty.
// Another MarkOld will also mark the object clean.
MarkOld(result);
rockfordlhotka commented 2 months ago

@hurcane fwiw, these days you should try to use await CheckRulesAsync() when possible so your code is ready for async rules if you use them in the future.

hurcane commented 2 months ago

I think the idea of not serializing a property and recalculating it when being deserialized is going to be difficult to design. How would the logic know which rule to run? Would we add an opt-in property on business rules to run after serialization? Would you just run the full CheckRules when deserializing?

I do like the idea of being able to simply exclude a property from being considered in the default IsDirty logic. Maybe a NeverDirty attribute? You could combine that with NonSerialized, or choose to still serialize it but not have to worry about marking the object clean just for the calculation rules.

One area we would use this is with descriptions of identity values. A customer ID is entered on an order and a rule looks up the customer name. If the rule runs again and the name was changed, it doesn't really affect the dirty state of the order, which is still for the same customer ID.