Sometimes you need to switch on behaviour in your code for short period that you want to ensure gets switched back off again afterwards. I had this recently with an app we were performance tuning.
We found a hotspot with our EF code where we were adding a batch of entities to a collection. We were adding a few dozen new entities, with some new nested entities of their own, and it was running slowly, compared to the rest of the stack.
There are two flags you can switch off in DbContext.Configuration in this scenario – AutoDetectChangesEnabled and ValidateOnSaveEnabled, which gave us a good performance boost and got rid of the hotspot. I added methods to our repository to let us switch those flags on and off, named to make it clear why we were using them:
public interface IRepository
{
void EnableFastInserts();
void DiableFastInserts();
}
public class Repository : IRepository
{
protected DbContext Context { get; set; }
public void EnableFastInserts()
{
Context.Configuration.AutoDetectChangesEnabled = false;
Context.Configuration.ValidateOnSaveEnabled = false;
}
public void DisableFastInserts()
{
Context.Configuration.AutoDetectChangesEnabled = true;
Context.Configuration.ValidateOnSaveEnabled = true;
}
}
For the duration of the code, it’s fine to have those settings disabled – we’re only adding new objects, so we don’t need to track changes to existing object state, and we’re not saving changes at that point so we don’t need to enable validation.
But it’s dangerous behaviour, and we certainly want the settings enabled again after we’ve finished building up our collection, and before we save changes. The implementation on IRepository works fine, but it doesn’t tell you the switch is dangerous and should only be used for a limited scope.
There’s nothing to stop someone enabling the fast insert switch and leaving it on, thinking it’s a permanent performance boost. Or switching the switch in a try block but forgetting to switch it off in a finally, so you’d only see strange behaviour in some circumstances.
It’s better to make it obvious that the switch belongs in a fixed scope, by isolating it in a scope class which implements IDisposable:
public class FastInsertScope : IDisposable
{
private readonly ISupportFastInserts _supporter;
public FastInsertScope(IRepository repository)
{
_supporter = repository as ISupportFastInserts;
if (_supporter != null)
{
_supporter.EnableFastInserts();
}
}
public void Dispose()
{
if (_supporter != null)
{
_supporter.DisableFastInserts();
}
}
}
Then you can make the switch behaviour internal to the infrastructure assembly where your repository is, so I’ve split it out into an secondary ISupportFastInserts interface:
public interface IRepository
{
}
internal interface ISupportFastInserts
{
void EnableFastInserts();
void DisableFastInserts();
}
public class Repository : IRepository, ISupportFastInserts
{
protected DbContext Context { get; set; }
void ISupportFastInserts.EnableFastInserts()
{
Context.Configuration.AutoDetectChangesEnabled = false;
Context.Configuration.ValidateOnSaveEnabled = false;
}
void ISupportFastInserts.DisableFastInserts()
{
Context.Configuration.AutoDetectChangesEnabled = true;
Context.Configuration.ValidateOnSaveEnabled = true;
}
}
The FastInsertScope is public, so that’s the only way you can enable the switch, and then it’s clear in your code what you’re doing:
using (new FastInsertScope(repository))
{
//dangerous
}
//not dangerous
The switch will get turned off whenever the object goes out of scope, whether there’s an exception or not, and it’s obvious from the usage that the behaviour should be limited to blocks where it’s explicitly needed. Making the actual implementation internal means you can’t turn the switch on or off explicitly, you have to go through the scope, so you can’t explicitly enable the behaviour and then neglect to disable it.
Not the intended purpose of IDisposable, but a very useful pattern.