mehdime / DbContextScope

A simple and flexible way to manage your Entity Framework DbContext instances
http://mehdi.me/ambient-dbcontext-in-ef6/
MIT License
634 stars 271 forks source link

Suggestion on how to deal with scopes in Application layer that does not depend on Infrastructure layer #3

Open patroza opened 10 years ago

patroza commented 10 years ago

In my setup I have the Domain layer, the Application Layer on top of it, and then a layer with Infrastructure, and Presentation on top of that. Generally known as the Onion architecture.

I process my use cases in the form of Commands or Queries in the Application Layer, receiving them from the Presentation Layer. In the C/Q handlers i'm pulling in infrastructure services to materialize my entities. The domain layer is then called to execute the use cases, perhaps with the assistance of domain services and perhaps other types of infrastructure services. Possibly queueing up events to be processed before or after initiating the DB transaction (e.g sending notifications by either email or SignalR, or kicking up background work through ServiceBus queues).

I do not use repositories, and thus I grab DbContexts based on interfaces into my application layer. The problem being that DbContextScope relies on DbContext implementations instead, which I do not have access to.

I came up with the following workaround for my problem:

    public static class DbContextScopeExtensions
    {
        // These are set from the composition root
        public static Func<IAmbientDbContextLocator, ISqlContext> GetSqlContextFromLocator;  // locator.Get<W6Context>();
        public static Func<IDbContextCollection, ISqlContext> GetSqlContextFromCollection; // collection.Get<W6Context>();

        public static ISqlContext GetSqlContext(this IAmbientDbContextLocator locator) {
            return GetSqlContextFromLocator(locator);
        }

        public static ISqlContext GetSqlContext(this IDbContextCollection collection) {
            return GetSqlContextFromCollection(collection);
        }
    }

as alternative, in Generic form:

    public static class DbContextScopeExtensions
    {
        // These are set from the composition root
        // if (type == typeof (ISqlContext)) return locator.Get<W6Context>();
        public static Func<IAmbientDbContextLocator, Type, IDbContext> GetDbContextFromLocator;

        // if (type == typeof (ISqlContext)) return collection.Get<W6Context>();
        public static Func<IDbContextCollection, Type, IDbContext> GetDbContextFromCollection;

        public static T GetByInterface<T>(this IAmbientDbContextLocator locator) where T : class, IDbContext {
            return GetDbContextFromLocator(locator, typeof (T)) as T;
        }

        public static T GetByInterface<T>(this IDbContextCollection collection) where T : class, IDbContext {
            return GetDbContextFromCollection(collection, typeof (T)) as T;
        }
    }

My main use case for DbContextScope in this context is to convert much of my request scoped handlers and dependent services, to singletons, as we no longer need to import a DbContext into the constructors, which made them essentially stateful.

Would there be a place for this setup in the DbContextScope project, and would you recommend a similar or different approach?

christianpeters78 commented 9 years ago

Hi,

how do you plan to use an Extension-method with an IDbContextCollection_interface which by itself references System.Data.Entity?

patroza commented 9 years ago

Hi,

My main problem was not having a reference to EntityFramework, but having a reference from my Application layer into my Infrastructure layer.

So my application layer has interfaces for Infrastructure. But Infrastructure layer implements these interfaces. Application layer does not have access to the implementations in Infrastructure, instead the Presentation/DI layer wires them up. so my workaround makes it possible to deal with the interfaces instead of the implementation.

In essence it's the Onion architecture: http://chicagoalt.net/event/july-2011-meeting-onion-architecture-with-asp-net-mvc

So in short I do not mind having a reference to a 3rd party library to make an extension method work. as in general we need to do that anyway to access all the IQueryable magic residing in there. (while its not 'pure', it is acceptable to me) However I do have problems with changing my app's layered architecture.

christianpeters78 commented 9 years ago

Hi,

understood. So this is not quiet what I thougt. My "problem" is, that the business-layer depends on the EntityFramework and I don't see how to resolve this issue without rewiting the DbContextCollection.

But that's not for here. ;o)

patroza commented 9 years ago

I see, well personally I do not believe the business layer should access EF, either directly or indirectly through either concrete nor abstract repositories. But that is another type of discussion :)

Also how far did you abstract away EF in the business layer, because if you access the Db from business layer, wouldn't you always have to have access due to the EF extensions, IQueryable provider etc?

maldworth commented 9 years ago

Hi Sickboy,

The situation you describe in your first and third posts here are similar to mine but I'm a bit curious as to your implementation.

Basically I'm wondering what your ISqlContext (and/or IDbContext) look like.

Here's what I was thinking. I simplified and left out some things, but enough there for you to understand what I'm trying to get at. Questions below the example.

// ######## 
// Domain
// ########

public interface IDbContext
{
}

public interface ISqlContext : IDbContext
{
    public DbSet<Employee>  Employees   { get; }
    public DbSet<Manager>   Managers    { get; }
    public DbSet<Staff>     Staff       { get; }
}

public interface IMySqlContext : IDbContext
{
    // another db context if we want
}

// ########
// Application Layer
// ########

SomeQueryHandler : IQueryHandler<TInput, TOut>
{
    private IDbContextScopeFactory  _dbContextScopeFactory  { get; set; }   // DI through constructor or property.
    // Your setup code
    // ...

    public TOut Execute(TInput)
    {
        using(var dbContextScope = _dbContextScopeFactory.CreateReadOnly())
        {
            ISqlContext dbCtx = dbContextScope.DbContexts.GetByInterface<ISqlContext>();

            // Do our DB query, with full access to the DbSet magic that is offered through IQueryable
            // I can't think of another way to do it, because IDbScopeCollection must return a type that inherits DbContext
            // and your extension method must return an IDbContext
        }
    }
}

SomeCommandHandler : ICommandHandler<TCommand>
{
    private IDbContextScopeFactory  _dbContextScopeFactory  { get; set; }   // DI through constructor or property.
    // Your setup code
    // ...

    public void Execute(TCommand)
    {
        using(var dbContextScope = _dbContextScopeFactory.Create())
        {
            ISqlContext dbCtx = dbContextScope.DbContexts.GetByInterface<ISqlContext>();

            // Again do Db Calls on the DbSets, but how would I call any methods that are part of the DbContext?

            dbContextScope.SaveChanges();
        }
    }
}

// ########
// Infrastructure Layer
// ########

public MyContext : DbContext, ISqlContext
{
    public MyContext : base("myconnectionstring")
    {
    }

    public DbSet<Employee>  Employees   { get; set; }
    public DbSet<Manager>   Managers    { get; set; }
    public DbSet<Staff>     Staff       { get; set; }
}

// ########
// Composition Root, assign our static funcs
// ########

DbContextScopeExtensions.GetDbContextFromCollection = (collection,type) =>
{
    if(type == typeof(ISqlContext)) return collection.Get<MyContext>();

    //return a default context? not quite sure what to do here.
}

So I don't really like having to define the DbSet in the ISqlContext. Also, because the extension method returns the interface, which only knows about the DbSets. Then I don't have access to any methods from the DbContext.

So I thought maybe the purpose of IDbContext was to have a property to the context.

// ########
// Domain
// ########

public interface IDbContext
{
    public DbContext DbCtx  { get; }
}

public interface ISqlContext : IDbContext
{
}

// ########
// Application
// ########

// Making appropriate changes, I also access the DbSets using the DbContext.Set<TEntity>()

// ########
// Then in your Infrastructure
// ########

public MyContext : DbContext, IDbContext
{
    public MyContext : base("myconnectionstring")
    {
    }

    public DbContext DbCtx  {
        get {
            return this;
        }
    }

    // Assign DbSets here instead of in the ISqlContext
    public DbSet<Employee>  Employees   { get; set; }
    public DbSet<Manager>   Managers    { get; set; }
    public DbSet<Staff>     Staff       { get; set; }
}

What do you think about this better, as the application layer cannot see the DbContext implementation, so we are okay there, and yet we have access to the DbContext Methods, and can still call the DbSets as well.