How dependency injection is instantiated using Unit of Work? - c#

I was looking to implement the Repository pattern with unit of work in my asp.net core 2.0 app. There are not many examples of doing this using the dependency injection (DI) principle being used in .net core. I tried to rewrite this example found in the docs. I also tried to add async operations where I could.
Now the idea is that the unit of work passes it's own dbcontext to the GenericRepository for each of the entities that is in use. This then makes sure that you ony use one dbcontext even if you work on two entities.
In my controller I fetch some data like this:
var model = new IndexViewModel
{
Companies = await _unitOfWork.CompanyRepository.GetAsync()
};
In my unit of work the dbcontext is being created using DI. Then it creates new instances of the GenericRepository for each entity while it passes it's dbcontext to the genericrepository's contructor:
private ApplicationDbContext _context;
private GenericRepository<Company> companyRepository;
private GenericRepository<Person> personRepository;
public UnitOfWork(ApplicationDbContext context)
{
_context = context;
}
public GenericRepository<Company> CompanyRepository
{
get
{
if (this.companyRepository == null)
{
this.companyRepository = new GenericRepository<Company>(_context);
}
return companyRepository;
}
}
//repeat for Person
But I fear the dependency injection will automatically create a new dbcontext for each time I use the GenericRepository.
public class GenericRepository<TEntity> where TEntity : class
{
internal ApplicationDbContext _context;
internal DbSet<TEntity> dbSet;
public GenericRepository(ApplicationDbContext context)
{
_context = context;
dbSet = context.Set<TEntity>();
}
//...other methods
}
I fear this will actually create two contexts. one for each(if two requests are made)? So in reality the dbcontext would be instantiated three times, one in unitofwork then one for each repository? Here is a link to the app on github. It is working, but I want to understand how this works. Thanks for any answers!

It all depends on how you register your DbContext and which lifetime you use.
The default overload of .AddDbContext will always register the DbContext with scoped lifetime. That means it will create one instance per request.
If you make it transient, it will create one instance per resolve and singleton one instance per application lifetime.
That should be true for most cases.
However, if you have a service which has a higher lifetime than its dependencies (i.e. a singleton and inject a scoped service), then the above is not true and you have to be careful when you design and do your registration and take this into consideration.

Related

NET 5 and EF: how to use AddPooledDbContextFactory in liu of DbContext in services

