I tried in scope of one HttpRequest call through Unit of Work my repository twice, but in second time I have got an System.ObjectDisposedException exception. Can anybody help me?
My DBContext
private readonly string connectionString;
public SwapDealContext(IConfigurationManager configurationManager)
: base()
{
this.connectionString = configurationManager.DbConnectionString;
}
public SwapDealContext(DbContextOptions<SwapDealContext> options)
: base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(this.connectionString);
}
}
public virtual DbSet<User> Users { get; set; }
AutoFac Module:
public class DataAccessAutoFacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
Assembly assembly = typeof(DataAccessAutoFacModule).Assembly;
base.Load(builder);
builder.RegisterAssemblyTypes(assembly).AsSelf().AsImplementedInterfaces().InstancePerLifetimeScope();
builder.RegisterType<UnitOfWork.UnitOfWork>().As<IUnitOfWork>().InstancePerLifetimeScope();
builder.RegisterType<UnitOfWorkFactory>().As<IUnitOfWorkFactory>().InstancePerLifetimeScope();
}
}
Interfaces:
public interface IUnitOfWorkFactory
{
IUnitOfWork CreateUnitOfWork();
}
public interface IUnitOfWork : IDisposable
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
T GetRepository<T>()
where T : class;
}
Implemantations:
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext dbContext;
private readonly Dictionary<string, object> repositories;
private readonly ILifetimeScope lifetimeScope;
public UnitOfWork(
DbContext dbContext,
ILifetimeScope lifetimeScope)
{
this.dbContext = dbContext;
this.lifetimeScope = lifetimeScope;
this.repositories = new Dictionary<string, object>();
}
public void Dispose()
{
this.dbContext.Dispose();
this.repositories.Clear();
}
public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
try
{
int changes = await this.dbContext.SaveChangesAsync(cancellationToken);
return changes;
}
catch (Exception ex)
{
throw;
}
}
public T GetRepository<T>()
where T : class
{
var typeName = typeof(T).Name;
if (!this.repositories.ContainsKey(typeName))
{
T instance = this.lifetimeScope.Resolve<T>(new TypedParameter(typeof(DbContext), this.dbContext));
this.repositories.Add(typeName, instance);
}
return (T)this.repositories[typeName];
}
}
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
private readonly ILifetimeScope lifetimeScope;
private readonly SwapDealContext context;
public UnitOfWorkFactory(
SwapDealContext context,
ILifetimeScope lifetimeScope)
{
this.context = context;
this.lifetimeScope = lifetimeScope;
}
public IUnitOfWork CreateUnitOfWork()
{
return new UnitOfWork(this.context, this.lifetimeScope);
}
}
Service:
public async Task<IList<UserDetails>> GetAllUsers()
{
using (var uow = this.unitOfWorkFactory.CreateUnitOfWork())
{
var userRepo = uow.GetRepository<IUserRepository>();
var result = await userRepo.GetAllUsers();
return Mapper.Map<List<UserDetails>>(result);
}
}
Controller
public class UserController : ControllerBase
{
private readonly IUserService userService;
private readonly ILogger logger;
public UserController(IUserService userService, ILogger logger)
{
this.userService = userService;
this.logger = logger;
}
[HttpGet]
[Route("users")]
[ProducesResponseType(typeof(IList<UserDetails>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[AllowAnonymous]
public async Task<IActionResult> GetUsersAnync()
{
try
{
var users = await userService.GetAllUsers();
var users2 = await userService.GetAllUsers();
if (!users.Any())
{
return NotFound($"No one user were found");
}
return Ok(users);
}
catch (Exception ex)
{
logger.ErrorFormat("Could not get users due to: {0}", ex, ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
}
StackTrace:
at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies.get_QueryProvider()
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ParameterExtractingExpressionVisitor..ctor(IEvaluatableExpressionFilter evaluatableExpressionFilter, IParameterValues parameterValues, IDiagnosticsLogger`1 logger, DbContext context, Boolean parameterize, Boolean generateContextAccessors)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryModelGenerator.ExtractParameters(IDiagnosticsLogger`1 logger, Expression query, IParameterValues parameterValues, Boolean parameterize, Boolean generateContextAccessors)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.System.Collections.Generic.IAsyncEnumerable<TResult>.GetEnumerator()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.IncludableQueryable`2.System.Collections.Generic.IAsyncEnumerable<TEntity>.GetEnumerator()
at System.Linq.AsyncEnumerable.Aggregate_[TSource,TAccumulate,TResult](IAsyncEnumerable`1 source, TAccumulate seed, Func`3 accumulator, Func`2 resultSelector, CancellationToken cancellationToken)
at SwapDeal.DataAccess.Repositories.UserRepository.GetAllUsers() in C:\Users\osmachenko\personal\VAO\SwapDeal.DataAccess\Repositories\UserRepository.cs:line 23
at SwapDeal.BizLogic.Services.UserService.GetAllUsers() in C:\Users\osmachenko\personal\VAO\SwapDeal.BizLogic\Services\UserService.cs:line 40
at SwapDeal.WebApi.Controllers.UserController.GetUsersAnync() in C:\Users\osmachenko\personal\VAO\SwapDeal.WebApi\Controllers\UserController.cs:line 37
So, when I call GetAllUsers method twice in Controller I have got System.ObjectDisposedException.
The reason you're seeing the context disposed is because you are disposing it.
If we trace through...
InstancePerLifetimeScope
. That means you'll get one per lifetime scope. In ASP.NET Core, that equates to one instance per request (basically).IUserService
in its controller. We don't see what the whole IUserService
looks like, but we do see GetAllUsers
in the example code.GetAllUsers
wraps the operation in a using
statement - it creates, and then disposes the unit of work when it's done.UnitOfWork
you pass in the DbContext
- the one instance you get that request, and then...UnitOfWork.Dispose
you dispose of the DbContext
. That happens at the end of GetAllUsers
because of that using
statement. public async Task<IList<UserDetails>> GetAllUsers()
{
// The factory creates the unit of work here...
using (var uow = this.unitOfWorkFactory.CreateUnitOfWork())
{
var userRepo = uow.GetRepository<IUserRepository>();
var result = await userRepo.GetAllUsers();
return Mapper.Map<List<UserDetails>>(result);
// At the end of this using statement, the unit of work gets disposed
// and in UnitOfWork.Dispose() you dispose of the DbContext.
}
}
If you want to call the operation twice, you either need to: