I have a repository like this
public abstract class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected DbContext _dbContext;
public BaseRepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public TEntity GetByKey(object keyValue)
{
// todo
}
}
and a concrete repository like this
public CustomerRepository : BaseRepository<Customer> , ICustomerRepository
{
public CustomerRepository(DbContext context) : base (context) { }
public Customer FindCustomerByKey(string key)
{
_dbContext.Set<Customer>().Find(key);
}
}
I have wcf service like this
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class CustomerSatisfactionService : ICustomerSatisfactionService
{
private ICustomerRepository _customerRepository;
private IHelpDeskRepository _helpdeskRepository;
public AccountService(ICustomerRepository customerRepository,IHelpdeskRepository helpdeskRepository)
{
_customerRepository = customerRepository;
_helpdeskRepository = helpdeskRepository;
}
public void DoSomethingUsingBothRepositories()
{
// start unit of work
// _customerRepository.DoSomething();
// _helpdeskRepository.DoSomething();
// commit unit of work
}
}
and I am using StructureMap for injecting DbContext instances like this
For<DbContext>().Use(() => new MyApplicationContext());
My problem is when a client calls the service, a new CustomerSatisfactionService instance is created, hence new instances of CustomerRepository and HelpdeskRepository are created but with different DbContexts.
I want to implement the unit of work pattern, but in the DoSomethingWithBothRepositories method, the two repositories have different DbContexts.
Is there any way to tell structure map to spin up a DbContext instance on a per call basis?
You must specify lifecycle for your DbContext so that only one instance is created per call. StructureMap doesn't contain build-in lifecycle management for per call WCF but you can find one implementation on this blog.
You need to implement UnitOfWork pattern so that same context is shared amongst entities. Take a look at http://blogs.msdn.com/b/adonet/archive/2009/06/16/using-repository-and-unit-of-work-patterns-with-entity-framework-4-0.aspx for a way to implement it.
I don't know if you need/want StructureMap to control the instantiation of dbcontext, have a look at this reply, UoW and UoW Factory setup dbcontext for the calls that need to be made in the repository.
EF ObjectContext, Service and Repository - Managing context lifetime.
Related
I have the StudentDbContext
public class StudentDbContext : DbContext
{
public StudentDbContext()
{
}
public StudentDbContext(DbContextOptions<StudentDbContext> options)
: base(options)
{
}
public virtual DbSet<Students> Students{ get; set; }
}
and then I have a repository and I try to understand what is the difference if I inject the StudentDbContext vs inject DbContextOptions
Inject the DbContextOptions
class StudentRepository : IStudentRepository
{
private readonly DbContextOptions<StudentDbContext> _context;
public StudentRepository(DbContextOptions<StudentDbContext> context)
{
_context = context;
}
}
Inject StudentDbContext
class StudentRepository : IStudentRepository
{
private readonly StudentDbContext _context;
public StudentRepository(StudentDbContext context)
{
_context = context;
}
}
Are there any advantages or disadvantages in each case?
DbContextOptions class is used to create the options to be used by a DbContext. It configures the database (and other options) to be used for the database context. DbContext class contains DbSet properties for each entity in the model.
If you try to use DbContextOptions in a repository you will have no access to any model since it doesn't have them.
DbContextOptions and DbContextOptions<TContext> have different use cases.
You need to inject DbContextOptions (generic or not) in subtypes of DbContext.
The DbContextOptions instance will contain the options that you have configured in the Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ConcreateDbContext>(
options => options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test")
);
}
If your DbContext implementation is not supposed to be inherited from, you will inject in your ConcreateDbContext the generic version of options type DbContextOptions<ConcreateDbContext>, and this ensures that the correct options for the specific DbContext subtype are resolved from dependency injection. You can also, mark your ConcreateDbContext as sealed, as the class is not designed to be inherited from.
public sealed class ConcreateDbContext: DbContext
{
public ConcreateDbContext(DbContextOptions<ConcreateDbContext> contextOptions)
: base(contextOptions)
{
}
}
In case that you want to have a DbContext that is intended to be inherited from, you are supposed to expose a protected constructor which will take as a parameter the non-generic version of DbContextOptions.
public class BaseDbContext: DbContext
{
protected BaseDbContext(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
In case you want to have a DbContext that is intended to be both instantiated and inherited from, you will have two constructors, one taking the generic, and one the non-generic version of DbContextOptions.
And when you want to interact with the data in your database, you will inject the concrete DbContext implementation (in your case StudentDbContext).
I have a controller with the following content (simplified version):
[HttpPost]
public Task<OkResult> Post([FromBody] commonRequest)
{
parser.DoWork(commonRequest);
return Ok();
}
The commonRequest object is populated from the incoming JSON request.
The parser.DoWork method should invoke the creation of a new instance of the class, depending on requestBody.
Here's what it looks like:
public class CommonParser : ICommonParser
{
private readonly ILogger<CommonParser> logger;
private IServiceProvider serviceProvider;
public CommonParser(ILogger<CommonParser> _logger, IServiceProvider _serviceProvider)
{
this.logger = _logger;
this.serviceProvider = _serviceProvider;
}
public void DoWork(CommonRequest commonRequest)
{
ICommonParser parser = (ICommonParser)Activator.CreateInstance(Type.GetType(commonRequest.instance)
, serviceProvider);
parser.DoWork(commonRequest);
}
}
I have three classes whose names are passed through commonRequest.instance. All of these classes implement the ICommonParser interface. Inside these classes, I pass a serviceProvider so that they can get the ILogger inside themselves and use it.
Here is an example constructor of this class:
private readonly ILogger<Parser1> logger;
public Parser1(IServiceProvider serviceProvider)
{
this.logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<Parser1>();
}
As a result, I can send only one message in this way. On the second call, I get a message that serviceProvider.GetRequiredServiceILoggerFactory () has been destroyed.
Please tell me what to do in such cases. I think I'm designing wrong.
From Dependency Injection in ASP.NET Core:
Avoid using the service locator pattern. For example, don't invoke
GetService or GetRequiredService to obtain a service instance when you
can use DI instead.
1) register the logger factory or the logger service, in case of the logger factory
services.AddSingleton<ILoggerFactory, LoggerFactory>();
2) use constructor injection to inject logger factory into the constructor
public Parser1(ILoggerFactory loggerFactory)
{
}
3) you might create a new interface for the parsers (parser1, 2, 3). The parsers implement this interface. Register them as services
public interface IParser
{
void DoWork(CommonRequest commonRequest);
}
services.AddTransient<Parser1>(); // implements IParser
services.AddTransient<Parser2>();
This post gives an answer how to resolve classes implementing the same interface. For getting parser with DI you will actually need IServiceProvider:
_serviceProvider.GetService<Parser1>();
I have my EntityFrameworkCore DBContext in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(Options => ... );
...
}
I also have my data access layer factory class which receives DBContext in constructor
public partial class DataAccessFactory
{
public readonly ProductsDataAccess Products;
public readonly CategoriesDataAccess Categories;
public DataAccessFactory(MyDBContext db)
{
Products = new ProductsDataAccess(db);
Categories = new CategoriesDataAccess(db);
}
}
In order to work with Data Access Layer, I have to create new instance of DataAccessFactory per each request.
My question is, does it make sence and is there any way to create one instance of DataAccessFactory and add it as a Singleton?
You can register your service inside ConfigureServices in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<DataAccessFactory>();
}
and inject it where you needed:
public class MyController
{
public readonly DataAccessFactory Factory;
public MyController(DataAccessFactory factory)
{
Factory = factory;
}
}
It will be created only once per application life. If you instead will want to change it scope for example per request, just change AddSingleton to AddScoped.
Update:
But be careful when mixing different-scoped services. You cannot inject short living object into long living, because it will cause exceptions. In your situation you will need to change DbContext scope to singleton (sic) or consider to change Factory lifetime to Scoped. Here is example how to change DbContext scope.
I want to get one context per request in ASP NET 5/EF 7 app for use it in some methods (not in controller).
Unfortunately I did not find the answer in the documentation
ASP.NET vNext template and examples aspnet/MusicStore
You may use some methods for achieving this purpose.
Using .AddDbContext<ApplicationDbContext>(); method for registering ApplicationDbContext in Dependency Injection system (in ConfigureServices() method), leads to the fact that it registered as Scoped dependence(or in another words "per request"). Thereby you only need get it from Dependency Injection system.
Add your dbContext as parameter of constructor method your class (in which you will use dbContext). Then you have to get this class using Dependency Injection system, e.g added it as parameter of controller's constructor.
public class HabitsController : Controller
{
public HabitsController(HabitService habitService)
{
}
}
public class HabitService
{
private GetHabitsContext _dbContext;
public HabitService(GetHabitsContext dbContext)
{
_dbContext = dbContext;
}
}
But if you don't want to use constructor injection for getting context, you can get necessary dependenses using GetService() method (but you need in ServiceProvider instance for that, in example below, i'am getting it through constructor injection too).
using Microsoft.Framework.DependencyInjection; // for beta 6 and below
using Microsoft.Extensions.DependencyInjection; // for beta 7 and above
public class HabitService
{
private IServiceProvider _serviceProvider;
public HabitService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public GetHabit()
{
var dbcontext = _serviceProvider.GetService<ApplicationDbContext>();
}
}
In first method, we can get HabitService through GetService() method too (not through the constructor injection).
using Microsoft.Framework.DependencyInjection; // for beta 6 and below
using Microsoft.Extensions.DependencyInjection; // for beta 7 and above
public class HabitsController : Controller
{
public HabitsController(IServiceProvider serviceProvider)
{
var habitService= serviceProvider.GetService<HabitService>();
}
}
public class HabitService
{
private GetHabitsContext _dbContext;
public HabitService(GetHabitsContext dbContext)
{
_dbContext = dbContext;
}
}
Thanks Tseng for remark:
I should be noted, that it's a pretty bad practice to inject the container into your objects. The container should only be referenced from the composition root and certain type of factories (which are implemented on application level, and not in the domain/business layer)
dbContext in HabitsController and _dbContext in HabitService are different contexts!
I checked, this is the same context.
I keep on getting an error when trying to resolve my repositories.
None of the constructors found with 'Public binding flags' on type can be invoked with the available services and parameters:
Cannot resolve parameter 'Repository.Data.INHibernateSession nHibernateSession' of constructor 'Void .ctor(Repository.Data.INHibernateSession)'.
Global.asax
builder.RegisterAssemblyTypes(assembly).Where(t => typeof(IDependency).IsAssignableFrom(t)).
AsImplementedInterfaces().InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(assembly).Where(t => typeof(ISingletonDependency).IsAssignableFrom(t)).
AsImplementedInterfaces().SingleInstance();
builder.RegisterAssemblyTypes(assembly).Where(t => typeof(ITransientDependency).IsAssignableFrom(t)).
AsImplementedInterfaces().InstancePerDependency();
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerDependency();
then i have my singleton factory
public interface INhibernateFactory : ISingletonDependency
{
ISessionFactory SessionFactory { get; }
}
then my instance per lifetime
public interface INHibernateSession : IDependency
{
ISession Session { get; }
}
public class NHibernateSession : IDependency
{
private ISession _session;
private readonly ISessionFactory _sessionFactory;
public NHibernateSession(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
then in my Generic repository
public class Repository<T> : IRepository<T> where T : class
{
private readonly INHibernateSession _nHibernateSession;
public Repository(INHibernateSession nHibernateSession)
{
_nHibernateSession = nHibernateSession;
}
public ISession Session
{
get { return _nHibernateSession.Session; }
}
It seems all i'm doing is creating a singleton, inject that to the session, then inject into repository. (all my other dependencies work fine)
Appreciate if someone could point me in the right direction why this wont resolve, I'm quite stumped?
The errors you get speaks for them selves, they state some type that cannot be resolved. After your latest change, this is the the 'ISessionFactory' interface which is missing.
To further elaborate: the current problem is that Autofac, when asked to build NHibernateSession instances, doesn't know how to provide an ISessionFactory instance. To "learn" the container how to build session factories it is not enough to provide the NHibernateFactory instance alone. Consider this registration:
builder.RegisterType<NhibernateFactory>().As<INhibernateFactory>().SingleInstance();
builder.Register(c => c.Resolve<INhibernateFactory>().SessionFactory);
The last line there registers a lambda that knows how to provide the session factory instance.
You'll need to provide the container with every type involved. Use the "no constructors..." error to see which types are missing.
To dig further into Autofac and NHibernate you'll have to search around the net. E.g. this question discusses NHibernate sessions:
Managing NHibernate ISession with Autofac
I am not familiar with AutoFac; but does your NHibernateSession class have to implement your INHibernateSession interface to resolve correctly?
Matt