QuoteReplyTopic: ForcePropertyChanged does not fire interceptors Posted: 11-Aug-2009 at 9:37am
Hi,
We use the Server Push Notification to push changes from a client to all other clients that have registered to the Notification Service. Below is the Code we use on the Client to update it's cache.
Now everything seems to be working as expected but somehow the changes are not reflected back on the Masks. As you can see we use the 'entity.EntityAspect.ForcePropertyChanged(null);' (in the switch with 'case EntityState.Modified') to try and force all interceptors in the viewmodel to fire (which in turn would change the values in the DependecyProperties we use in WPF). This however doesn't happen. Any clues on why not?
Or even better, is there another way to provoke the Interceptors to fire. The 'ForcePropertyChanged' would result in the Entity getting state 'modified' which is not what we want.
We know that using 'OverwriteChanges' as MergeStrategy is not favoured. We will work on that too.
Regards,
Paul Sinnema
Diartis AG
/// <summary>
/// Gets called from the server if it has something to tell
/// </summary>
/// <param name="userToken"></param>
/// <param name="args"></param>
private void PushNotificationCalled(object userToken, params Object[] args)
{
// We should not handle our own changes here
//if (!userToken.Equals(m_GuidUserToken))
//{
//}
MemoryStream stream = (MemoryStream)args[0];
stream.Seek(0,
SeekOrigin.Begin);
BinaryFormatter bf = new BinaryFormatter();
EntityContainer ec = (EntityContainer)bf.Deserialize(stream);
KLIBEntityManager manager = VMContext.DefaultManager;
Entity entity;
EntityKey key;
foreach (EntityInfo ei in ec.List)
{
switch (ei.State)
{
case EntityState.Added:
key =
new EntityKey(ei.Type, ei.KeyValues);
// manager.GetBy
manager.DefaultQueryStrategy =
QueryStrategy.DataSourceOnly;
entity = (
Entity)key.ToQuery().FirstOrNullEntity();
if (!entity.EntityAspect.IsNullEntity)
{
manager.AddEntity(entity);
}
manager.DefaultQueryStrategy =
QueryStrategy.Normal;
break;
case EntityState.Deleted:
key =
new EntityKey(ei.Type, ei.KeyValues);
entity = (
Entity)manager.FindEntity(key, /*includeDeleted=*/true);
if (!entity.EntityAspect.IsNullEntity)
ForcePropertyChanged(null) just fires a PropertyChanged event for the item itself (the property name is an empty string or null). Listeners, like bindings, may call the property getters on the item, and in doing so the get property interceptors will be called, but firing the event won't itself force the interceptors to be called, and also won't change the EntityState of the item.
Unfortunately, there is no way to force the interceptors to fire. You can, with some pain, obtain the interceptor actions and invoke them, but this is difficult because these methods take generic arguments and require that you already know the properties and data types (e.g, Employee.EmployeeIdDataProperty.GetterInterceptor.GetActions() or PropertyInterceptorManager.CurrentInstance.GetActions<TArgs>(type, property, mode)).
A somewhat heavy handed approach would be to invoke the getters (and setters) to force the interceptors to be called. (In the next release, setting a value to itself will not dirty the item as it does now.) Something like this will invoke the getters for data properties (not navigation properties) -
var props = _entityManager.MetadataStore.GetEntityMetadata(typeof(Employee)).DataProperties; foreach (DataEntityProperty p in props) { p.GetValue(anEmployee); }
Since we use T4 for Generating standard functionality for each Entity (like the 'Create()') we're able to use the Employee.EmployeeIdDataProperty.GetterInterceptor.GetActions() construct you propose. I've generated something like this:
That's cool that you're using T4! The GetActions() call only retrieves the actions, however; it doesn't invoke them. You'll also need to create interceptor arguments appropriate to the property and call each action:
var actions = AbstractEntity.IdEntityProperty.GetterInterceptor.GetActions(); var args = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,int>(AbstractEntity.IdEntityProperty, anEntity, null); foreach (var a in actions) { a.Action(args); }
After going to all this trouble, though, you're doing almost what the GetValue() call does - really everything but returning the property value - so it might be easier to call GetValue(), unless you what to exclude certain actions.
Yeah.... T4 is realy cool. It's not easy to program in T4 though. You've always got 2 mingled languages (C# in the Template and C# being generated) but the result is pretty cool. Here's what I generate now. I've still not been able to test this. I'll let you know the result.
// [DebuggerNonUserCode] public void Refresh() {
var actionsId = AbstractEntity.IdEntityProperty.GetterInterceptor.GetActions(); var argsId = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,Int32>(AbstractEntity.IdEntityProperty, this, null);
foreach ( var a in actionsId) { a.Action(argsId); }
var actionsCreatedOn = AbstractEntity.CreatedOnEntityProperty.GetterInterceptor.GetActions(); var argsCreatedOn = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,DateTime>(AbstractEntity.IdEntityProperty, this, null);
foreach ( var a in actionsCreatedOn) { a.Action(argsCreatedOn); }
var actionsCreatedBy = AbstractEntity.CreatedByEntityProperty.GetterInterceptor.GetActions(); var argsCreatedBy = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,Int32>(AbstractEntity.IdEntityProperty, this, null);
foreach ( var a in actionsCreatedBy) { a.Action(argsCreatedBy); }
var actionsChangedOn = AbstractEntity.ChangedOnEntityProperty.GetterInterceptor.GetActions(); var argsChangedOn = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,DateTime>(AbstractEntity.IdEntityProperty, this, null);
foreach ( var a in actionsChangedOn) { a.Action(argsChangedOn); }
var actionsChangedBy = AbstractEntity.ChangedByEntityProperty.GetterInterceptor.GetActions(); var argsChangedBy = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,Int32>(AbstractEntity.IdEntityProperty, this, null);
foreach ( var a in actionsChangedBy) { a.Action(argsChangedBy); }
var actionsDeletedOn = AbstractEntity.DeletedOnEntityProperty.GetterInterceptor.GetActions(); var argsDeletedOn = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,DateTime?>(AbstractEntity.IdEntityProperty, this, null);
foreach ( var a in actionsDeletedOn) { a.Action(argsDeletedOn); }
var actionsDeletedBy = AbstractEntity.DeletedByEntityProperty.GetterInterceptor.GetActions(); var argsDeletedBy = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,Int32?>(AbstractEntity.IdEntityProperty, this, null);
foreach ( var a in actionsDeletedBy) { a.Action(argsDeletedBy); }
Looks good, except be sure to use the correct property when you create the args -
var actionsCreatedOn = AbstractEntity.CreatedOnEntityProperty.GetterInterceptor.GetActions(); var
argsCreatedOn = new
DataEntityPropertyGetInterceptorArgs<AbstractEntity,DateTime>(AbstractEntity.CreatedOnEntityProperty,
this, null);
I've changed the code as suggested. But it still doesn't find any Interceptors. I guess it's because we don't have any 'Get' Interceptors. We only have 'After Set' interceptors in our ViewModel. Below is what we generate in the ViewModel to intercept the Set actions on the Model. These Actions take care of forwarding the Value of the Properties to the WPF UI.
var actionsId = AbstractEntity.IdEntityProperty.GetterInterceptor.GetActions(); var argsId = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,Int32>(AbstractEntity.IdEntityProperty, this, null); foreach ( var a in actionsId) { a.Action(argsId); }
var actionsCreatedOn = AbstractEntity.CreatedOnEntityProperty.GetterInterceptor.GetActions(); var argsCreatedOn = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,DateTime>(AbstractEntity.CreatedOnEntityProperty, this, null); foreach ( var a in actionsCreatedOn) { a.Action(argsCreatedOn); }
var actionsCreatedBy = AbstractEntity.CreatedByEntityProperty.GetterInterceptor.GetActions(); var argsCreatedBy = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,Int32>(AbstractEntity.CreatedByEntityProperty, this, null); foreach ( var a in actionsCreatedBy) { a.Action(argsCreatedBy); }
var actionsChangedOn = AbstractEntity.ChangedOnEntityProperty.GetterInterceptor.GetActions(); var argsChangedOn = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,DateTime>(AbstractEntity.ChangedOnEntityProperty, this, null); foreach ( var a in actionsChangedOn) { a.Action(argsChangedOn); }
var actionsChangedBy = AbstractEntity.ChangedByEntityProperty.GetterInterceptor.GetActions(); var argsChangedBy = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,Int32>(AbstractEntity.ChangedByEntityProperty, this, null); foreach ( var a in actionsChangedBy) { a.Action(argsChangedBy); }
var actionsDeletedOn = AbstractEntity.DeletedOnEntityProperty.GetterInterceptor.GetActions(); var argsDeletedOn = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,DateTime?>(AbstractEntity.DeletedOnEntityProperty, this, null); foreach ( var a in actionsDeletedOn) { a.Action(argsDeletedOn); }
var actionsDeletedBy = AbstractEntity.DeletedByEntityProperty.GetterInterceptor.GetActions(); var argsDeletedBy = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,Int32?>(AbstractEntity.DeletedByEntityProperty, this, null); foreach ( var a in actionsDeletedBy) { a.Action(argsDeletedBy); }
var actionsUserToken = AbstractEntity.UserTokenEntityProperty.GetterInterceptor.GetActions(); var argsUserToken = new DataEntityPropertyGetInterceptorArgs<AbstractEntity,Guid?>(AbstractEntity.UserTokenEntityProperty, this, null); foreach ( var a in actionsUserToken) { a.Action(argsUserToken); }
I've solved the problem just partly. I've written an Interface that is added to the partial Classes generated by DevForce. This interface has a 'Refetch' Method which is called when the Server Push tells our PushNotification Routine on the Client an Entity has changed. The Refetch in turn refetches the Entity and fires an Event called 'Refetched'. Our ViewModel in turn registers itself for this Event and fires a Refresh routine. This routine, which is generated, simply takes all the values from the Entity and fill the Dependency properties of WPF (pfew.... hope you understand what I've been writing here).
The problem is however solved only partly. We have a property called 'FullName' which is a calculated field (which has been giving us some headache already, see thread: http://www.ideablade.com/forum/forum_posts.asp?TID=1345). The Fullname is not in the EF Model and thus not in the DevForce model. We can only generate code for properties that are in the EF Model so no refresh is done for the 'FullName' property.
Could you give me an example on how to find all registered 'Set' Interceptors without invoking them. I would like to write a routine that gathers all 'Set' interceptors and then gets the values from the properties of these Interceptors without invoking them. What we needs is:
A routine to get all the 'Set' interceptors (including our 'FullName' interceptor) and...
a routine to get the values from the properties without invoking any interceptors using the values from the previous routine
I guess that our 'FullName' interceptor we wrote for a calculated field will fire it's 'BeforeGet' too when we try to get it's value.
Entity properties have both a GetterInterceptor and a SetterInterceptor (except for NavigationListEntityProperties, which don't have setters) so you can always obtain the actions from the appropriate interceptor. Calling setter actions is a little tricky, however, since you need to determine what property value, if any, you pass to the action.
Here's how to obtain and invoke the setter actions for a property.
var actions = AbstractEntity.IdEntityProperty.SetterInterceptor.GetActions();
// Now set up arguments and invoke - var args = new DataEntityPropertySetInterceptorArgs<AbstractEntity, int>(AbstractEntity.IdEntityProperty, this, null); args.Value = ??????? args.Tag = "dummy call"; foreach (var a in actions) { a.Action(args); }
I'm not really sure if this is what you need, though. You said you want to "get the values from the properties without invoking any interceptors", which may be something entirely different. Setter interceptor actions are code that runs before or after a property value is set, but the value comes from elsewhere. Getter actions would be the code run before or after the value is obtained from the "backing store". In both cases, if you invoke the interceptor actions separate from the actual property getter or setter, then you need to be careful with the arguments received by the action (e.g., post-get actions will not have an args.Value since the actual get wasn't performed).
It is possible to temporarily "skip" actions. Using AddSkipKey and RemoveSkipKey on the interceptor you can indicate the named action to be skipped. You must provide a key name on each action, and to add or remove multiple actions you need to call the method for each key.
// Action won't be called when do the get now var name = anEntity.Id;
// Add the action back AbstractEntity.IdEntityProperty.GetterInterceptor.RemoveSkipKey("A");
I'm not certain how the performance is with this, so you may need to do some timings if this is in a performance critical piece of your code.
Another option is to use the Tags property in the getter or setter arguments to indicate what the action should do. You could set the Tag when building the arguments, as shown at the beginning of this post.
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot delete your posts in this forum You cannot edit your posts in this forum You cannot create polls in this forum You cannot vote in polls in this forum