I recently came across AddPooledDbContextFactory concept as part of my NET 5 self-education piece and am keen to implement it properly. However, I am not sure how to use it with generics that I generally use.
Example of my current setup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TestDbContext>(
(s, o) => o.UseNpgsql(Configuration.GetConnectionString("DatabaseConnection"))
.UseLoggerFactory(s.GetRequiredService<ILoggerFactory>()));
// other code //
}
my repository generic:
public class Repository<T> : IRepository<T> where T
{
private readonly TestDbContext _dbContext;
public Repository(TestDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task Create(T entity)
{
await _dbContext.Set<T>().AddAsync(entity);
await _dbContext.SaveChangesAsync();
}
// other methods //
}
this is invoked in following manner as example:
public class WeatherForecastController : ControllerBase
{
private readonly IRepository<Test> testRepo;
public WeatherForecastController(IRepository<Test> testRepo)
{
this.testRepo= testRepo;
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> GetAsync()
{
await testRepo.Create(new Test { Name = "Superman" });
// other code
}
}
I would like to convert this to use the new AddPooledDbContextFactory concept but cannot find enough documentation to figure out how to do this.
Atm only thing that comes to mind is using statements at each method but that doesn't make sense.
Any advice on this?
Documentation is not yet complete and is in progress, you track this issue
https://github.com/dotnet/EntityFramework.Docs/issues/2523
You can also a look at the tests for AddPooledDbContextFactory to see how to register DbContext with
https://github.com/dotnet/efcore/search?q=AddPooledDbContextFactory
for example to register DbContext:
services.AddPooledDbContextFactory<TContext>(ob =>
ob.UseSqlServer("ConnectionString").EnableServiceProviderCaching(false), poolSize: 32)
Then in your class, inject an IDbContextFactory<TContext> and use it like this:
using(var context = _factory.CreateDbContext())
{
var orders = await context.Orders.Where(o => o.Id > 3).ToListAsync();
}
According to this post:
Note that the DbContext instances created in this way are not managed
by the application's service provider and therefore must be disposed
by the application
You can also check out this post to see how to use IDbContextFactory:
https://learn.microsoft.com/en-us/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-5.0
#Aeseir your code looks good to me. You are following best practices and you don't need to change it.
You are using the Repository Pattern, so your Repository class has all of your query logic which helps create loosely coupled and maintainable code.
In your ConfigureServices, calling: services.AddDbContext<TestDbContext>() registers TestDbContext with Scoped service lifetime. This is the way that DbContext is designed to work, and it will also work well with ASP.NET controllers, since they have a Scoped lifetime as well.
You did not show your code for registering IRepository, but that service lifetime should be Scoped as well. Btw, you can tell BuildServiceProvider() to validate scope registrations:
builder.Services.BuildServiceProvider(validateScopes: true);
Since DbContext is designed to have a Scoped service lifetime, and since your IRepository and Controller services are Scoped as well, every request gets brand new:
Controller
IRepository
DbContext
Those services are used for the request and then Diposed. This is how ASP.NET is intended to work.
Apparently at some point, DbContext pooling has been introduced to improve performance. In this case, EF Core manages a pool of context instances for you and resets them after each request. This can improve performance, although in some situations, the benefit might be small. See MSDN documentation for more details.
I think for use with ASP.NET controllers (i.e. the code you posted above) all you need to do to take advantage of EF Core context pooling is call AddDbContextPool():
builder.Services.AddDbContextPool<ApplicationDbContext>(/* ... */);
However, if you needed to use DbContext in services registered with Singleton lifetime, then the pattern above would not work well. Because when a Scoped service gets used in a Singleton service, the Scoped service is essentially a Singleton. Each request would not get a new DbContext, nor a reset one from the pool. (See QUESTION below.)
In that case, you might want to use the DbContext factory pattern instead:
builder.Services.AddDbContextFactory<ApplicationDbContext>(/* ... */);
Or, if you want to use context pooling with a factory pattern:
builder.Services.AddPooledDbContextFactory<ApplicationDbContext>(/* ... */);
The DbContextFactory can then be used in other services through constructor injection. For example:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
The injected factory can then be used to construct DbContext instances in the controller code. For example:
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
Keep in mind that when you call CreateDbContext(), context instances are not managed by the service provider and therefore must be disposed by the application. Hence you need to Dispose of them yourself, such as in the example above which does so with the using statement.
QUESTION
I am doing my best to understand this stuff and explain it, but I might be wrong, so please call out an inaccuracies in my post.
When using AddDbContextPool(), does the DbContext get registered as a Singleton or Scoped?
I found in MSDN documentation that it's effectively registered as a Singleton:
Context pooling works by reusing the same context instance across requests; this means that it's effectively registered as a Singleton, and the same instance is reused across multiple requests (or DI scopes). This means that special care must be taken when the context involves any state that may change between requests.
However, I have found that if AddDbContextPool() is used along with true for validatedScopes:
builder.Services.BuildServiceProvider(validateScopes: true)
When DbContext is consumed from another service which is registered as a Singleton, the following exception is thrown:
System.InvalidOperationException: 'Cannot consume scoped service 'ApplicationDbContext' from singleton 'IRepository'.'
Hence why I stated above that DbContext still gets Scoped service lifetime.

Register type as InstancePerRequest with Exception(s)

I am using AutoFac in my Web API application (using the latest versions available as of time this question was posted). One of my service dependencies is an AuditService which uses an instance of by DbContext type (let's call it MyDbContext for now). Most of my services and the MyDbContext type are all registered using InstancePerRequest. For my AuditService I want to make an exception, I always want to inject an owned (new) instance of my MyDbContext.
Question: Using AutoFac registrations, how do I register my AuditService in such a way that it always gets an owned (new) instance of MyDbContext?
What could work:
I could hard code the creation of MyDbContext in the constructor of AuditService thus circumventing AutoFac all together.
I could use PropertyInjection or MethodInjection and provide a new instance of MyDbContext in the Life Time event OnActivating
I could define a second interface on MyDbContext and provide a second registration and use InstancePerOwned.
Do I have to pick one of these above options (if so I would lean towards 3) or am I missing something simple? Is there a way to define what I want in my registration code?
// registration called in web api startup code
public void RegisterAutofac(ContainerBuilder builder)
{
builder.RegisterType<MyDbContext>()
.As<IMyDbContext>()
.InstancePerRequest();
builder.RegisterType<BusinessService>()
.As<IBusinessService>()
.InstancePerRequest();
builder.RegisterType<AuditService>()
.As<IAuditService>()
.InstancePerRequest();
}
public class AuditService
{
// expects an isolated instance on this request
private readonly IMyDbContext _dbContext;
public AuditService(IMyDbContext dbContext)
{
_dbContext = dbContext;
}
}
public class BusinessService
{
// expect a shared IMyDbContext instance across the request
private readonly IMyDbContext _dbContext;
public BusinessService(IMyDbContext dbContext)
{
_dbContext = dbContext;
}
}
Solution Attempts with InstancePerOwned
This causes an exception
builder.RegisterType<MyDbContext>()
.As<IMyDbContext>()
.InstancePerRequest()
.InstancePerOwned<AuditService>();
Autofac.Core.DependencyResolutionException: "No scope with a tag matching 'AuditService' is visible from the scope in which the instance was requested. If you see this during execution of a web application, it generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario). Under the web integration always request dependencies from the dependency resolver or the request lifetime scope, never from the container itself.
at Autofac.Core.Lifetime.MatchingScopeLifetime.FindScope(ISharingLifetimeScope mostNestedVisibleScope)
at Autofac.Core.Resolving.InstanceLookup..ctor(IComponentRegistration registration, IResolveOperation context, ISharingLifetimeScope mostNestedVisibleScope, IEnumerable`1 parameter
I tried reversing the order of InstancePerOwned and InstancePerRequest calls but this seems to have no effect, the same MyDbContext instance is reused for both BusinessService and AuditService instances in the same request. This was tested with object.ReferenceEquals from in an ApiController and passed in both instance's _dbContext fields.
builder.RegisterType<MyDbContext>()
.As<IMyDbContext>()
.InstancePerOwned<AuditService>()
.InstancePerRequest();
Try switching from InstancePerRequest to InstancePerLifetimeScope. In most apps this generally behaves the same and is the way to share registrations across apps that both do and don't have per-request semantics anyway. (Which is to say, this is pretty common.)
Once you have InstancePerLifetimeScope on your context object, you can use Owned<T> in your AuditService constructor to get a fresh copy.
So...
builder.RegisterType<MyDbContext>()
.As<IMyDbContext>()
.InstancePerLifetimeScope();
then...
public AuditService(Owned<IMyDbContext> dbContext)
Note your AuditService will be responsible for disposing of the dbContext when it's done, so you'll have to handle that manually (that's part of using Owned<T>). But if you've already got some one-off stuff going on, that shouldn't be a big deal.

DBContext between business layers

I've created two projects:
Web Project, that contains all the viewmodels/data/controllers etc. And a Web Api project to allow form capture.
I simply want to capture the data in the web Api and save it to the database where it will become accessible to the front end.
I am experiencing an issue initialzing the DBcontext within the Api controller and need help.
namespace ZebraCRM.API2.Controllers
{
[Route("api/[controller]")]
public class LeadsController : Controller
{
private readonly ApplicationDbContext _context;
public LeadController(ApplicationDbContext context)
{
_context = context;
}
// POST api/values
[HttpPost]
public void Post(Lead formData)
{
formData.DateCreated = DateTime.Now;
_context.Lead.Add(formData);
_context.SaveChanges();
}
}
The above idea was taken from the controller in the main web project, but is obviously not the right approach in this situation.
the debug outputs the following
System.InvalidOperationException: Unable to resolve service for type 'ZebraCRM.Web.Data.ApplicationDbContext' while attempting to activate 'ZebraCRM.API2.Controllers.LeadsController'.
The framework doesn't know how to constructor a LeadController because it doesn't know how to satisfy the ApplicationDbContext context parameter when it calls the constructor. To solve this, you could simply assign the value as part of your constructor, eliminating the parameter.
namespace ZebraCRM.API2.Controllers
{
[Route("api/[controller]")]
public class LeadsController : Controller
{
private readonly ApplicationDbContext _context;
public LeadController()
{
_context = new ApplicationDbContext();
}
// POST api/values
[HttpPost]
public void Post(Lead formData)
{
formData.DateCreated = DateTime.Now;
_context.Lead.Add(formData);
_context.SaveChanges();
}
}
}
Or, if you do want to leave the constructor parameter in place, you'll need a DI container such as AutoFac or Ninject. If you're new to DI, I suggest you watch this video. When you set things up for Dependency Injection, you will basically pass something to the framework that says "When constructing an object that needs an X, here's how you give it an X". This allows you to better follow SOLID principles. An object can demand an IRepository (an interface) and as part of your DI setup you can say "when an object demands an IRepository, I want to pass it a SqlServerRepository (which would implement IRepository)". Then if you later decided to switch to MySQL, you could modify the setup to use a MySqlRepository instead of SqlServerRepository, without needing to modify anything about the controller, since the controller would use the repository via the interface.

LightInject a database context in MVC

I'm starting to use LightInject in my MVC application, but I'm a bit confused as to how to implement an instance of a DB Context class.
I know I can just inject it via a constructor... but what's the point of LightInject if I have to do this.
Also, the DB Context class in my application already implements an interface (IdentityDbContext) so it somehow doesnt seem right to create another interface for the repository.
The DB Context class does have this in the constructor:
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
So I can quite easily call:
_context.Create()
In a constructor - but my understand is, this would go against SOLID principles.
So how does one use LightInject to create an instance of a database context class?
You can register the context and then pass it with constructor injection:
In you ioc configuration file:
container.Register<Context, Context>(new PerScopeLifetime());
Some service:
private readonly Context _context;
public BookService(Context context)
{
_context = context;
}
If you want to use an interface, then register an interface and pass it everywhere you want.

ASP.NET C# Services using IRepository and each other

I have my IRepository interface which is bound using Ninject and InRequestScope.
The Repository Binding is:
kernel.Bind<IRepository>().To<DefaultRepository>().InRequestScope().WithConstructorArgument("dbContext", dbContext => kernel.Get<DefaultContext>());
If I have a service IAccountService which is bound in the same way.
The concrete implementation:
public class AccountService : IAccountService
{
IRepository _repository;
public AccountService(IRepository repository) { _repository = repository; }
}
If I now create another service IBlogService which is bound in the same way.
The concrete implementation:
public class BlogService : IBlogService
{
IRepository _repository;
IAccountService _accountService;
public AccountService(IRepository repository, IAccountService accountService)
{
_repository = repository;
_accountService = accountService;
}
}
Both these services are asking for IRepository are they getting the same instance or are they requesting two completely different isolated DbContexts?
It does "work" but is there an obvious downfall to this method?
Because you use .InRequestScope() all services are getting the same instance of DefaultRepository during your request. So when a new request comes in, an new instance of your DefaultRepository is created.
Also if your IRepository interface implements IDisposable, Ninject will dispose it when needed.
I don't think there is any downfall, since in web applications, the lifecycle of your DbContext should be per request (unless you have very good reasons not to follow this approach).
EDIT
You can also implement a transaction per request pattern, so you can avoid inconsistency if saving in one repository succeeds, but saving in another repository fails. This way you can rollback all the changes if anything goes wrong during your request.
Not saying you should implement this, just wanted to let you know it's possible. This is a very basic example, you should do some extra checks on when you want to create a transaction (for example only on http POSTS), and maybe you want to delegate this to a seperate class so your Global.asax will not be cluttered with all this resposiblilites. Another thing to mention is that this will only work if you have your DbContext injected with .InRequestScope(), so the same context is used through your request.
It works like this:
In your Global.asax Application_BeginRequest() method, you should initialize your transaction like this:
var transaction = _context.Database.BeginTransaction(IsolationLevel.ReadCommitted);
_httpContext.Items["_Transaction"] = transaction;
In your Global.asax Application_Error() method, you can set an error flag like this
_httpContext.Items["_Error"] = true;
Finally in your Global.asax Application_EndRequest() you can commit everything if there are no errors, or else rollback:
var transaction = (DbContextTransaction)_httpContext.Items["_Transaction"];
if (_httpContext.Items["_Error"] != null)
transaction.Rollback();
else
transaction.Commit();

Categories