Hello,
We would like to disable N-Level undo on Editable Class i.e. for class derived from BusinessBase.
Instead of N-Level undo, Can we make it just 1 level undo ? i.e. undo to Original version that was fetched from database.
If the above is not possible, can we disable the undo ? If the user hits cancel, we can always re-fetch the data from the Database.
Another que: We frequently need to compare original value fetched from the database with current value of a property and based on the comparision, we write our business logic. Where and how to store the original value of the object fetched from the database. The old value is required on client as well on server. We do not want to serialize these old values between client and server to avoid the overhead.
Any help in the regard is highly appreciated....
1. There is a setting which allows you to not use N level undo. It is discussed here: http://forums.lhotka.net/forums/thread/16652.aspx
2. "The old value is required on client as well on server. We do not want to serialize these old values between client and server."
You can't really have it both ways!
You have to "carry the old value around" if you later want to compare it to the current value. The alternative is hitting the DB with it or pulling a copy of the originally fetched BO from cache. But that really complicates things. It is much simpler to create mOrigValue and compare it to mValue in the same BO. You would only expose OrigValue as a readOnly property (if at all.)
I recall that it is also possibel to extend the new managed fields concept to include metadata like OrigValue and IsDirty at the field level. Rocky does not support this in CSLA itself - but he allows you to extend CSLA to get this behavior if you want to pay that cost.
Joe
Disabling the undo capabilities depends on how you intend to use your objects. You can disable the IEditableObject support, which would turn off the automatic undo capabilities of your objects. Once you've disabled that, the amount of undo is totally up to you. If you only want 1 level, then you only call "BeginEdit" once.
Having said that, there are some potential caveats. If this is a web app, then you don't have to do anything - you're not getting automatic undo, and if you don't want any undo behavior (and almost every web site I've seen doesn't), just don't call the Begin/End/ApplyEdit methods.
If you're working with a Windows Forms app - well, that becomse more complicated. Disabling IEditableObject means that the automatic data-binding you get from the Forms architecture doesn't happen, and you're on your own. Essentially, you would turn your Windows app into a web app, from a data-binding perspective. You would be required to call the Begin/End/ApplyEdit methods as appropriate. But you'll also encounter some potentially strange behaviors in certain areas, mostly related to grids.
If you're in a WPF app - well, I can't help you there, as I'm not into WPF yet, so I don't know how that would be affected.
As for your original/current question, there isn't a built-in way to manage that within a CSLA object, so you're pretty much on your own to manage that. But I'm a little confused about your comment about not wanting to serialize the original values. I understand the overhead concerns, but you have to get the data to the client somehow, so it has to be serialized the one way. And once you have the data, why would you then disregard it and re-query the database on the server side? The server objects are not generally stateful, so there wouldn't be an instance of your business object sitting on the server holding those original values - the server object is completely reconstituted from the byte stream sent to it by the client. That's the crux of the "mobile object" concept. You can't mark your original value fields as NonSerialized, because they then wouldn't make it to your client. So if you want the original values on the client, you'll be sending them back and forth anyway.
HTH
- Scott
Thanks for comments guys.
Platform: WPF/WCF/LINQ
Product: Our product targets wholesale fashion industry with full EDI support. We may have anyway from 100 to 10,000 lines per production order. So, If we keep the original values with in the BO, we are potentially making 20,000 objects travel instead of 10,000. To make things worse, every production line is a matrix of Sizes, Dimension and Colors as grand children.
For Client side, When we have fetched the object from the database, can we store in the client side for reference? If yes, then what is the best way of storing the retreiving the properties on client side?
For Server side, we can re-fetch from the database, i think....
Regarding undo,
On one form, We have 8 collections/lists, where data is moved by user between one collection/list to another. Size of each list is just about 1000 string variables. They are all binding lists though. So Whe we are moving data between collection in UI, its very slow, and after 6,7 attempts the system goes out of memory.
We disabled the undo behaviour as you have suggested, and the system is responding very fast and out of memory problem has gone away, but we are not sure what exactly will stop working.
Again, Thanks for any help.!
handaajay15:Product: Our product targets wholesale fashion industry with full EDI support. We may have anyway from 100 to 10,000 lines per production order. So, If we keep the original values with in the BO, we are potentially making 20,000 objects travel instead of 10,000. To make things worse, every production line is a matrix of Sizes, Dimension and Colors as grand children.
I'm not sure I follow this. If you keep the original values of your properties in the BO, why does that double the number of objects you send? Yes, it will make your BO's bigger, but it shouldn't create more of them to send back and forth. Sending the original values to the client inside your BO is going to be a lot smaller (and faster) then trying to send two copies...
Would you be storing them on the client in order to create your "original value" BO's? Sure, you can do that - but with an object graph of that size, you're going to notice the performance hit. Also, you can't really keep that large an object graph sitting in memory (see your out-of-memory problems), so you'd probably need to serialize it to disk. But you'd then have to load it back into memory to do your field comparisons, so that doesn't seem to help too much.
Maybe I'm not understanding you, but I don't know why you would re-fetch from the database on the server side. If you have to have your original database values on the client, then they will be sent in the serialized byte stream to the client, as well as from the client to the server. There is no need to re-fetch from the database, and doing so will not make your objects smaller as they travel over the wire.
Besides, what's to guarantee that the data you re-fetch from the database is the same as when you initially created your BO's?
We disabled the undo behaviour as you have suggested, and the system is responding very fast and out of memory problem has gone away, but we are not sure what exactly will stop working.
I can't say what disabling IEditableObject will do to a WPF app - no experience there. I have heard that WPF doesn't completely rely on existing data-binding infrastructure, so you may be OK. I also can't really speak to the performance/memory issue either, at least not without seeing more of how the screen is designed. 8,000 strings is a fair number, and certainly disabling the undo stack will help. But if you're just moving them from list to list, you shouldn't be gobbling up more and more memory.
HTH
- Scott
handaajay15:Platform: WPF/WCF/LINQ
In WPF you don't really need to disable n-level undo, as it isn't used automatically by WPF.
The CslaDataProvider control will automatically use n-level undo if you set ManageObjectLifetime=true - but the default is to not use n-level undo.
In other words, in WPF you shouldn't need to worry about this issue at all - it only gets used if you explicitly use it.
handaajay15:Another que: We frequently need to compare original value fetched from the database with current value of a property and based on the comparision, we write our business logic. Where and how to store the original value of the object fetched from the database. The old value is required on client as well on server. We do not want to serialize these old values between client and server to avoid the overhead.
Any help in the regard is highly appreciated....
We implemented a valuecache in our BO's. It's initially empty, but all the property setters will instantiate the cache (if necessary) and save the original value anytime a new value is provided for a property -- it wasn't very hard to implement, but would be tedious without a code generator for your objects.
It really helps to have though. We use it for database concurrency (e.g. detecting collisions), and also being able to tell if a property is "really" dirty (e.g. change a property, then change it back to original value. It's not actually changed any more).
rsbaker0:We implemented a valuecache in our BO's. It's initially empty, but all the property setters will instantiate the cache (if necessary) and save the original value anytime a new value is provided for a property -- it wasn't very hard to implement, but would be tedious without a code generator for your objects.
It really helps to have though. We use it for database concurrency (e.g. detecting collisions), and also being able to tell if a property is "really" dirty (e.g. change a property, then change it back to original value. It's not actually changed any more).
Yes, I think thats what we need. Any code samples would be highly appreciated to understand how to do it .
rsbaker0:We implemented a valuecache in our BO's. It's initially empty, but all the property setters will instantiate the cache (if necessary) and save the original value anytime a new value is provided for a property -- it wasn't very hard to implement, but would be tedious without a code generator for your objects.
One way to do this in CSLA 3.5 is to subclass FieldData and PropertyInfo. You can then extend FieldData to store original values or other metadata about the field behind each property.
I didn't do this directly in CSLA because the overhead is obviously quite high (doubling the size of the serialized byte stream to/from the server), but the design of the field manager is such that you should be able to add this functionality in a pretty seamless manner by just extending those two classes through inheritance.
RockfordLhotka:
One way to do this in CSLA 3.5 is to subclass FieldData and PropertyInfo. You can then extend FieldData to store original values or other metadata about the field behind each property.
Rocky,
Assuming someone chooses to subclass and extend FieldData to contain original values they may also want to modify the way that IsDirty is handled. If the original value is 1 and it is changed to 2 then it IsDirty. But if it is later changed back to 1 then they may consider that to be NOT dirty.
I made a quick attempt at subclassing FieldData and I was not very successful. Could someone please explain how to improve this code? Or are framework changes required?
Issues:
1. I cannot Override the Value or IsDirty property. I used Overloads here to remove the compiler warning.
2. This line of code does not even compile. How can I compare the new value to the Original value.
If MyBase.Value = _origdata Then 'Operator '=' is not defined for types 'T' and 'T'.
3. How do you make sure the original value is only set once? Will the idea shown below work for Integers?
Imports
Csla.CoreNamespace
BO<Serializable()> _
End Class
End
NamespaceJoe
On second issue….
This should compile and work:
If MyBase.Value.Equals(
_origdata) Then
On third issue I would use a variable “HasOrigianlValueBeenSet”
because Nothing could be a valid original value, technically…
Sergey Barskiy
Senior Consultant
office: 678.405.0687 |
mobile: 404.388.1899
Microsoft Worldwide Partner of the Year | Custom
Development Solutions, Technical Innovation
From: JoeFallon1
[mailto:cslanet@lhotka.net]
Sent: Wednesday, July 02, 2008 10:21 AM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] How to Disable N-Level undo on Editable Class
RockfordLhotka:
One way to do this in CSLA 3.5 is to subclass FieldData and PropertyInfo. You can then extend FieldData to store original values or other metadata about the field behind each property.
Rocky,
Assuming someone chooses to subclass and extend FieldData to contain original
values they may also want to modify the way that IsDirty is handled. If the
original value is 1 and it is changed to 2 then it IsDirty. But if it is later
changed back to 1 then they may consider that to be NOT dirty.
I made a quick attempt at subclassing FieldData and I was not very
successful. Could someone please explain how to improve this code? Or are
framework changes required?
Issues:
1. I cannot Override the Value or IsDirty property. I used Overloads
here to remove the compiler warning.
2. This line of code does not even compile. How can I compare the new value
to the Original value.
If MyBase.Value = _origdata
Then 'Operator '=' is
not defined for types 'T' and 'T'.
3. How do you make sure the original value is only
set once? Will the idea shown below work for Integers?
Imports Csla.Core
Imports Csla.Core.FieldManager
Namespace BO
<Serializable()> _
Public MustInherit
Class MyFieldData(Of
T)
Inherits FieldData(Of T)
Implements IFieldData(Of T)
Private _origdata As T = Nothing
Public Sub New(ByVal name As String)
MyBase.New(name)
End Sub
Public Overloads Property Value() As T
Get
Return MyBase.Value
End Get
Set(ByVal value As T)
MyBase.Value =
value 'sets IsDirty = True
If
_origdata Is Nothing
Then 'only set the
original value once. Will this work for non-reference values of type T?
_origdata = value
End If
If MyBase.Value =
_origdata Then 'Operator
'=' is not defined for types 'T' and 'T'.
MarkClean() 'sets IsDirty to False
End If
End Set
End Property
Public ReadOnly Property OrigValue() As
T
Get
Return _origdata
End Get
End Property
Public Overloads ReadOnly Property
IsDirty() As Boolean
Get
Dim child As
ITrackStatus = TryCast(MyBase.Value,
ITrackStatus)
If child IsNot Nothing Then
Return child.IsDirty
Else
If MyBase.Value
= _origdata Then 'Operator
'=' is not defined for types 'T' and 'T'.
Return False
Else
Return MyBase.IsDirty
End If
End If
End Get
End Property
End Class
End Namespace
Joe
Sergey,
Thanks for the quick response. Both ideas worked fine.
Private _hasBeenSet As Boolean = FalsePublic
Overloads Property Value() As TPublic
Overloads ReadOnly Property IsDirty() As BooleanSo now all we need is some advice on the Override vs. Overload issue.
Joe
They this for your overloads problem. Use Shadows instead
of Overloads. The only problem with this approach is that there is no equivalent
to Shadows in C#.
Public Shadows ReadOnly Property IsDirty() As
Boolean
Get
Dim child As
ITrackStatus = TryCast(MyBase.Value,
ITrackStatus)
If child IsNot Nothing Then
Return child.IsDirty
Else
If MyBase.Value
= _origdata Then 'Operator
'=' is not defined for types 'T' and 'T'.
Return False
Else
Return MyBase.IsDirty
End If
End If
End Get
End Property
End Class
Sergey Barskiy
Senior Consultant
office: 678.405.0687 |
mobile: 404.388.1899
Magenic ®
Microsoft Worldwide Partner of the Year | Custom
Development Solutions, Technical Innovation
From: JoeFallon1
[mailto:cslanet@lhotka.net]
Sent: Wednesday, July 02, 2008 10:42 AM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] RE: How to Disable N-Level undo on Editable
Class
Sergey,
Thanks for the quick response. Both ideas worked fine.
Private _hasBeenSet As Boolean = False
Public Overloads Property Value() As T
Get
Return MyBase.Value
End Get
Set(ByVal value As T)
MyBase.Value = value 'sets IsDirty = True
If _hasBeenSet = False Then 'only set the original value once.
_origdata = value
_hasBeenSet = True
End If
If MyBase.Value.Equals(_origdata)
Then
MarkClean() 'sets
IsDirty to False
End If
End Set
End Property
So now all we need is some adivce
on the Override vs. Overload issue.
Joe
It could be that those properties should be virtual. I think I erred on the side of caution, figuring I could relax the restriction later.
In the short term you can always implement IFieldData directly - don't subclass at all.
RockfordLhotka:It could be that those properties should be virtual. I think I erred on the side of caution, figuring I could relax the restriction later.
In the short term you can always implement IFieldData directly - don't subclass at all.
Yup. That was what I was getting at when I asked if framework changes were required. Any plans to make them Overriddable in 3.5.1?
Joe
I am willing to consider making them overridable.
You’ve done more research into this than I have (I just
considered it in the design and never prototyped it). Do you think it is
sufficient to make those two overridable?
Rocky
Yes. I think that making those 2 Overridable would make it easier/possible to implement the inheritance strategy you recommended. Re-creating the entire class without inheritance would work but a developer should not have to do that.
Joe
Yes, changed in svn in 3.5.1.
My only real worry here, is that a subclass will need to detect that it contains a child object and handle that correctly - so it still isn't necessarily trivial to write a subclass.
But then again, your PropertyInfo subclass could make that determination and return a standard FieldData rather than a custom FieldData for child objects.
Here is an excerpt from our value cache implementation. We have two different kinds of objects, one of which tracks changed values by backing field name (long story) and the other which uses the property names, so I kept the same interface for both. This code uses the property names.
To make this work correctly, you have to do two things in your property setter:
(1) Make the setter a "no-op" if the value passed in matches the current property value.
(2) Call "OnPropertyChanging(..)" below with the previous value of the property before updating your backing field with the new value.
#region
Changed Value Support // Changed Value Support modeled after WOMapperBusinessBase implementation, except it // uses property names only and ignores member fieldnames // Similar OnPropertyChanging() interface kept for consistency/switchability protected virtual void OnPropertyChanging(string propertyName, object oldValue, string memberfieldName){
// Enforce authorization rulesCanWriteProperty(propertyName,
true); // When editing existing object, cache original value on first property change // for dirty field/concurrency support. // NOTE: Differs from WORMapperBusinessBase implementation because uses property and not field names if (!IsNew && !String.IsNullOrEmpty(propertyName)){
CacheValue(propertyName, oldValue);
}
}
protected void CacheValue(string propertyName, object oldValue){
if (!ValueCache.Contains(propertyName))ValueCache.Add(propertyName, oldValue);
}
protected void ClearConcurrencyCache(){
if (!ReferenceEquals(_valueCache, null))_valueCache.Clear();
_valueCache =
null;}
protected System.Collections.Specialized.HybridDictionary _valueCache; protected System.Collections.Specialized.HybridDictionary ValueCache{
get{
if (ReferenceEquals(_valueCache, null))_valueCache =
new System.Collections.Specialized.HybridDictionary(); return _valueCache;}
}
protected T ReconstructOriginal(){
T copy =
this.Clone(); if (!ReferenceEquals(_valueCache, null) && _valueCache.Count > 0){
foreach (object key in _valueCache.Keys){
Util.
ReflectHelper.SetPropertyValue(copy, key.ToString(), _valueCache[key]);
}
}
return copy;}
protected void CopyChangedFields(T target){
if (!ReferenceEquals(_valueCache, null) && _valueCache.Count > 0){
foreach (object key in _valueCache.Keys){
Util.
ReflectHelper.SetPropertyValue(target, key.ToString(), _valueCache[key]);
}
}
}
protected bool IsDirtyProperty(string fieldMember)
{
object original; return IsDirtyProperty(fieldMember, out original);}
protected bool IsDirtyProperty(string fieldMember, out object original){
bool bDirty = false;original =
null; if (_valueCache != null && _valueCache.Contains(fieldMember)){
object cachedValue = _valueCache[fieldMember]; object currentValue =
Util.ReflectHelper.GetPropertyValue(this, fieldMember);
if (!object.Equals(cachedValue, currentValue))
{
original = cachedValue;
bDirty = true;
}
}
return bDirty;
}
#endregion
Copyright (c) Marimer LLC