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
|
Hello, Consider a registration entity with and email address and a status property. The business rules imply that when a user registers a second time with the same email address, that the status of the first registration be set to "expired". In order to implement this, I override the OnSaving method of the Registration entry and it looked as follows: p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.5px Helvetica; color: #2c2cf6} p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.5px Helvetica} span.s1 {color: #000000} span.s2 {color: #29a2ba} span.s3 {color: #2c2cf6} partial class Registration { protected override void OnSaving(CancelEventArgs e) { this.OnSaving();
if (Mindscape.LightSpeed.EntityState.New == this.EntityState) { var uow = (RegistrationUnitOfWork) UnitOfWork; var registrations = uow.Registrations.Where(r => r.EmailAddress == this.EmailAddress); foreach (var registration in registrations) { if (registration == this) { continue;
} registration.Status = RegistrationStatus.Expired;
} } base.OnSaving(e);
} } With this code, existing registrations in the database is not updated. If I call SaveChanges, a stack overflow occurs. I then looked to override the SaveChanges method of my UnitOfWork subclass, but I can't find the registrations that are new. Where is the appropiate place to implement this business logic? I'd prefer to implement it as part of the Registration class, or a seperate class all together if needed. What I don't like about EF is that all this sort of logic lives in the SaveChanges method of the ObjectContext or DbContext. That results in a lot of code and its rather difficult to test. Much appreciated. Werner
|
|
|
The stack overflow occurs because the first line of your OnSaving method is: this.OnSaving(e); This is an unconditional recursion and will never bottom out. Remove this call. Note that you may still hit problems using OnSaving, because LightSpeed has already started building up the list of entities to save. Entities which become dirty once this process has begun may or may not be picked up in this save. You can work around this by doing a second SaveChanges (which will pick up all the entities dirtied during the first SaveChanges) under the aegis of a transaction. An alternative approach is to do this in the Email property setter -- load all Registration entities with the Email address being set, and expire them there and then. The expiry will not be persisted to the database until the new registration, so this should be safe. To make this work, set the Email property's Generation option to FieldOnly, and implement the Email property wrapper in a partial class. To find the registrations which are new in a UnitOfWork.SaveChanges override, use this.OfType<Registration>().Where(e => e.EntityState == EntityState.New). UnitOfWork is IEnumerable<Entity>. |
|
|
Hello, Thanks for the suggestions. That first call this.OnSaving() is actually a call to an extension method. No argument is being passed into it. Its not responsible for the stack overflow. When I call the SaveChanges on the UnitOfWork instance after the foreach, then it blows up. One concern I do have implementing this logic as part of the EmailAddress property is that it has side effects during testing and using the entity as part of a WCF service. It looks like I'm hitting the database even when the new registration is invalid. Database resources are literally expensive, i.e. licensing, CPU, memory and storage. I have a a lot of business logic that lives in the SaveChanges method of ObjectContext and DbContext (written in Entity Framework). That code has side effects due to the way EF works. I'm hoping LS4 can provide me with a simpler, less error prone and better encapsulated method for executing business logic when records are created, modified, deleted and queried. Here is the sort of things I need to do:
It will be benefitical if LS can provide the means to run this type of logic before actually doing its saving logic. I can certainly improve code maintainability if I can implement this in an attribute (similiar to a validation attribute) on an entity or property. For example, I can implement an attribute called VersionableAttribute (that inherits from an attribute type you supply) with a method Saving which LS can call. It can then perform any business logic to support versioning of the entity when its modified. Rather than overriding methods I can simply add the custom attribute to the entity using the designer. Like with validation, this reduces code bloat and may have no side effects. Just an idea. Thanks for the quick responses and fixes. You guys know how to impress someone. Werner
|
|
|
Whoops, you're right -- didn't spot the different signature for OnSaving. I'm not sure where the stack overflow is coming from then. You mentioned calling SaveChanges() after the foreach, which would certainly cause a stack overflow (because the entity is still dirty at this point, so its OnSaving would be re-entered), but I don't see such a call in your code. Could you maybe post the cyclical part of the stack trace? Regarding your requirements, as you're probably aware you can use Track Create Time, Track Update Time and Soft Delete to transparently keep most of that info (not when entities were queried though). Transparent multi-tenancy is something we'd like to do one day, but don't currently have the resources for. Immutable versioned records are another interesting idea (they also tie in with certain regulatory environments) but also unlikely to happen any time soon. The idea of providing extension hooks for the save process so that you could implement "expire and append" or versioning as a cross-cutting concern is a very intriguing one but I'd have to look at how feasible it might be. As a user of this hypothetical feature, can you post an example of the kind of code you'd like to be able to write in an attribute or policy class in order to meet your requirements? That would help us to think about how such a feature might work and be implemented. |
|
|
Overriding SaveChanges on the UnitOfWork does not work. Once one record is modified, the line of **error** fails stating that the collection was modified. So the only place to do this logic seems to be the property. I'm not a big fan of this due to the side effects it has. The other alternative would be to implement it outside of the UnitOfWork and Entity, like the MVC Controller, but it makes it less reusable. p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.5px Helvetica} span.s1 {color: #2c2cf6} span.s2 {color: #29a2ba} public override void SaveChanges(bool reset) { var registrations = this.OfType<Registration>().Where(e => e.EntityState == EntityState.New); foreach (var registration in registrations) /* **error** */ { var registrations1 = this.Registrations.Where(registration1 => registration1.EmailAddress == registration.EmailAddress); foreach (var registration2 in registrations1) { if (registration2.EntityState == EntityState.New) { continue;
} registration2.Status = RegistrationStatus.Duplicate;
} } base.SaveChanges(reset);
} Thanks, Werner
|
|
|
Pop a ToList() on the end of the first line. That will effectively take a snapshot of the filtered set. Subsequently adding entities to the unit of work will not affect the snapshot, so this should avoid the 'collection was modified' exception. |
|
|
Here is what I have done. Its rather simple. Consider the following attributes: public abstract class EntityAttribute : Attribute And the following extension method: public static class EntityExtensions Now I have a string property that I'd like to be "random" when a record is created. So, I implement the following RandomStringAttribute.
public class RandomStringAttribute : PropertyAttribute At first I called the extension method in the overriden SaveChanges method of the entity. However, you showed me how to get access to the new, modified and deleted entities in SaveChanges method in the UnitOfWork, so I can now call it for all entities being effected. Many business rules needs to deal with the actual creation, deletion, modification and querying of data, so one can add addional methods to the attribute to handle those. The key to overriding queries is to intercept them, pass them to the attribute's method which adds additional filters and then pass it to the UnitOfWork's respective method. I have used this techniques on IQueryable (aka DbSet and ObjectSet) in EF. It seems possible to do this by overriding each method in a UnitOfWork and call respective methods to the respective attributes. Its very similiar to the way Validation is done these days. Its taken about two hours to implement on my side and I can imagine that you can do this incrementally. Hope it helps. Werner
|
|
|
The ToList trick worked rather well. Many thanks. |
|
|
Using the scheme described earlier, I migrated the code to update registrations during SaveChanges to an attribute and reused it on several entities. It relies heavily on reflection and conventions to work, but it keeps code clean and seems to reduce code bloat. It only took a couple of hours to implement. Using the mechanism to intercept certain events, like property changes, before and after the entity is being loaded, entity state changes, validation events is more difficult. So are modifying Queries transparently on the fly. I think this is where LS support for this technique can really simplify the solution even more. Thanks for all the help. Werner |
|