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
|
My Question:
Is there a way to delete an entity, that belongs to a collection, outside of a UnitOfWork and have it save properly to the database? My Scenario: I have a parent table called Employee and child table called AlterEgo which are joined in a one-to-many relationship (the AlterEgo table has an EmployeeId as a non-nullable foreign key) This all works perfectly fine UNLESS I try to delete one of the AlterEgos from the Employee's child collection(Employee.AlterEgos.Remove(myAlterEgo)). If I try to delete an AlterEgo from the Employee's collection of AlterEgos: And obviously at this point I no longer have access to that entity (as it has been removed from the collection) to manually fix the problem so that I can push the delete through. I have noted that if I perform the delete from WITHIN the UnitOfWork, the entityState of the AlterEgo is properly set to "Deleted" and the AlterEgo Entity is successfully deleted. Am I just taking a wrong step in my process? Is there a way to make this work? Thanks! |
|
|
I don't think there's a way to do this (though I'd need to dig a bit deeper to be sure). The way to make it work is to be enrolled in a unit of work while making your changes. The unit of work represents a... well... a unit of work -- the set of entities and changes required for a particular piece of work (a business transaction). If you need to modify an entity or its associations, you should normally be doing that in a unit of work. The lifecycle should usually be: create unit of work, load entities, modify entities, save unit of work, dispose unit of work. And at this point the business transaction is done: you can keep the participants around, but if you want to make further changes you should enrol them in a new business transaction (UnitOfWork) first, or get updated copies as part of that new business transaction (perform a new Find, typically using the Id from the old entity). So the "wrong step" may be detaching from the UOW in the first place. You're probably doing this because you know that short-lived units of work are good, and that's true, but there's such as thing as being too short-lived *grin*. Your modifications of the Employee and its AlterEgos collection are part of a unit of work in the abstract sense of the term -- they are part of a changeset that you plan to commit -- and should therefore be carried out as part of a LightSpeed UnitOfWork. So one solution is to keep the original unit of work alive until you have finished with the entities involved. Alternatively, if the work done by the old UOW really is complete, and you really are starting a new business transaction, but you do not want to reload fresh copies of the entities, the "wrong step" may be the order in which you perform the Attach and the changes. If you Attach to a new UnitOfWork, and then start making the changes, you should be good to go. We will, by the way, investigate the error anyway, because it is anomalous that you can add AlterEgos but not remove them while detached; we agree it would be nice to be able to make "offline" changes. But we reiterate that the preferred "fix" is to have a live UnitOfWork to correspond to your logical unit of work / business transaction.I hope this advice doesn't sound too abstract. If you could provide some more info about why you want to perform these changes outside a UnitOfWork, and about the business transactions (logical units of work) involved in your scenario, we can try to offer some additional guidance. |
|
|
Ivan, Our team is interested in this as well. Particularly - in the capability to modify object graphs outside of the scope of a UnitOfWork instance with the intent to re-attach later and propagate those changes to the database. Here are the possible scenarios we would like to support: 1) Expose fully serialized LS derived Entities over WCF service boundares to be consumed by rich .net client apps (purposefully ignoring the SOA tenet of 'contract only' to avoid having to maintain business logic on 'server side' entities as well as 'client side' entities. We have lots of calculations and rules attached to our domain objects that would be great to have available on both the application server and the WPF rich client we are building. 2) Having (relatively) 'long lived' UnitOfWork instances that are alive for as long as a user may keep aggregate root related application screens open (hours, or days, rarely weeks). I recognize that this usage pattern represents a departure from the Fowler originated Unit of Work, but certainly would allow for the scenarios mentioned above. Comments? The "Unit of Work Mechanics" chapter in the LS documentation states: "A unit of work is designed to be short-lived: typically you will create the unit of work, load and/or modify some data, save changes if required, and then dispose of the unit of work." What does "short-lived" mean in this case? For a Windows app / For a Web app? Assuming that I can't expect to make these kinds of changes outside of a Unit of Work these are the following architectural options I can imagine. 1) Eliminate the WCF service layer and have a (relatively) long lived unit of work in the rich client. This would raise some infrastructure related concerns as services were intended to be adopted to aid bandwidth constrained remote locations. It opened up the opportunity to compress traffic; queue transactions, etc. 2) Take a more traditional "SOA" approach and exose DTO objects to rich clients. This would probably mean that our services would be more "repository-oriented" than business oriented so that the domain classes (now completely decoupled from LS) would be excercised mainly on the client side. This would seemingly require custom state change tracking to occur on the client side or would require the services to hydrate the persisted versions of the DTO's, compare them with incoming DTO's and make changes within a Unit Of Work instance. If we were to take this approach, is there a mechanism that could be used outside of the designer to generate DTO's? 3) Extend LightSpeed's EntityCollection or Entity classes so that deletes are tracked and acted upon once a Unit Of Work is in play. (perhaps before update?) What are your suggestions?
|
|
|
Thanks for your response, Ivan. Currently we are developing a thick client in which our windows frequently have several controls that are bound to a collection of entities. At this time we are retrieving the entities using the UnitOfWork and detaching them for the lifetime of the form, then reattaching them as the user designates the need to save their work. My next question would be: Does a UnitOfWork maintain an open connection to the database? (Our biggest concern over keeping the UnitOfWork alive, is that the user may have a given form open for hours or even days as they slowly complete their work. If the connection to the database stays open for these long stretches of time, it may become very unmanageable.) Thanks
|
|
|
A UnitOfWork maintains an open connection to the database from when it first needs one. So just calling CreateUnitOfWork() does not open a connection; but if you load an entity from the database into a UnitOfWork, that opens the connection and it will remain open from then on (until the UOW is disposed). Beware that the connection may be opened even when you're not explicitly loading or saving, for example when traversing an association or when deleting an entity (so that LightSpeed can prepare any required cascade deletes). JD is writing a response to Aaron's post which will provide some more advice around patterns for long-lived entities; that should be up a bit later and should address your scenario as well. |
|
|
Hi guys, Appreciate your feedback on how you're working with LightSpeed and we're keen to do whatever it takes to help ensure you get a rock solid application using LightSpeed. I'm going to focus more on discussing the unit of work lifecycle and ways to work with objects being added/attached/etc rather than discuss the specifics of a distributed environment (I'll let Jeremy focus on that as he has more experience with those specific scenarios than I do). Generally there is nothing overly wrong with a long running unit of work as long as you can appreciate that the application usually imposes standard unit of work boundaries. For example, when editing an entity on a pop up dialog it would be safe to assume that the save action wraps a unit of a work. The parent form which may be open for weeks would likely not stay in a connected state but you would need to provide users with some mechanism for fetching data - such as a refresh button. To discuss these two aspects in more detail: Save buttons If you have a dialog where a user may be editing an object for a considerable length of time the best practice is to retrieve the latest version of the entity when you load the editing screen and close the unit of work. This way you do not run into any issues with holding a unit of work open too long. When the user finishes editing the object create a new unit of work and attach the object and save the changes. Problems here: Concurrency issues potentially can occur with this approach. If another user edits this object between loading the entity and saving it you can run into issues. Use optimistic concurrency and handle the concurrency violation exception and allow the user to reconcile the changes somehow. We're open to getting feedback on this scenario to make it easier for developers to work with highly concurrent entities. Refresh buttons The host application will likely be displaying a bunch of data from the system - perhaps a listing of sales, users, or whatever domain specific entities are useful to be displayed. The best way to handle this situation is for users to have an explicit refresh button to refresh the current view as well as any underlying hosted views of entities. More advanced applications (or more demanding end users?) may want data to be updated automatically as it changes. In this situation you'll need to be more careful about how you design the refresh implementation so as to not cause issues with any current data editing (using the detached model as mentioned with the save button would aid in this). Patterns Another key element to providing an easier development experience with LightSpeed in a rich client environment is the use of design patterns such as MVC/MVVM/MVP etc. The reason these higher level architectural patterns are handy is that they impose natural boundaries around certain system interactions which are often perfectly scoped to how a unit of work would operate. One of the benefits of web clients is that the natural request/response flow of the web imposes much tighter boundaries which act as clean edges of a unit of work - hence boundaries imposed by design patterns can help give a finer structure to work against with rich clients. Of course design patterns are not the be all and end all - the very fact you have a stateful environment is a big part of the challenge also and we want to ensure we help with that scenario as much as possible. Open for feedback While there is nothing inherently wrong with a long running unit of work can impose challenges when you wish to retrieve data from the database that may already be cached in the unit of work. One idea we've kicked around before was having the ability to instruct LightSpeed to bypass the cache for certain calls as this would help improve the situation however we've then returned to the focus that a unit of work should be short lived. This presents a much larger problem in rich clients than it does in web clients and so we want to ensure we get as much feedback from users who are using LightSpeed in rich client situations to ensure we're doing the best to meet their needs as well. If you have any specific suggestions that would make your development easier I would love to hear about them. I hope that helps, John-Daniel Trask |
|
|
Good Afternoon John-Daniel, "If you have a dialog where a user may be editing an object for a considerable length of time the best practice is to retrieve the latest version of the entity when you load the editing screen and close the unit of work. This way you do not run into any issues with holding a unit of work open too long. When the user finishes editing the object create a new unit of work and attach the object and save the changes." This returns me to my original post where I mentioned that we were receiving errors when trying to Delete entities while not attached to a Unit Of Work. Is this just a limitation of working with an entity collection that is not connected to a unit of work (not being able to delete entities)? And if so, will it continue to be a limitation? We have methods to help us mitigate any concurrency issues, so if we could in fact find a way to delete entities while remaining detached from a Unit of Work, it would be ideal. Obviously, if this will never be an option we will need to return to the drawing board to find a workable alternative. Thanks! |
|
|
Hi, Unfortunately you will always need a unit of work because the unit of work holds the database connection. Being able to delete an entity without one is the same as asking if you could delete an entity without a connection to the database. You should be able to create a unit of work, attach the entity to delete and the delete the entity using the same model as I'm using with the "Save button". Just to clarify - are you meaning you want to just call UoW.Remove(entity) but flush at a later date (perhaps attached to another UoW) or when you say "delete an entity" you mean UoW.Remove(entity); UoW.SaveChanges();? The latter meaning when you say "delete the entity" you also flush those changes at the same time? If that last section doesn't make sense please let me know! :-) John-Daniel Trask |
|
|
Sorry, allow me to clarify. 1) Create/load an entity within a UnitOfWork context.Using this process, we have had success in adding entities to the child collection and in making modifications to the parent entity as well as the child entities, but (upon saving) we always receive an error if we attempt to delete a child entity. Thanks |
|
|
John-Daniel, Can you confirm that I'm understanding you correctly: 1) LightSpeed expects entity changes to occur with the scope / lifetime of a UnitOfWork instance. This is particularly important where a graph of entity objects is involved. 2) For 2-tier application architecture scenarios - (think thick client that connects to a DB directly), The recommended architecture is to keep a UnitOfWork instance alive for the duration of a user's session even though this implies that a database connection remains open for the entirety of a user's session and could affect scalability of the app. 3) If we were to try to circumvent the scalability issues of #2 - by modifying the entity object graph outside of the scope of a UnitOfWork instance to preserve database server scalability, the following interaction is necessary: Hydrate the Entity Graph: (Create UnitOfWork -> retrieve entity -> detach entity -> close UnitOfWork) Modify the Entity Graph (outside of the UOW): (Change Property values, Delete objects in owned collections etc.) Update the Entity in the Database: (Create UnitOfWork -> Retrieve a copy of the entity in question by the same ID) -> manually (property by property) map the state of the modified Entity on to the freshly hydrated Entity with the same ID -> Call UnitOfWork.SaveChanges) Can you confirm that this is the expected usage of LightSpeed and that there aren't any conveniences in this (seemingly arduous process) that I have overlooked? |
|
|
Hi Tony, Apologies for the slow response on this one. 1. LightSpeed expects a unit of work to be present if you're carrying out a delete on an association. That is the key current limitation. Other activities - changing property values, adding entities, work happily. 2. If you can keep it open then ideally yes however this does have scale issues potentially. If you have those issues to contend with then creating a unit of work to fetch entities, detach them and close the unit of work. If changes are required for removing items from child collections then you'll need to attach that entity to a unit of work for the time of the change so it can pull in other entities that may now need to be deleted. Hydrate the entity graph: Correct, basically you can fetch it. You don't need to explicitly detach however you will need to ensure you aren't making use of features such as lazy loading when not attached to a unit of work. There is also the issue of removing items from child collections requiring that the unit of work being attached (so that it can propagate any removal to any dependent entities). Modify Entity Graph: You can change values, the issue is currently just with removing items from collections as LightSpeed will try to identify what other entities require removal. Update the Entity in the Database: No, you can simply use the UnitOfWork.Attach() method to attach the altered entity to the unit of work and it will be updated. The issue with removing items from a collection is certainly not an ideal situation. These work arounds are a hassle so we are looking at the issue as we think we can improve this situation and make it smarter. We're currently working on LightSpeed 3.0 so we're juggling priorities on changes that were not already planned for LightSpeed 3.0 and delivering the 3.0 features. I hope that helps, John-Daniel Trask |
|
|
FYI, I was able to work around the deletes issue by 1) deriving a custom subclass from EntityCollection<TEntity>, 2) subscribing to the EntityAdded and EntityRemoved events within the subclass and maintain an internal list of deleted items. 3) I also derived a custom subclass from Entity<TEntity> which uses the OnSaving extension point to use reflection and search for any CustomEntityCollection<> field members marked with a DependentAttribute and use the Entity's unit of work to Explicitly call Remove for the objects that were removed. When used in conjunction with the [Dependent] attribute this seems to work seamlessly when re-attaching entities who have child collections which may have had entities removed from them that should translate into deletes within the DB. |
|
|
Scratch that solution above. Because updates are optimized, the OnSaving template method is not always called for parent entities who have child entity collections when the parent is not dirty. The above solution depends on parent entities always having their OnSaving method invoked.
|
|
|
John-Daniel, Presuming that you and your team are open to taking suggestions for a solution to the non-ideal situation you refer to in your last paragraph, please consider the following, and PLEASE consider implementing it within the LS3 timeframe as I'm sure there are other customers and other potential customers who will come in contact with the same issues as we have. The major issue here is not necessarily that modifications (including deletes) must be done outside the context of a unit of work, but that the LightSpeed unit of work as currently implemented introduces scalability issues. Scalability issues then cause us to consider the many "detaching" scenarios which lead to making changes outside of a unit of work. Other ORMs have the same problem, but consider what (for example) NHibernate does to overcome this: NHibernate breaks the affinity between a unit of work and an open physical database connection by introducing the concept of releasing a connection. This is documented here: Either having a configurable "mode" as nhibernate does (preferred) or explicitly letting users of LS control when a database session is closed and reopened (suitable stop-gap) would help our team out greatly as well as some other teams that we are now coaching on the use of LightSpeed for other projects. In fact it would also allow us to reasonably consider some alternative architectures if the scalability limitations implied by this were able to be resolved. I implore your product team to consider this as soon as possible. Please count me in for whatever customer assistance (or rallying of the troops) is necessary to move on this within the LS3 timeframe. |
|