Configuring Entity Framework In ASP.NET 5 using a DAL - c#

I'm trying to completely separate the repository / data access later whilst still using configuration in the main project (through appsettings.json)
My current solution is either to hard code the connection string in the OnConfiguring method in the DbContext which is not ideal. Or perhaps create a config file just for the repository layer and read from that.
I would like the main project (web service in this case) to be completely independent of the data access layer (as I will be communicating to it using a service layer anyway), but be able to configure it at start-up using the default appsettings.json
Is there a good approach of doing this? Or must the main project have a reference to the repository layer.
Project layout:
Project.WebService
- Startup.cs
- EmployeeController.cs
- appsettings.json
Project.Service
- EmployeeService.cs
- EmployeeDTO.cs
Project.DAL
- DbContext.cs
- EmployeeRepository.cs
Project.Entities
- Employee.cs
The WebService references the Service, the Service references the DAL and the DAL references the Entities / POCOs
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
string connString = Configuration["AppSettings:ConnectionString"];
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MyDbContext>(options => options.UseSqlServer(connString));
}
The above doesn't work anyway with the DbContext being in another class library. The following exception gets thrown.
An exception of type 'System.InvalidOperationException' occurred in
EntityFramework.Core.dll but was not handled in user code
Additional information: No database providers are configured.
Configure a database provider by overriding OnConfiguring in your
DbContext class or in the AddDbContext method when setting up
services.
Which is one reason why I hard-coded the connection string into the OnConfiguring method.
Does anyone have any good approaches / solutions for this or am I missing something? The web service doesn't need to know what database it is talking to, or have any reference to it at all. It simply calls a service, a receives a DTO or, sends a DTO.

Hook up all dependencies:
If you really insist on your webservice not having a reference to your DAL, you could always try an 'IT ops' layer.
Some infrastructure layer (which you can call for instance "InfratructureService", which has a reference to all your projects (webservice, Service, DAL, Entities). In this infrastructure layer you create the container which every project is going to use.
Then all your projects should have a class which is called "ConfigureMe" or something, with 1 method called "Configure" for instance. That method takes as parameter an instance of the container which was build in InfratructureService. At startup your Webservice is going to give "InfratructureService" a sign to build the container and call every "Configure" method of every "ConfigureMe" class of every project it references.
In those "Configure" methods each project can ofcourse register their own depedencies in the container. And voila, all dependencies are known in the container without making unnecessary references and ready to be resolved via constructor injection.
The connectionstring problem:
Now as for your hardcoded connectionstring, you know about configuration in asp.net core right? Once you've set up your configuration as described here you can insert via dependency injection the IOptions anywhere you want. That means also in your DAL.
Let me know if anything is unclear or you can't figure it out.

Related

'AddEntityFramework*' was called on the service provider, but 'UseInternalServiceProvider' wasn't called in the DbContext options configuration

I'm upgrading an ASP.NET Core application from Framework 2.2 to 3.1. It also uses Entity Framework Core.
In the Startup.ConfigureServices method, there is this code:
services.AddEntityFrameworkNpgsql()
.AddDbContext<MainDbContext>(options => options
.UseNpgsql(Configuration.GetConnectionString("MainDbContext")));
Everything was fine with .NET Core 2.2. With .NET Core 3.1, I get this warning on every application start:
'AddEntityFramework*' was called on the service provider, but 'UseInternalServiceProvider' wasn't called in the DbContext options configuration. Remove the 'AddEntityFramework*' call as in most cases it's not needed and might cause conflicts with other products and services registered in the same service provider.
Looking up the UseInternalServiceProvider method, it looks like that should be called on the options to pass on the main service provider. Unfortunately, at this point, the service provider does not exist yet. It is just about to be built.
I don't understand what the problem is and what this warning wants to tell me, but failed to do. How can I make that warning go away? The web doesn't know about this message yet.
Remove AddEntityFrameworkNpgsql. The docs explain that :
Calling this method is no longer necessary when building most applications, including those that use dependency injection in ASP.NET or elsewhere. It is only needed when building the internal service provider for use with the method. This is not recommend other than for some advanced scenarios.
The actual Getting Started page For Npgsql shows there's no need for anything extra :
simply place the following in your ConfigureServices method in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Other DI initializations
services.AddDbContext<BloggingContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("BloggingContext")));
}

What is the proper way to reuse DbContext across different projects in a single solution?

