Question

Given a DbContext that implements 2 interfaces like so:

public interface IQueryEntities
{
    IQueryable<User> Users { get; }
    IQueryable<Computer> Computers { get; }
    // other IQueryable<T> get properties
}

public interface IUnitOfWork
{
    int SaveChanges();
}

public class MyContext : DbContext, IQueryEntities, IUnitOfWork
{
    // implement interfaces using EF
}

First question, is it a bad idea to separate out the query aspects of DbContext (IDbSets) from the command aspects (SaveChanges)? I am exploring a refactor to the above because there are a lot of cases where we just need to query data, without saving anything.

The problem I'm running into involves unity DI, which currently injects MyDbContext using a singleton-per-http-context lifetime for the IUnitOfWork interface. I'm not sure how to go about setting up injection for the IQueryEntities interface so that it will reuse an existing DbContext instance that may have already been injected against the IUnitOfWork interface. Or vice versa. Is this even possible?

Here is the current lifetime manager that reuses previously-injected instances of IUnitOfWork in the same http context:

public class UnityHttpContextLifetimeManager : LifetimeManager
{
    private const string KeyFormat = "SingletonPerCallContext_{0}";
    private readonly string _key;

    public UnityHttpContextLifetimeManager()
    {
        _key = string.Format(KeyFormat, Guid.NewGuid());
    }

    public override object GetValue()
    {
        return HttpContext.Current.Items[_key];
    }

    public override void SetValue(object newValue)
    {
        HttpContext.Current.Items[_key] = newValue;
    }

    public override void RemoveValue()
    {
        HttpContext.Current.Items.Remove(_key);
    }
}

By the way if there is a way to do this, I would prefer to do it in unity web.config section rather than compiled c# bootstrapper.

Update

With help from onof I was able to get this working, however my config looks different from what he suggested. Am I doing something wrong? When I don't give each interface the lifetime manager, one HttpContext ends up with multiple instances of the DbContext. Only when I give all 3 the lifetime manager does it reuse the same DbContext instance across a single request for both interfaces. Is something wrong with this config?

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <namespace name="MyApp.MyNameSpace" />
    <assembly name="MyApp" />
    <alias alias="singleton-per-http-context" 
        type="MyApp.MyNameSpace.UnityHttpContextLifetimeManager, MyApp" />
    <container>
        <register type="MyContext">
            <lifetime type="singleton-per-http-context" />
        </register>
        <register type="IUnitOfWork" mapTo="MyContext">
            <lifetime type="singleton-per-http-context" />
        </register>
        <register type="IQueryEntities" mapTo="MyContext">
            <lifetime type="singleton-per-http-context" />
        </register>
        ...
    </container>
Was it helpful?

Solution

is it a bad idea to separate out the query aspects of DbContext (IDbSets) from the command aspects (SaveChanges)?

I think it's a good idea, because of Interface Segregation Principle, which states that each client should see only the interface it needs to do its work.

To register, i would do:

container.RegisterType<MyContext>(new UnityHttpContextLifetimeManager());
container.RegisterType<IQueryEntities, MyContext>();
container.RegisterType<IUnitOfWork, MyContext>();

AFAIK it's the only way to share the same instance, once the object created.

To do it at design-time (in web.config), it's straightforward:

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <namespace name="MyApp.MyNameSpace" />
    <assembly name="MyApp" />
    <container>
      <register type="MyContext" >    
         <lifetime type="UnityHttpContextLifetimeManager" />
      </register>
      <register type="IQueryEntities" mapTo="MyContext" />
      <register type="IUnitOfWork" mapTo="MyContext" />
    </container>

OTHER TIPS

You need to register one interface as singleton and the other will automatically follow.

container.RegisterType<IQueryEntities, MyContext>(new UnityHttpContextLifetimeManager());
container.RegisterType<IUnitOfWork, MyContext>();

Assuming that your LifetimeManager works correctly this would scope the lifetime of a instance of MyContext to the HttpContext and the mapping from IUnitOfWork would reuse the same instance as the target of the mapping is the same.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top