Get Db context inside data access layer - c#

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();
}
}

Related

C# Asp net core How to call database context outside of controllers

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);
}
}
}

Some services are not able to be constructed in ASP.NET Core

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>()
}

Using DI in AspNET Core

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;
}
}

Why is DbContext (ASP.NET) null in MyService?

I use c#, ASP.NET Core, EF.
I have Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
...
}
I have Controller:
public class HomeController : Controller
{
public AppDbContext _db;
public HomeController(AppDbContext db)
{
_db = db;
}
public IActionResult Index()
{
// _db is NOT NULL
var d = _db.Users.FirstOrDefault();
}
}
I have class MyService.cs:
public class MyService
{
public static AppDbContext _db;
public MyService(AppDbContext db)
{
_db = db;
}
public static void GetOtherValue()
{
// _db is NULL
var d = _db.Users.FirstOrDefault();
}
}
HomeController works correctly: _db is not null.
Why variable _db is null in MyService.cs? How to fix it?
UP:
public class OtherController : Controller
{
....
MyService.GetOtherValue();
....
}
You are not injecting your service anywhere.
You just use static method thus constructor is never called. You can inject through the constructor.
First add a service into ASP.Net Core container:
services.AddScoped<MyService, MyService>();
Then inject via constructor in your controller class:
public class HomeController : Controller
{
private readonly MyService _myService;
public HomeController (MyService myService)
{
_myService = myService;
}
}
MyService class will be created every requests ( AddScoped()). Then the your DbContext will be created automatically.
Read more about DI in .NET Core

EF 7 (Core). Create DBContext like AddTransient

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();
}
}

Categories

Resources