I am using the repository pattern within EF using an Update
function I found online
public class Repository<T> : IRepository<T> where T : class
{
public virtual void Update(T entity)
{
var entry = this.context.Entry(entity);
this.dbset.Attach(entity);
entry.State = System.Data.Entity.EntityState.Modified;
}
}
I then use it within a DeviceService
like so:
public void UpdateDevice(Device device)
{
this.serviceCollection.Update(device);
this.uow.Save();
}
I have realise that what this actually does it update ALL of the device's information rather than just update the property that changed. This means in a multi threaded environment changes can be lost.
After testing I realised I could just change the Device
then call uow.Save()
which both saved the data and didnt overwrite any existing changes.
So my question really is - What is the point in the Update()
function? It appears in almost every Repository pattern I find online yet it seems destructive.
After Slauma's very profound and practical answer I'd like to zoom in on some basic principles.
In this MSDN article there is one important sentence
Simple question. What has the business logic to do with
Update
?Fowler defines a repository pattern as
So as far as the business logic is concerned a repository is just a collection. Collection semantics are about adding and removing objects, or checking whether an object exists. The main operations are
Add
,Remove
, andContains
. Check out theICollection<T>
interface: noUpdate
method there.It's not the business logic's concern whether objects should be marked as 'modified'. It just modifies objects and relies on other layers to detect and persist changes. Exposing an
Update
methodif
constructs will creep in to check whether values have changes or not.UPDATE
.I wouldn't call this generic
Update
method generally "destructive" but I agree that it has limited use cases that are rarely discussed in those repository implementations. If the method is useful or not depends on the scenario where you want to apply it.In an "attached scenario" (Windows Forms application for instance) where you load entities from the database, change some properties while they are still attached to the EF context and then save the changes the method is useless because the context will track all changes anyway and know at the end which columns have to be updated or not. You don't need an Update method at all in this scenario (hint:
DbSet<T>
(which is a generic repository) does not have anUpdate
method for this reason). And in a concurrency situation it is destructive, yes.However, it is not clear that a "change tracked update" isn't sometimes destructive either. If two users change the same property to different values the change tracked update for both users would save the new column value and the last one wins. If this is OK or not depends on the application and how secure it wants changes to be done. If the application disallows to ever edit an object that is not the last version in the database before the change is saved it cannot allow that the last save wins. It would have to stop, force the user to reload the latest version and take a look at the last values before he enters his changes. To handle this situation concurrency tokens are necessary that would detect that someone else changed the record in the meantime. But those concurrency checks work the same way with change tracked updates or when setting the entity state to
Modified
. The destructive potential of both methods is stopped by concurrency exceptions. However, setting the state toModified
still produces unnecessary overhead in that it writes unchanged column values to the database.In a "detached scenario" (Web application for example) the change tracked update is not available. If you don't want to set the whole entity to
Modified
you have to load the latest version from the database (in a new context), copy the properties that came from the UI and save the changes again. However, this doesn't prevent that changes another user has done in the meantime get overwritten, even if they are changes on different properties. Imagine two users load the same customer entity into a web form at the same time. User 1 edits the customer name and saves. User 2 edits the customer's bank account number and saves a few seconds later. If the entity gets loaded into the new context to perform the update for User 2 EF would just see that the customer name in the database (that already includes the change of User 1) is different from the customer name that User 2 sent back (which is still the old customer name). If you copy the customer name value the property will be marked as Modified and the old name will be written to the database and overwrite the change of User 1. This update would be just as destructive as setting the whole entity state to Modified. In order to avoid this problem you would have to either implement some custom change tracking on client side that recognizes if User 2 changed the customer name and if not it just doesn't copy the value to the loaded entity. Or you would have to work with concurrency tokens again.You didn't mention the biggest limitation of this
Update
method in your question - namely that it doesn't update any related entities. For example, if yourDevice
entity had a relatedParts
collection and you would edit this collection in a detached UI (add/remove/modify items) setting the state of the parentDevice
toModified
won't save any of those changes to the database. It will only affect the scalar (and complex) properties of the parentDevice
itself. At the time when I used repos of this kind I named the update methodFlatUpdate
to indicate that limitation better in the method name. I've never seen a generic "DeepUpdate
". Dealing with complex object graphs is always a non-generic thing that has to be written individually per entity type and depending on the situation. (Fortunately a library like GraphDiff can limit the amount of code that has to be written for such graph updates.)To cut a long story short:
Update
method is redundant as EFs automatic change tracking does all the necessary work to write correct UPDATE statements to the database - including changes in related object graphs.Update
method and requires significantly more (non-generic) work.