mardi 21 avril 2020

How to Prevent Access to Parent EF Unit of Work

I realize this is fairly similar, at least in title, to Nested Unit of Work in Entity Framework and Unit of Work and EF. I have given them a look and do not think the questions or their answers address our use case.

I have what already feels like somewhat smelly code that I am trying to work with/improve. The scenario is trying to do some Unit of Work implementation with a "main" top-level UOW, which has a factory method to create nested child UOW objects. So something like

public interface IUnitOfWork
{
    public IFancyEntityDataAccess { get; }

    public IUnitOfWork CreateChildUow();
}

public interface IFancyDataAccess
{
    public void DoSomeRawSqlPreprocessing();

    public FancyEntity ReadFancyEntity(int id);
}

Each UOW has its own Entity Framework DbContext to do data access operations. (I realize EF is already something of a UOW itself, but we are trying to avoid giving our business logic layer direct access to our data access layer's DbContext.) Sometimes we hit use cases where we need to use raw SQL queries against the database (safely tucked away in their own DAL methods, of course) -- e.g. a bulk insert that EF is sluggish about, or a hierarchical Oracle query that doesn't translate with Linq to SQL. Either of these raw SQL operations may modify the database without the executing DbContext being aware, so whatever cached entities may be invalidated. (Just assume for the sake of the question we cannot efficiently invalidate the cache for our domain model. Looking into ways of possibly doing that, but it is rather convoluted.) So to ensure a fresh cache, we have the UOW object create a child UOW, have it eventually do the raw SQL with its child DbContext, and then dispose the child UOW. Then we come along with the main UOW, which up to now has not (read, had better not have) done anything with the entities or DbSets affected by the child UOW's raw SQL operation. So when it loads its data from its main DbContext, it gets the fresh data post-raw-SQL. This ends up looking something like this:

using (var child = mainUow.CreateChildUow())
{
    child.FancyEntityDataAccess.DoSomeRawSqlPreprocessing();
}

// Provided this is the main UOW's first use of `DbSet<FancyEntity>`, it will get fresh results
var entity = mainUow.FancyEntityDataAccess.ReadFancyEntity(5);

What I don't like about this setup -- there is nothing preventing the main UOW from somehow using its DbSet<FancyEntity> before the child UOW does its work. If the main UOW already has FancyEntitys cached away beforehand, it defeats the whole purpose of the child UOW. This could happen by using the main UOW before the child UOW, either within the child's using block or before it.

I suppose the fundamental problem here is that we don't have context consistent from the raw SQL. I'm thinking the ideal solution would be to somehow invalidate the context after the raw SQL so that everything is consistent and just use that one context in its one UOW. Might try looking into Invalidating/Disabling Entity Framework cache for this.

But again, barring that possibility, the secondary issue here is that both the main and child UOWs exist in the same scope. I'd like it if there was some way of preventing use of the main UOW before the child -- possibly by preventing use of the parent completely in this scenario, instead using a second child UOW to do the post-SQL ReadFancyEntity. Regardless, the mere fact that CreateChildUow is called on the main UOW instance and returns another UOW instance, seems to force this confusion, allowing the parent to be used out of turn.

So I guess my question is, is there any way I could work with this kind of a child UOW setup and not have access to the main UOW that generated it? Thinking it might be something like getting rid of the main UOW entirely and just having a CreateUow factory method, but I'm not quite sure I want to pull that trigger just yet. Having the main UOW is helpful in the main use case that doesn't require this sort of special raw SQL operation. Just interested if anyone has already encountered/solved this.

Aucun commentaire:

Enregistrer un commentaire