This thread looks to be a little on the old side and therefore may no longer be relevant. Please see if there is a newer thread on the subject and ensure you're using the most recent build of any software if your question regards a particular product.
This thread has been locked and is no longer accepting new posts, if you have a question regarding this topic please email us at support@mindscape.co.nz
|
Hi, I'm trying to extend class generated by LS. One of the problem I have is that I need to call a initialization function once after an Entity has been initialized and after it's filled with data from DB. I can't do this in the constructor because the data has not been read-in. I can't call this initialization function everytime I access DB as it's redundant. I'm sure there is a way to call a callback function like "Loaded" which is called once after data has been filled. Does anyone know how to do this? Thanks.
|
|
|
Ah.. I found there is AfterLoad function. I'll give it a try. ^^
|
|
|
Hmm... I found a bug in the code. Within AfterLoad function, I need to reference associations but at this point, association has not been loaded, cauing an exception error. I think AfterLoad function should be called after all other association have been loaded.
|
|
|
The problem is that associations aren't necessarily going to be loaded at any point -- associations are lazy loaded by default, and LightSpeed is not able to interrupt the database read in progress in order to perform another (within the same unit of work; of course, two different UOWs can perform database reads in parallel). If you want to be notified after a query has completed, consider overriding the UnitOfWork.Find(Query, IList) method to intercept the result and perform your post-processing. When this returns, the IList will contain all entities resulting from the query. Any eager loaded associations will by now have been wired up, and LightSpeed will be ready to load lazy loaded associations. E.g. partial class MyUnitOfWork { (The reason for using the (Query, IList) overload is that this is the one called by all other overloads and LINQ queries. If you are using stored procedures or FindBySql then you would also need to override those methods.) Alternatively, if you can provide more information about your specific use case then we may be able to come up with a more specific solution or an enhancement to suit your needs. |
|
|
Hi Ivan, I guess my use case is clear as stated before. I need to perform initialization of Entity (user extended through partial class) once after it's read-in from DB. I tried the initialization in AfterLoad that references the association, and it currently throws exception error as you explained. Thus, I cannot use AfterLoad and I'm calling my own initialization with OnceOnly flag after each db query. I have to call this initialization each time because I don't know when it first load from DB and it's causing some headaches. Your suggestion to override Find might work, but then it's doens't seem like the clean solution. I do not know exactly how the lazy loading mechanism work, but the name AfterLoad suggest that and it should be ready to handle lazy loading. Otherwise, AfterLoad is not AfterLoad after all. I believe it can be done by changing an order when AfterLoad called. (If it get's called at the very end of load, I believe it will just work) If not, we add an event, "Loaded", like WPF's "Loaded" event. It will be triggered after the load has been completed.
I appreciate for your help. Cheers! -chris |
|
|
Sorry, by 'use case' I meant the problem you are trying to solve. I understand your proposed solution, but I am trying to get a handle on what you are trying to achieve in case there is a easier solution or more suitable enhancement. Put another way, what do you want to do in AfterLoad and why? Unfortunately, the change to the timing of AfterLoad is not as simple as you suggest because of the way the eager load graph is traversed. We could defer the AfterLoad call for the primary results of the query as you suggest, but it is rather harder to defer it for eager loaded entities. We are open to looking at this but we would prefer to investigate simpler alternatives first, which is why I want to understand the actual requirement. |
|
|
Hi, Ivan, I better show you the code sniffet to better illustrate but there is nothing special. 1. Load a record from DB and call Prepare(). Prepare() is the intialization step and should be replace by AfterLoad() callback function. public void LoadMap() { CurrentMap = Umud.Maps.First(i => i.Id == 64); } 2. There is a flag, _isPrepared so that it's initialized only once. this.Layer is the reference to the association (through foreign key) but it's not available when called from AfterLoad(), casuing the runtime error. public partial class Map { public void Prepare() { if (_isPrepared) return; Restrict = MapRestrict.no_restriction; MapType = Umake.MapTypes.First(i => i.Id == this.TypeId); Tiles = new byte[Width, Height]; // consolidate all tiles. foreach (Layer layer in this.Layers) { layer.SetSize(Width, Height); for (int i = 0; i < Width; i++) for (int j = 0; j < Height; j++) if (layer.Tile(i, j) != 127) Tiles[i, j] = layer.Tile(i, j); } _isPrepared = true; } } I hope it's clear now. Thanks. -chris
|
|
|
Hi Chris, If I understand this correctly, you're basically caching precalculating some data so that you can cache it within the entity for subsequent use. Is that correct? If so, an alternative to AfterLoad would be to lazy initialise that data when it is first needed e.g. private bool _isPrepared; public MapType MapType { // similarly for Tiles private void EnsurePrepared() { This avoids the need for application code to remember to call the Prepare() method, and also avoids the cost of initialising the lazy data if it is never used. I realise it makes the MapType and Tiles getters a bit more complicated than if you eager initialised the data, but that complexity is safely hidden within the class. And you might even be able to use the Lazy<T> type to simplify it (not sure, haven't investigated). Would this work for you or have I misunderstood the requirement? |
|
|
Hi, Ivan, Thanks for your answer. It looks like it will work but it requires carefully handling of properties, and makes code less readable. To me, it seems like a kludge rather than the solution. I think it's still possible to do this in AfterLoad if AfterLoad get called very at the end of Entity loading. In other words, builds list of AfterLoad to call but wait until the very end and call them. This way, it's the same as calling "Prepare" right after loading an Entity. What do you think? Thanks. -chris
|
|
|
We agree it would be nice to defer AfterLoad until all entities have been loaded, but given that there is a relatively easy workaround in this case, and that deferral could have performance implications for large loads, it's not something we're planning to spend time on right now. We will add it to the wish list though -- thanks for the suggestion! |
|
|
Hi, Ivan, Hmm.. perhaps there is some misunderstanding. I don't understand why it would have performance implication. The lazy loading will still work as before. Right now, AfterLoad cannot load association, therefore behavior of existing code will be the same, not affected by changes. It more like giving a choice to user to load association or not in AfterLoad. If not, AfterLoad is probably a misnomer as it stands
Cheers! -chris
|
|
|
The performance implication is because we would need to maintain a separate queue of entities on which AfterLoad still has to be called, instead of calling it as we go. This adds memory pressure during large loads. I don't think this is likely to be a huge issue, but testing for it is yet another factor in deciding whether to spend time on this change or not. As I have said, we have assessed effort versus impact, and have decided that we will not implement this change right now but we have noted the suggestion and will consider it for a future release. Again, thanks for the suggestion -- we are listening but we do have to decide where to allocate our resources. |
|
|
Hi, Ivan, I have Hierarchical "TreeListViewer" that displays nested properties. I spend some time trying to resolve this issue this weekend and I ran into problems. These properties are read-in from DB and I need to "Prepare" them before I can display. The work-around you suggested would not work because, properties are nested (association leads to other association and so on) and it's gets pretty complicated. I was wondering if there is a chance to fix "AfterLoad" sooner. I think it's more like a bug than an enhancement. The name "AfterLoad" suggest that it should be able to reference associations, otherwise it's too limiting. I would like a callback when after all association are bounded. If the callback doesn't reference associations, nothing will be loaded, thus lazy loding still works the same way. I'm sure you can think of some clever ideas to make the performance implication minimal. (well.. it's just having extra queues only once when it's first loaded and I don't think it will be too bad) Even if there are some penalties, I prefer having this. I would really appreciate if you can help me on this.
Thanks. -chris
|
|
|
Hi Chris, As previously mentioned, we are not currently planning to implement the requested changes to AfterLoad. We will review this if other customers ask for this feature, but at the moment there are other areas where we are focusing our resources. We will not be renaming the AfterLoad method because this will break existing code. As previously noted, you can implement end-of-query processing by overriding the UnitOfWork.Find method, or you can prepare entities on demand using the _isPrepared technique discussed above. If you don't think either of these strategies would work then perhaps you could say a bit more about what you need to do in terms of 'preparation' and we can give you further advice. We have several other customers displaying LightSpeed hierarchies in tree views and we're not sure what's different about your scenario. |
|
|
Hi, Ivan, I understand that resources are scares and I hope someone else reads this post and vote. I tried to explain the problem I have and I'm sure you understand. Imagine making a property editor that can display nested properties. These properties are read-in from DB as FK association. I need to prepare them before I display; depends on properties, I need to make connection to other associated entities, change colors, add icons depends on property type that are also stored in DB, and so on. I just pass a collection to "TreeListView" and use DataTemplate to display tree items. Basically, when there is a entity "load" I need to call "Prepare", and each sub-trees are nested associations and I can't seem to inject "Prepare" properly.
Ivan, how about you help me implment AfterLoad? Basically, I need build a list of entities when it's first loaded, and call "AfterLoad" at the end. I'm thinking about storing the list of entities somwhere and check to see if it's non-null at the end of query. Do you think it will work? And are there anything else I need to worry about?
Thanks. -chris
|
|
|
From the fact that you're talking aboiut DataTemplates, I'm assuming you're in a WPF situation, right? So in that case the _isPrepared solution should work fine. WPF data binding happens asynchronously, so by the time WPF tries to bind to your viewmodel properties (e.g. colour and icon) all loading will be complete. That means your colour and icon properties can perform the necessary queries: public Color Color { private void EnsurePrepared() { The big benefit of this is that you don't need to 'inject' the prepare step. It happens automatically if and when it is needed. I'm really not sure why you would not want to do this; I actually prefer this to doing it in AfterLoad because it saves me initialising viewmodel data if I'm using the model in a different application (e.g. batch processing). Regarding implementing the AfterLoad changes yourself, I think you basically need to accumulate results as you go along. Each chunk of eager loading accumulates results in ObjectLoader.CurrentResults (see ObjectLoader.cs line 230). You can duplicate this as you go into a shared collection, then call AfterLoad across that shared collection just before returning from ObjectLoader.LoadInternal. Does that give you enough to get started? |
|
|
Hi, Ivan, Thanks for a quick reply. I would use "Color" as you described but I need to do that for every association (even though it's the same entity, I have to do this everywhere because if it's used in several places) which is not so elegant. (doing it once in AfterLoad is much more elegant solution) And what if it's a collection type that are generated by LS? I guess I can define another collection that wraps-around LS generated collection. It starting to become messy and I'm trying to avoid that.
I'll take a look at the code you mentioned. Thanks. -chris
|
|
|
Hi, Ivan, It works like a charm!!! I just had to add few lines of code.
Thanks. -chris |
|