I have a .Net Core solution which consists of 3 projects like below:
Common : (All EF Data, migrations, Model, DbContext)
WebApi : (Rest api consuming DbContext from Common)
Worker : (Background services that aims to consume DbContext from Common)
I wanted to place all my EF logic and DbContext in Common and consume it from my other two projects.
WebApi is working fine,
but I couldn't use it from my hosted services found in Worker project.
There are 4 background workers and all of them require access to database so I wanted to get access to my DbContext inside them.
So, what is the proper way to reuse a DbContext across multiple projects.
It can be considered that all services need access some common tables. So isolating tables via different Dbcontexts is not an option for me.
This is my Startup.cs in WebApi:
services.AddDbContext<DataContext>(options => options
.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
This is my Program.cs in Worker:
services.AddDbContext<DataContext>(options => options
.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
It throws an error telling me that I can not consume a scoped service from a singleton one.
If you just want to reference a DbContext in your Web API + Background Workers, then I don't understand what the issue is: Just reference the Common project from your worker projects. From your post the issue looks to be that the service injection you are using successfully in the Web API doesn't work with the background worker services. (Assuming Windows Services?)
Start instead with a simpler scenario. Initiate the DbContext inside the service when used rather than inject it.
I.e.
using(var context = new DataContext("DefaultConnection"))
{
// ...
}
Your connection string configuration should be identifying NPGSQL as the provider so as long as that config is all set up in your Services config then the DBContext should be able to configure by connection string name. If that works then there will probably be a different mechanism for injection. From what I could quickly find it seems examples used a service locator pattern to resolve dependencies, I don't know if there are better options these days for Windows Services.
If injection isn't really an option and you have to resort to service locator-like implementations then I would probably consider something like a Lazy Property injection pattern I've used in the past:
public class WorkerService
{
private readonly IContainer _container = null;
public WorkerService(IContainer container)
{
_container = container ?? throw new ArgumentNullException("container");
}
private IDataContextFactory _contextFactory = null;
public IDataContextFactory ContextFactory
{
get { return _contextFactory ?? (_contextFactory = _container.Resolve<IDataContextFactory>()); }
set { _contextFactory = value; }
}
public void Execute()
{
using(var context = ContextFactory.Create()) // returns a DataContext.
{
// do stuff.
}
}
}
Where IContainer represents a contract interface for your given DI framework. (Unity, Autofac, etc.)
Alternatively, a unit of work for scoping a DbContext. Given a Service instance will be long running we don't want to inject or resolve a DbContext, but rather a Context Factory which we can use to receive an initialized DbContext which can be used in a dispose. Normally with web requests the instance is scoped to the request and disposed by the container at the end of the request. With a service we want to ensure the DbContext is disposed regularly. A DI can be set up so that transient instances of the context are returned, but those instances need to be disposed meaning it's not suited for constructor injection, but rather via a service locator. If a single DbContext instance was used and injected in the constructor of a service, it would live until the service stopped which would see that DbContext get slower and slower as time went on due to tracked entities.
I think you're a bit confused here. The thing you want to reuse is the DbContext code + all EF logic. You don't want to (can't) reuse same DbContext instance across projects (apps).
So to reuse the code, you just need to put all of your Model + DBContext in a project. Then in other projects, you can add reference to it. And start using it.
Remove all AddDbContext from Startup.cs/Program.cs.
Put DbContext connectionstring in Common project instead.
Create some CRUDs in Common project. Then all other projects can use Common's CRUD which is connected to same DbContext.

Access connection string in data layer

I'm currently developing a web API in .NET core. I have three projects in my solution with the following references:
Web -> Services -> DataAccess
So the web layer does not have a direct reference to the DataAccess layer.
My question is: What is the right way to get the connectionstring in this type of architecture with three layers? I have read around, but can't find any nice solution where I can access my connectionstring in the third layer, just because the web layer does not have a reference to the third layer.
I came accross this approach:
services.Configure<ConnectionConfig>(Configuration.GetSection("ConnectionStrings"));
services.AddScoped<IQueryHelper>(c => new QueryHelper(cn));
This work well if I just have two layers, where the QueryHelper is in the service-layer.
But I want to access one or multiple connectionstrings in my DataAccess-layer.
Edit: Injecting the configuration might not be the smartest idea as you can read here. Better way would be to configure options for each connection string that can be accessed by the DAL aswell.
services.Configure<MyConnectionInfo>(options => Configuration.GetSection("MyConnectionInfo").Bind(options));
Now in your repository just inject IOptions<MyConnection> and use the values.
Old Answer: Just inject your configuration into your datalayer-classes. Before you have to register the configuration with the ioc-container.
services.AddSingleton(typeof(IConfiguration), Configuration);
Now access the connectionstrings you need by injection the instance of IConfiguration. You could also configure more options instead, but injecting the configuration is fine aswell.

How to manage db context in n-tier asp.net application with dependecy injection?

I have 3-layers architecture - asp.net web api, BLL and DAL. I use Ninject as dependency injector for injecting db context and objects between layers. As ORM i use Entity Framework . Injection of db context is processed in DAL. So every time some repository in BLL is instancied, new instance of db context is also created. Im doing it like this:
public class UserRepository : IUserRepository
{
private IChatDbModel _chatDbModel;
public UserRepository(IChatDbModel chatDbModel)
{
this._chatDbModel = chatDbModel;
}
It´s neccesary to say that PerWebRequest, which would solve my problem is not availible in lower layers than web api. Only web api layer has info about http request lifetime, so can use Ninject.Web.Common library.
My question is, is there a way how to share db context for whole request like using of PerWebRequest in this architecture? Or is really neccesary to create new instance of db context for every new instance of repository?
Edit
I forgot to mention that in each layer I´m referencing Ninject library and I´m registering mapping for the specific layer. The method in DAL looks like this:
public static void Register(IKernel kernel)
{
kernel.Bind<IChatDbModel>().To<ChatDbModel>();
}
in BLL it looks like this:
public static void Register(IKernel kernel)
{
kernel.Bind<IUserRepository>().To<UserRepository>();
NinjectDataAccess.Register(kernel);
}
in API it looks like this, it´s located in NinjectWebCommon.cs:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IUserLogic>().To<UserLogic>();
NinjectLogic.Register(kernel);
}
so in each layer, I´m not only mapping it´s own objects but also calling register method of the layer lying below if any and with mechanism like this, I can register dependency mapping of each layer without referencing all layers in API, where I should not reference any other layer than BLL, so in my case DAL. If I reference the DAL in API layer, then it would be possible to define the mapping and call PerWebRequest, because I would have the objects, but I´m not and I think this should be avoided by the architecture, or am I wrong?
You can achieve per request instance by registering OnePerRequestHttpModule http module, which internally uses HttpContext lifecycle to track registered types and dispose them at the end of the request/response lifecycle.
After installing Ninject.Web.Common package, in NinjectWebCommon.cs you have to do (it will be added automatically once nuget package is installed)
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule))
And for your type registrations, you can do the following
//register IChatDbModel with per request scope
kernel.Bind<IChatDbModel>().To<ChatDbModel>().InRequestScope();
//register repositories with default transient scope
kernel.Bind<IUserRepository>().To<UserRepository>();
All your repositories will be transient, so everywhere they are injected , a separate instance will be supplied, but your DBContext instance will created and disposed per request.
I am assuming you have added both BAL and DAL reference to web api project, so that web api project has access to IChatDbModel to perform type registration in Ninject kernel.

Putting DBContext scope to IService

I have a web app and a windows service app.
The web app injects IPersonService into its MVC controllers.
The windows app also uses IPersonService.
The service takes 3 dependencies on IPersonRepo, IAddressRepo, IEmploymentRepo for example.
The implementations of the repositories take a DBContext for Entity Framework use.
In a web app I can register the DBContext as Bind<MyContext>().ToSelf().InRequestScope();
In the windows service its trickier. I could leave it so the DBContext is transient but that seems wrong.
So I thought I could make the services be the scope to determine the life cycyle of the DBContext but am completely unsure how I would go about that to make sure it worked well for a web app and windows service app.
Any help would be greatly appreciated
If it's important that all 3 repos use the same DbContext instance, you can do something like this:
var context = new DbContext(...);
Bind<IPersonRepo>().To<PersonRepo>().WithConstructorArgument("dbContext", context);
Bind<IAddressRepo>().To<AddressRepo>().WithConstructorArgument("dbContext", context);
Bind<IEmploymentRepo>().To<EmploymentRepo>().WithConstructorArgument("dbContext", context);
Like this the same context instance is shared between the repos.
If the repositories are not aware of each other's entities (and changes to these entities), you could simply inject a fresh instance of DbContext in each repo, by binding in transient scope (default behavior):
Bind<MyContext>().ToSelf();

Categories

Resources