Using a generic Update method to reduce repeated code
The problem
I have an application that is using Entity Framework to connect to the underlying SQL database. A number of these tables allow items to be updated, but not all of them. Each of the tables that can be updated have the following properties
- dbo.Delivery
- Columns
- Id (PK, int, not null)
- Project_Id (FK, int, not null)
- Project_Date (FK, datetime, not null)
- …
Updated_By_Name (nvarchar(100), null)- Updated_By_Date (datetime, null)
- RowVer (timestamp, not null)
- Columns
How am I to create a generic class that will allow me to update the tables that have these properties – but not other tables in the database? And without breaking the DRY principle by creating duplicate repetitive code in each of the repositories?
The approach I have taken is to declare a new interface IUpdateable
, that all the updateable tables have applied to them, and defines the fields that they must have
namespace Application.Data.Interfaces { public interface IUpdateable { string Updated_By_Name { get; set; } DateTime? Updated_By_Date { get; set; } } }
As the model is created and updated using the edmx save operation, I can’t rely on any changes I make to the classes themselves being maintained. An approach would be to update the tt file to detect if the properties listed above exist in the table, but this is slightly out of scope for the moment.
Instead I have created partial classes that extend the automatically generated ones, for example
namespace Application.Data { public partial class Delivery : IUpdateable { } }
This means that the Delivery
class implements the IUpdateable
interface.
To perform the update itself I can then declare in my Repository an object of the UpdateableRepository
that will handle objects of the type Delivery
, and call the UpdateAll
method upon it, for example
namespace Application.Data.Repositories { public class DeliveryRepository : BaseRepository<Delivery_Schedule>, IDeliveryRepository { private readonly IUpdateableRepository<Delivery> _updateableRepository; public DeliveryScheduleRepository() { _updateableRepository = new UpdateableRepository<Delivery>(); } public bool UpdateAll(List<Delivery> modelList) { return _updateableRepository.UpdateAll( modelList); } } }
Finally, I can create the generic class UpdateableRepository
that will contain my common UpdateAll
method
namespace Application.Data.Repositories { public class UpdateableRepository<T> where T : class, IUpdateable { public bool UpdateAll(List<T> modelList) { using (PIMMSEntities db = new PIMMSEntities()) { foreach (T model in newItemsList) { model.Updated_By_Date = DateTime.Now; model.Updated_By_Name = HttpContext.Current.User.Identity.Name; db.Set<T>().Attach(model); db.Entry(model).State = EntityState.Modified; } db.SaveChanges(); } return true; } } }
In this code the line
where T : class, IUpdateable
mandates that the item of type T
must be a class, and it must implement IUpdateable
.
Due to this restriction we are then allowed to use the common properties of IUpdateable Updated_By_Date
and Updated_By_Name
within the generic method as all classes that attempt to call this code must have these properties.
Now this might not look like it’s worth the effort at the moment, but if I want to make changes to this UpdateAll
method I now only have to do it in one place. For example in my actual application the UpdateAll
method first performs a concurrency check against an existing list of items using the RowVer
column described above (that is also added to the IUpdateable
interface), adds new items if they don’t already exist, and a clearing of a cache if it exists. All of this would have to be repeated multiple times without using this approach.
A similar approach can be taken to adding deletion features to specific tables, rather than making this entirely generic and able to be used by repositories that should not allow data to be deleted from them.
In the actual application I have also extended this approach to assist with Unit testing by ensuring that the UpdateableRepository
implements its own interface IUpdateableRepository<T>
which declares the UpdateAll
method as mandatory.
Leave a Reply