Knowing early if an entity got loaded or re-loaded
Printed From: IdeaBlade
Category: DevForce
Forum Name: DevForce 2010
Forum Discription: For .NET 4.0
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=3190
Printed Date: 13-May-2026 at 11:36am
Topic: Knowing early if an entity got loaded or re-loaded
Posted By: stephenmcd1
Subject: Knowing early if an entity got loaded or re-loaded
Date Posted: 03-Jan-2012 at 4:50pm
|
We have some properties on our entities that get populated when the entity first gets materialized on the client (for example, we load some foreign key data, build some big data structures, etc.). Originally, we did this logic in the EntityManager.EntityChanged event (we see if the 'Action' is Add, AddOnImport, etc., etc.). This worked fine for a while but then we ran into a problem when an entity was refreshed (re-loaded from the database....for example, because we did a SaveChanges). In that case, we do get notified about the EntityAction.ChangeCurrentAndOriginal but it was too late. We do logic in the PropertyChanged logic and we often load entities via a call to EntityManager.Merge(...) but that call ends up firing an OnPropertyChange("") on the entity before the EntityChanged event gets raised. So then we moved our 'initialize' logic into Entity.OnPropertyChanged and check for empty string....if we see empty string, we assume we need to re-populate our properties.
Now we are running into a new problem. EntityManager.Merge(...) fires validations on entities before raising EntityChanged or OnPropertyChanged(""), so any validation logic we have that expects everything to be populated will fail. And at this point, we haven't come up with a good work-around for that.
Do you have any suggestions? Is there any way to know when an Entity has been loaded (or reloaded) very early on.....that is, before Dev Force starts calling validations on the entity or raising change notification for it? We've thought of a few options but they don't seem that great:
- Populate the data structures, load FKs, etc. every time somebody asks for the data. This defeats the whole purpose of trying to build the data up once to be used multiple times. We could try to 'cache' the built up data, but when the entity gets re-loaded from the DB, we'd need to know about that so we could clear the cache. But we don't have a Dev Force event that fires early enough to know we need to clear our cached values.
- Mark all our initialization data with [DataContract] so it gets serialized to the server and back. This seemed like it might work but then we'd be wasting a lot of bandwidth sending data to the server and back - and sometimes our initialization data can be quite large.
- Find some way to defer our validations during an EntityManager.Merge(...) call and then run the validations manually after the merge. This seems doable but then it's just one more hoop we have to jump through and then who knows what could be the next thing to break.
Any suggestions or feedback is appreciated!
Thanks, -Stephen
|
Replies:
Posted By: kimj
Date Posted: 04-Jan-2012 at 7:52pm
|
I assume you mean EntityCacheState.Merge(), or EntityManager.ImportEntities().... Entity validation is not usually performed during these operations unless the entity already had validation errors, so I'm not sure I understand the issue. If you find that property validation is occurring, you can turn it off by flipping the VerificationEnabled flag on the EntityGroup(s) while the merge is in process. After the merge, you can manually validate your entities by calling the Execute method on the verifier engine (em.VerifierEngine). But I'd also like more information on what's happening here, since validation shouldn't be occurring. You probably already know this, but the EntityManager and EntityGroup also have EntityChanging events, which since they fire earlier may do what you want. Here's more info - http://drc.ideablade.com/xwiki/bin/view/Documentation/entity-events - http://drc.ideablade.com/xwiki/bin/view/Documentation/entity-events .
|
Posted By: stephenmcd1
Date Posted: 04-Jan-2012 at 8:33pm
|
Thanks for response.
Yes, I meant EntityCacheState.Merge()....sorry for the confusion.
That explains a lot.....that validation will only occur for entities that already have validation errors. Our problem was somewhat intermittent but we hadn't figured out just what factors caused it to happen. But that makes sense....so we run into this problem when an entity has existing validation errors.
Somehow I most have overlooked EntityManger.EntityChanging (or maybe I tried it without success so long ago that I've forgotten :-)). I'll give that a try. I'm worried that that could be too early (didn't think I'd be saying that!) especially if that fires before the entity gets added to the Entity Manager or before navigation properties get fixed up, etc. But I'll give it a try.
Thanks!
|
Posted By: stephenmcd1
Date Posted: 05-Jan-2012 at 11:29am
I tried using EntityManager.EntityChanging event but ran into a few problems:
- Sometimes EntityChanging is fired before the Entity has even been added to the Entity Manager (I think this happens when you first query for an entity). This breaks a lot of my initialization logic because it's as if the entity is out floating around without being part of an Entity Manager....and without an Entity Manager, I'm pretty limited in what I can do.
- EntityChanging doesn't seem to be deferred doing a load while EntityChanged is. We need this defer logic because we might do a EntityCacheState.Merge(...) where we want to load a couple of related entities (say a parent and a couple children). The fact that EntityChanged is deferred is great because then we know that once either of the entities have EntityChanged on them, all the entities are loaded and navigation properties between them work. Without it being deferred, we won't know if the parent gets loaded before it's children exist, or maybe only some of it's children got loaded or maybe a child got loaded and the parent hasn't. To handle that case would greatly complicate our logic.
- The defer is also important for the validation that is done during a merge (if there are existing validation errors). Many of our validations span entities....and if you fire a validation on a child before the parent has been added and we've had a chance to do our initialization (since currently, EntityChanged seems to be the only place), bad things will happen.
It seems like our only possibility now (at least without any changed on IdeaBlade's side :-) - it would be great if maybe validations could be run after all the entities get merged in instead of one by one from within the IsLoadingBlock), is to turn off validation for all the Entity Groups before doing a Merge and then doing the validation logic ourself once the merge completes. The EntityGroups are specific to an Entity Manager instance, is that correct? I wouldn't want a merge on one Entity Manager to disable verification from happening on different Entity Manager.
|
Posted By: stephenmcd1
Date Posted: 05-Jan-2012 at 3:13pm
|
...also, disabling verification at the EntityGroup level doesn't seem to help. The Merge operation calls VerifierEngine.Execute(...) which seems to only check VerifierEngine.IsEnabled. It doesn't seem to care about EntityGroup.VerificationEnabled (that flag, only seems to stop the DataEntityProperty.SetValueWithVerification from triggering verfication.
And even if that did disable verification, I think I'd end up having to validate *all* the entities instead of just the ones that previously had errors since, by the time the merge finished, DevForce would have already cleared out any existing errors.
I'll just wait and hope you have some more options for me. :-)
Many thanks, -Stephen
|
Posted By: kimj
Date Posted: 05-Jan-2012 at 3:15pm
|
The logic surrounding entity events is unfortunately quite complex and our documentation is lacking, so I know all this is harder than it should be. Regarding these problems - 1 - Since EntityChanging will fire before an entity is added to the cache (when first queried or loaded), the entity will be detached. So, yes, that would make things difficult without an EntityManager. 2 - You're right that EntityChanging is not deferred. This is because the change can be cancelled by a handler. 3 - The validation done during the merge actually occurs once all the entities are loaded. For those entities which had validation errors previously, the errors are cleared and instance-level validation is performed. This does occur, though, before the deferred events are fired. Unfortunately instance-level validation can't be turned off with the VerificationEnabled flag on the EntityGroup, since this flag controls property-level validation. (This may be something we either change or better document.) You could disable the VerifierEngine for the duration of the merge, but you'd need to be careful if the same VerifierEngine was shared by multiple EntityManagers - by default on the client each EM has its own VerifierEngine. EntityGroups are specific to the EntityManager.
|
Posted By: kimj
Date Posted: 05-Jan-2012 at 3:20pm
|
Sorry, I didn't see the post from a few minutes ago when I answered. I might be out of options. You're right that the previous validation errors will be cleared. Maybe grab a list of those entities with validation errors prior to the merge, so you'll then know what to validate afterwards.
|
Posted By: stephenmcd1
Date Posted: 05-Jan-2012 at 3:53pm
Originally posted by kimj
... - by default on the client each EM has its own VerifierEngine. |
When we make new Entity Managers on the client, we do so via the constructor that takes a previous Entity Manager (so new ones are logged in, authenticated, etc.) - as suggested http://drc.ideablade.com/xwiki/bin/view/Documentation/multiple-entitymanagers#HCreateasecondEntityManager - here . Doing it that way, it seems Dev Force re-uses the VerifierEngine for the new Entity Manager (I couldn't find any documentation that states that but a quick peek in Reflector seems to show it). So in our case, I think there is only one VerifierEngine for the entire app. Or am I mistaken?
|
Posted By: stephenmcd1
Date Posted: 05-Jan-2012 at 6:11pm
|
A colleague of mine reminded me that the http://drc.ideablade.com/ApiDocumentation/IdeaBlade.EntityModel~IdeaBlade.EntityModel.EntityManager~_ctor%28EntityManager,EntityManagerContext%29.html - API documentation for the constructor does mention that the Verifier Engines can be shared:
| The VerifierEngine associated with this new EntityManager is determined by whether this ctor is called on the same thread as the entityManager passed in. If so, then the two entityManagers will share a single VerifierEngine, if not, a new VerifierEngine will be created for this entityManager. This behavior is intended to avoid threading issues because VeriferEngines ( like EntityManagers) are not thread safe. |
So I suppose it should theoretically still be safe to disable verification on the Verifier Engine since there shouldn't be multiple/concurrent threads trying to validate something using the same VerifierEngine. It could start to be an issue on the server but I think VerifierEngines are a bit different on the server? And we don't do much validation on the server anyway.
|
Posted By: kimj
Date Posted: 06-Jan-2012 at 9:14am
|
Yes, if using the EntityManager copy constructor the VerifierEngine will be shared if the EMs are on the same thread. You can always set a new VerifierEngine for an EM, however, by using EM.VerifierEngine = new VerifierEngine() or similar. On the server a threadsafe ConcurrentVerifierEngine is used by default.
|
Posted By: stephenmcd1
Date Posted: 06-Jan-2012 at 2:17pm
|
Thanks for all the help and insight. As it stands now, I think my best option is to disable verification while I'm doing the merge. I think I found a way to know exactly what entities need to be re-verified after the merge......I temporarily attach a handler to EntityChanging and see if the entity has validation errors when that event is raised. In that case, I can add that entity to a list of ones that need to be verified.
For reference, I've included my custom version of Merge. Other than the threading issues that will likely crop up on the server, I think it's a decent solution. If you see anything glaringly wrong with it, let me know! Thanks.
/// <summary>
/// Merges this <see cref="EntityCacheState"/> into an <see cref="EntityManager"/>. This method will
/// defer validation until after all entities have been merged and initialized.
/// </summary>
/// <param name="cacheState">The data to merge</param>
/// <param name="em">EntityManager to merge data into</param>
/// <param name="restoreStrategy">The restore strategy to use</param>
/// <returns>An collection of original entityKeys and the EntityAspects that they were merged into</returns>
/// <remarks>
/// <b>Merge</b> is functionally equivalent to the
/// <see cref="CacheStateManager.RestoreCacheState(EntityCacheState, RestoreStrategy)" />
/// method in <see cref="T:IdeaBlade.EntityModel.CacheStateManager" />.
/// </remarks>
public static IEnumerable<Tuple<EntityKey, EntityAspect>> SafeMerge(
this EntityCacheState cacheState,
EntityManager em,
RestoreStrategy restoreStrategy)
{
//This will keep track of the entities that were affected by the Merge and had previous
// validation errors. DevForce will clear those validation errors and then try to validate
// the updated entity. Since that will be too early for our liking (we want entity initialization to have
// occurred), we will have validations disabled. Once the Merge is complete, we'll manually validate
// these entities to get the same behavior Dev Force would otherwise have.
var toValidate = new List<Entity>();
//An event handler that will add the entity to our list if the entity has errors
// (we don't use an anonymous method so we can unsubscribe after the Merge)
EventHandler<EntityChangingEventArgs> entityChanging
= (sender, eventArgs) =>
{
if (eventArgs.EntityAspect.ValidationErrors.Any())
toValidate.Add((Entity) eventArgs.Entity);
};
//Watch for entity changes (this will be fired before DevForce clears out the validation errors)
em.EntityChanging += entityChanging;
//Disable validation
//HACK: This is only thread-safe on the client. Need to revisit when executed on the server
em.VerifierEngine.Enabled = false;
//Perform the merge as usual
var results = cacheState.Merge(em, restoreStrategy);
//Re-enable validation
em.VerifierEngine.Enabled = true;
//We don't care about this event any longer
em.EntityChanging -= entityChanging;
//Go through all the entities that we need to validate and validate them
foreach (var entity in toValidate)
entity.EntityAspect.VerifierEngine.Execute(entity);
return results;
}
|
Posted By: kimj
Date Posted: 06-Jan-2012 at 6:51pm
|
Looks good. So in an ideal world, what would DevForce provide you that would make what you want to do easier? Not necessarily just in the merge logic ....
|
Posted By: stephenmcd1
Date Posted: 20-Jan-2012 at 1:25pm
|
In our case, we don't want anything to happen on entities until after the EntityManager.EntityChanged event has been raised on it. 'anything happen' is a bit vague but that includes not validating them in this case. Ideally it might also mean that property change notifications don't fire on them (we have run into problems where DevForce fires a PropertyChanged with empty/null PropertyName [meaning the whole entity was reset] pretty early on when an entity is materialized or merged or rematerialized. In that case, the UI will start trying to interact with the entity but EntityChanged hasn't fired to give us a chance to prepare it.
I suppose in a perfect world, there would be some event (or protected virtual method) on the Entity that is like an 'initialize' that gets called when the entity is first materialized but before 'stuff' happens to it. And probably an event (or method) that gets called subsequent times when the entity gets reset (for example after a merge or re-query).
|
|