In MSDN forums I came across a post addressing an issue I have also faced. Auditing fields can cause concurrency issues in LightSwitch (not exclusively).
In general basic auditing includes keeping track of when an entity was created/modified and by whom. I say basic auditing because auditing is in general much more than this.
Anyhow, this basic auditing mechanism is very widely implemented (it’s a way for developers to be able to easily find a user to blame for their own bugs :-p), so let’s see what this can cause and why in LightSwitch.
In the aforementioned post but also in this one, I have clearly stated that IMHO the best way to handle concurrency issues is using RIA Services. If you don’t, read what follows.
Normally in any application, updating the fields that implement Audit tracking would be a task completed in the business layer (or even Data layer in some cases and this could go as deep as a database trigger). So in LightSwitch the first place one would look into to put this logic would be EntityName_Inserting and EntityName_Updating partial methods that run on the server. Which is right, but causes concurrency issues, since after saving the client instance of the entity is not updated by the changes made at the server and as soon as you try to save again this will cause concurrency error.
So, what can you do, apart from refreshing after every save which is not very appealing? Update at the client. Not appealing either but at least it can be done elegantly:
Let’s say all entities to implement auditing have 4 fields:
Then, also in the common project, add a new class called EntityExtensions:
Now let’s suppose your entity’s name is Customer (imagination was never my strong point), the screen’s name is CustomerList and the query is called Customers.
First Viewing the Customer entity in designer click write code and make sure that:
Then at your screen’s saving method write this:
This should do it. This way you can also easily move your logic to the server as the interface and extension class are defined in the Common project and they are also available to the server.
In general basic auditing includes keeping track of when an entity was created/modified and by whom. I say basic auditing because auditing is in general much more than this.
Anyhow, this basic auditing mechanism is very widely implemented (it’s a way for developers to be able to easily find a user to blame for their own bugs :-p), so let’s see what this can cause and why in LightSwitch.
In the aforementioned post but also in this one, I have clearly stated that IMHO the best way to handle concurrency issues is using RIA Services. If you don’t, read what follows.
Normally in any application, updating the fields that implement Audit tracking would be a task completed in the business layer (or even Data layer in some cases and this could go as deep as a database trigger). So in LightSwitch the first place one would look into to put this logic would be EntityName_Inserting and EntityName_Updating partial methods that run on the server. Which is right, but causes concurrency issues, since after saving the client instance of the entity is not updated by the changes made at the server and as soon as you try to save again this will cause concurrency error.
So, what can you do, apart from refreshing after every save which is not very appealing? Update at the client. Not appealing either but at least it can be done elegantly:
Let’s say all entities to implement auditing have 4 fields:
- DateCreated
- CreatedBy
- DateModified
- ModifiedBy
namespace LightSwitchApplication{ public interface IAuditable{ DateTime DateCreated { get; set; } string CreatedBy { get; set; } DateTime DateModified { get; set; } string ModifiedBy { get; set; } } }
Then, also in the common project, add a new class called EntityExtensions:
namespace LightSwitchApplication{ public static class EntityExtensions{ public static void Created<TEntityType>(this TEntityType entity, IUser user) where TEntityType: IAuditable{ entity.DateCreated = entity.DateModified = DateTime.Now; entity.CreatedBy = enity.ModifiedBy = user.Name; } public static void Modified<TEntityType>(this TEntityType entity, IUser user) where TEntityType: IAuditable{ entity.DateModified = DateTime.Now; entity.ModifiedBy = user.Name; } } }
Now let’s suppose your entity’s name is Customer (imagination was never my strong point), the screen’s name is CustomerList and the query is called Customers.
First Viewing the Customer entity in designer click write code and make sure that:
partial class Customer : IAuditable{ }
Then at your screen’s saving method write this:
partial void CustomerList_Saving{ foreach(Customer customer in this.DataworkSpace.ApplicationData.Details.GetChanges().AddedEntities.OfType<Customer>()) customer.Created(this.Application.User); foreach(Customer customer in this.DataworkSpace.ApplicationData.Details.GetChanges().ModifiedEntities.OfType<Customer>()) customer.Modified(this.Application.User); }
This should do it. This way you can also easily move your logic to the server as the interface and extension class are defined in the Common project and they are also available to the server.