I have a derived class of DbContext, called NavigationContext, that looks like this:
public class NavigationContext : DbContext
{
private readonly IConfiguration _configuration;
public NavigationContext(DbContextOptions<NavigationContext> options, IConfiguration configuration) : base(options)
{
_configuration = configuration;
}
//DbSets here ...
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(_configuration.GetConnectionString("NavigationLoggingDatabase"));
}
}
}
The Configuration is registered to the DI container in Startup.cs, like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<NavigationContext>(options => options.UseSqlServer(Configuration.GetConnectionString("NavigationLoggingDatabase")));
services.AddSingleton(_ => Configuration);
}
My question is what do I send to the NavigationContext constructor?
public int Add(TEntity item)
{
using (NavigationContext context = new NavigationContext(_contextOptionsBuilder.Options, ???))
{
context.Set<TEntity>().Add(item);
context.SaveChanges();
return item.Id;
}
}
That's not how you do DI (Dependency Injection). Whenever you see the new keyword for a service, you have to know it's wrong.
First, you don't have to pass in anything to the DbContext, that OnConfiguring override shouldn't be there as you are not using it. This call takes care of that configuration:
services.AddDbContext<NavigationContext>(options => options.UseSqlServer(Configuration.GetConnectionString("NavigationLoggingDatabase")));
Secondly, you don't use using with injected dependencies, so:
public int Add(TEntity item)
{
_context.Set<TEntity>().Add(item);
_context.SaveChanges();
return item.Id;
}
And, for this to work:
public class SomeController : Controller
{
private readonly NavigationContext _context;
public SomeController(NagivationContext context)
{
_context = context;
}
}
And, as a last advice, you should really, really, use the asynchronous versions of the Entity Framework Core methods as much as possible:
public async Task<int> Add(TEntity item)
{
_context.Set<TEntity>().Add(item);
await _context.SaveChangesAsync();
return item.Id;
}
You don't use new NavigationContext(...) at all, you're completely missing the point of dependency injection if you do that. Instead you should be injecting the context into the class that needs it. For example, if you need it directly in your controller, that would look something like this:
public class FunkyController : Controller
{
private readonly NavigationContext _nagivationContext;
public FunkyController(NagivationContext nagivationContext)
{
//Context is injected into the constructor of the controller
_nagivationContext = nagivationContext;
}
public int Add(TEntity item)
{
_nagivationContext.Set<TEntity>().Add(item);
_nagivationContext.SaveChanges();
return item.Id;
}
}
Related
I have a FilterAttribute that has two parameters, one defined in dependency injection and one defined on method of controller as as string
public controller : ControllerBase
{
[MyFilter("Parameter1", FromDependency)]
public ActionResult MyMethod()
{
....
}
}
and the filter
public MyFilter : Attribute
{
MyFilter(string parameter1, context fromDependency)
{
}
}
How can I inject the parameter from dependency injection?
You can implement an IFilterFactory for this purpose. The runtime checks for this interface when creating filters and calls the CreateInstance method that gets an IServiceProvider as a parameter. You can use this provider to create services and inject them into the filter.
The following sample is taken from the docs:
public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
new InternalResponseHeaderFilter();
private class InternalResponseHeaderFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context) =>
context.HttpContext.Response.Headers.Add(
nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));
public void OnActionExecuted(ActionExecutedContext context) { }
}
}
If you need to both use services from DI and values defined on the attribute, you can use the following approach:
public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
private readonly string _attrParam;
public ResponseHeaderFilterFactory(string attrParam)
{
_attrParam = attrParam;
}
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var svc = serviceProvider.GetRequiredService<IMyService>();
return new InternalResponseHeaderFilter(_attrParam, svc);
}
private class InternalResponseHeaderFilter : IActionFilter
{
private readonly string _attrParam;
private readonly IMyService _service;
public InternalResponseHeaderFilter(string attrParam, IMyService service)
{
_attrParam = attrParam;
_service = service;
}
public void OnActionExecuting(ActionExecutingContext context) =>
context.HttpContext.Response.Headers.Add(
nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));
public void OnActionExecuted(ActionExecutedContext context) { }
}
}
You can then apply the filter like this:
public controller : ControllerBase
{
[ResponseHeaderFilterFactory("Parameter1")]
public ActionResult MyMethod()
{
....
}
}
You can implement ActionFilterAttribute to get DI dependencies from HttpContext.RequestServices:
public sealed class MyAttr : ActionFilterAttribute
{
MyAttr(string parameter1)
{
}
public override void OnActionExecuting(ActionExecutingContext context)
{
//Get dependency from HttpContext services
var myDependency = context.HttpContext.RequestServices.GetService<MyDependency>();
//Use it
myDependency.DoSomething();
//....
}
}
Injecting components into action filter attributes directly is not possible but there are various workarounds to allow us to effectively accomplish the same thing. Using ServiceFilter is a relatively clean way to allow dependency injection into individual action filters.
The ServiceFilter attribute can be used at the action or controller level. Usage is very straightforward:
[ServiceFilter(typeof(MyFilter))]
And our filter:
public class MyFilter: IActionFilter
{
MyFilter(string parameter1, context fromDependency)
{
}
}
Obviously, as we are resolving our filter from the IoC container, we need to register it:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<MyFilter>(x =>
new Service(x.GetRequiredService<IOtherService>(),
"parameter1"));
...
}
more details in Paul Hiles article: here
I have a question related to the use of database contexts outside the controller, namely, how to call the database context in a regular class?
To communicate with the database, I use: EF Core
I used options like this:
private readonly MSSQLContext _context;
public BookingController(MSSQLContext context)
{
_context = context;
}
Alternative
using (MSSQLContext context=new MSSQLContext())
{
context.get_Users.ToList();
}
Startup.cs
services.AddDbContext<MSSQLContext>(options =>
options.UseSqlServer(connection));
MSSQLContext.cs
public MSSQLContext()
{
}
public MSSQLContext(DbContextOptions<MSSQLContext> options)
: base(options)
{
}
public DbSet<VIVT_Core_Aud.Models.Core.Logger_Model> Logger_Models { get; set; }
and more tables...
Inject the context into whatever class you need to call into and register that class with the DI framework in your startup class.
For instance,
services.AddTransient<YourType>();
class YourType
{
public YourType(YourDbContext context) { ... }
}
You need to inject context using DI(dependency injection). I am showing you an example of repository pattern. You can search for "Repository pattern EF Core C# examples" will give you lots of examples or blogs to follow. Check here and here
Check out below example..
MyRepository.cs
namespace MyApp.Data.Services
{
public class MyRepository: IMyRepository, IDisposable
{
private MyAppContext _context;
private readonly ILogger<MyRepository> _logger;
public MyRepository(MyAppContext context, ILogger<MyRepository> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger;
}
public IEnumerable<MyTable> GetMyTableData()
{
return _context.MyTable.ToList();
}
}
}
IMyRepository.cs
namespace MyApp.Data.Services
{
public interface IMyRepository
{
//Interface implementation
IEnumerable<MyTable> GetMyTableData();
}
}
Startup.cs of MVC project
services.AddDbContext<MyAppContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("AppDbConnString")));
//Scope of repository should be based on your project used, I recommend to check lifetime & scope based your project needs.
services.AddScoped<IMyRepository, MyRepository>();
Mycontroller.cs
using MyApp.Data.Services;
namespace MyApp.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IMyRepository _myRepository;
public HomeController(ILogger<HomeController> logger, IMyRepository myRepository)
{
_logger = logger;
_myRepository = myRepository ??
throw new ArgumentNullException(nameof(myRepository));
}
public IActionResult Index()
{
return View();
}
public IActionResult GetAllData()
{
var result = _myRepository.GetMyTableData();
return Json(result);
}
}
}
having hard time with CQRS because of this exception. I have a Movie model and also MovieDTO model
//EDIT Okay I've just realized that in GetMoviesQuery I don't use IRepository< MovieDTO > and when I change MovieRepository to IRepository<MovieDTO> then it works
IRepository.cs
public interface IRepository<TEntity>
{
IEnumerable<TEntity> GetAll();
TEntity Get(int id);
TEntity Save(TEntity entity);
void Delete(int entityId);
}
MovieRepository.cs
public class MovieRepository : IRepository<MovieDTO>
{
private MyContext _context;
private IMapper _mapper;
public MovieRepository(MyContext context)
{
_context = context;
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Movie, MovieDTO>();
});
_mapper = config.CreateMapper();
}
public IEnumerable<MovieDTO> GetAll()
{
return _context.Movies.ToList().Select(movie => _mapper.Map<Movie, MovieDTO>(movie));
}
}
GetMoviesQuery.cs
public class GetMoviesQuery
{
public class Query : IRequest<IEnumerable<MovieDTO>> { }
public class Handler : RequestHandler<Query, IEnumerable<MovieDTO>>
{
private MovieRepository _repository;
public Handler(MovieRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(_repository));
}
protected override IEnumerable<MovieDTO> Handle(Query request)
{
return _repository.GetAll();
}
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddTransient<IRepository<MovieDTO>, MovieRepository>();
services.AddHttpClient();
services.AddDbContext<MyContext>(options => options.UseSqlite("Data Source = blogging.db"));
services.AddMediatR(typeof(Startup));
}
Exception:
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: MediatR.IRequestHandler2[TicketReservationSystem.Server.CQRS.Queries.GetMoviesQuery+Query,System.Collections.Generic.IEnumerable1[TicketReservationSystem.Server.Models.DTO.MovieDTO]] Lifetime: Transient ImplementationType: TicketReservationSystem.Server.CQRS.Queries.GetMoviesQuery+Handler': Unable to resolve service for type 'TicketReservationSystem.Server.Data.Repository.MovieRepository' while attempting to activate
I have no idea how to actually find out where's the problem.
public Handler(MovieRepository repository)
should be changed to
public Handler(IRepository<MovieDTO> repository), since you registered your container with interface, not implementation.
services.AddTransient<IRepository<MovieDTO>, MovieRepository>();
If you want to use your original code, register class itself instead
services.AddTransient<MovieRepository>();
If you have implementation Interfaces then add this in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IDepartments, Departments>()
}
I have some problems with EF-Core that I'm trying to figure out.
I use the startup code in the MVC Core application to initalize the db context.
This is my DB context:
public class AccountsDBContext : DbContext
{
public AccountsDBContext(DbContextOptions<AccountsDBContext> options)
:base(options)
{
}
// ...
}
And startup code:
public void ConfigureServices(IServiceCollection services)
{
// Inject the account db
services.AddDbContext<AccountsDBContext>(options =>
options.UseMySQL(Configuration.GetConnectionString("AccountsStore")));
// ...
In all the exampes I see the DB Context is a delivered via the constructor to the controller (I assume by dependency injection) and from there on to other entities\ layers.
[Route("api/[controller]")]
public class AccountsController : Controller
{
private AccountsDBContext _db;
public AccountsController(AccountsDBContext context)
{
this._db = context;
}
However, I'm not very fond of the idea that the db context will be a member at the controller.
I really prefer to get a hold of the db context in the data access layer instead of getting it passed into the repositories classes.
Is there a way to get the context inside the data access layer? (There is no IServiceCollection, IApplicationBuilder, IServiceScopeFactory there as far as I know)
I Understand what you are trying to do. I have done exactly that. The key is to Create a static class in your DAL that uses the IServiceCollection. then in here you add your context here's mine and it works a treat My front end doesn't even know about entity framework, nethier does my business layer:
public static IServiceCollection RegisterRepositoryServices(this IServiceCollection services)
{
services.AddIdentity<ApplicationUser, IdentityRole<int>>(
config => { config.User.RequireUniqueEmail = true;
config.Cookies.ApplicationCookie.LoginPath = "/Account/Login";
config.Cookies.ApplicationCookie.AuthenticationScheme = "Cookie";
config.Cookies.ApplicationCookie.AutomaticAuthenticate = false;
config.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = async ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/visualjobs") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 401;
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
}
await Task.Yield();
}
};
}).AddEntityFrameworkStores<VisualJobsDbContext, int>()
.AddDefaultTokenProviders();
services.AddEntityFramework().AddDbContext<VisualJobsDbContext>();
services.AddScoped<IRecruiterRepository, RecruiterRepository>();
services.AddSingleton<IAccountRepository, AccountRepository>();
return services;
}
then in my service layer I have another static class. My service layer has a reference to the repository layer and I register the repository services here (bootstrapping the repository into the service layer), like so and then I do the same again in the UI:
Service layer code:
public static class ServiceCollectionExtensions
{
public static IServiceCollection RegisterServices(this IServiceCollection services)
{
services.RegisterRepositoryServices();
services.AddScoped<IRecruiterService, RecruiterService>();
services.AddSingleton<IAccountService, AccountService>();
return services;
}
}
The Magic in the Repository Layer:
public partial class VisualJobsDbContext : IdentityDbContext<ApplicationUser, IdentityRole<int>, int>
{
private IConfigurationRoot _config;
public VisualJobsDbContext() { }
public VisualJobsDbContext(IConfigurationRoot config, DbContextOptions<VisualJobsDbContext> options) : base(options)
{
_config = config;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(#_config["ConnectionStrings:VisualJobsContextConnection"]);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{....
Inject your repository/DAL implementation into the controller and have the DbContext injected into the repo constructor. The DI container will hook it all up as long as the appropriate classes are registered
How about this?
DALAccount.cs
public class DALAccount
{
private AccountsDBContext _db;
public DALAccount(AccountsDBContext db)
{
_db = db;
}
public IQueryable<User> Get()
=> _db.User.AsQueryable();
}
Your Api
public class AccountsController : Controller
{
private AccountsDBContext _db;
public AccountsController(AccountsDBContext context)
{
this._db = context;
}
public IActionResult Index()
{
DALAccount dal = new DALAccount(_db);
var list = dal.Get();
}
}
According to documents when I configure DbContext like below DI register it in scope (per http request)
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
The problem appears when I am trying to access it in another thread.
public class HomeController : Controller
{
private readonly DBData _context;
public HomeController(DBData context)
{
_context = context;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}
I want to configure DbContext creation per each call (AddTransition). It would give me possibility to write next code
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
//somehow configure it to use AddTransient
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
services.AddTransient<IUnitOfWorkFactoryPerCall, UnitOfWorkFactory>();
services.AddScoped<IUnitOfWorkFactoryPerRequest, UnitOfWorkFactory>();
services.AddMvc();
}
public interface IUnitOfWorkFactoryPerCall : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactoryPerRequest : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactory : IDisposable
{
DBData Context { get; }
}
public class UnitOfWorkFactory : IUnitOfWorkFactoryPerCall, IUnitOfWorkFactoryPerRequest
{
public UnitOfWorkFactory(DBData context)
{
Context = context;
}
public DBData Context
{
get; private set;
}
public void Dispose()
{
Context.Dispose();
}
}
So now if I want to create DBContext per request I will use IUnitOfWorkFactoryPerRequest, and when I want to use DBContext in some background thread I can use IUnitOfWorkFactoryPerCall.
My temporary solution.
I created singleton which can create Context "in transient way"
public class AppDependencyResolver
{
private static AppDependencyResolver _resolver;
public static AppDependencyResolver Current
{
get
{
if (_resolver == null)
throw new Exception("AppDependencyResolver not initialized. You should initialize it in Startup class");
return _resolver;
}
}
public static void Init(IServiceProvider services)
{
_resolver = new AppDependencyResolver(services);
}
private readonly IServiceProvider _serviceProvider;
public AppDependencyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IUnitOfWorkFactory CreateUoWinCurrentThread()
{
var scopeResolver = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
return new UnitOfWorkFactory(scopeResolver.ServiceProvider.GetRequiredService<DBData>(), scopeResolver);
}
}
Then I call init method in Startup Configure method
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
AppDependencyResolver.Init(app.ApplicationServices);
//other configure code
}
And after all I can call AppDependencyResolver.Current.CreateUoWinCurrentThread() in some background thread.
If someone can provide more elegant solution I will be appreciated.
Within your controller, why are you trying to inject into private readonly DBData _context;? If you've registered your IUnitOfWorkFactoryPerCall via DI, you should be injecting that into your controller I believe? You then access your context via the interface.
To expand, this is what I am suggesting you do:
public class HomeController : Controller
{
private readonly IUnitOfWorkFactoryPerCall _contextFactory;
public HomeController(IUnitOfWorkFactoryPerCall contextFactory)
{
_contextFactory = contextFactory;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _contextFactory.Context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}