DbContext in ASP.NET Core throws System.ObjectDisposedException - c#

I am saving a Todo model to database using EF Core in ASP.NET Core app.
But when I try to print the success message using Console.Writeline it throws System.ObjectDisposedException
Here's full application github code link
Here's ToDo model:
public class Todo
{
public int Id { get; set; }
[Required]
[MaxLength(10)]
public string Title { get; set; }
public bool IsCompleted { get; set; } = false;
}
Here's the business logic that throws exception.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ToDoApp.Models;
using ToDoApp.Data;
namespace ToDoApp
{
public class ToDoService
{
readonly ToDoContext _context;
public ToDoService(ToDoContext context)
{
_context = context;
}
public async Task<int> CreateToDo(CreateToDoBindingModel input)
{
Todo todo = new()
{
Title = input.Title,
IsCompleted = input.IsCompleted
};
_context.Add(todo);
await _context.SaveChangesAsync();
Console.WriteLine("Successfully saved todo.");
return task.Id;
}
}
}
Exception Details:
System.ObjectDisposedException
HResult=0x80131622
Message=Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'ToDoContext'.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
Why does Console.Writeline throws exception?
Edit:
Here's ToDoContext:
public class ToDoContext: DbContext
{
public ToDoContext(DbContextOptions<ToDoContext> options)
: base(options)
{ }
public DbSet<Todo> Todos { get; set; }
}
Here's how it is registered in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
var connectionString = Configuration["ConnectionString"];
services.AddDbContext<ToDoContext>(options => options.UseNpgsql(connectionString));
services.AddScoped<ToDoService>();
}
Here's full application github code link

Problem is in this part of your code:
public IActionResult OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
Task<int> id = _service.CreateTask(Input);//<--- this line
return RedirectToPage("Index");
}
After call createTask that returns a task means not complete yet and you don't wait for it to be completed, When call RedirectToPage current request finished. because your service and dbcontext added to DI with Scope lifetime that means the life of object is during the request and when request finished that object will disposed.
So for solve problem just use this way:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
int id = await _service.CreateTask(Input);
return RedirectToPage("Index");
}

Related

Is it possible to clear a save-process (EF/AutoMappIng)?

I'm using EF/Automapping. An error occurs with a Foreign Key. With a try...catch, I want to catch the error. And I want to go further by inserting / updating data.
But I have the idea the process stays in the same exception. It looks the same as LINQ-to-SQL. That the SaveChanges-method try to save all updates who are waiting in the stack for saving.
Is it possible to clear a save-process?
The Insert-method in the LogMessagesService
public int Insert(LogBerichtDto LogBerichtDto)
{
LogBericht entity = _mapper.Map<LogBerichtDto, LogBericht>(LogBerichtDto);
_logBerichtRepository.Insert(entity);
_logBerichtRepository.Save();
return entity.Id;
}
In GenericRepository
public void Save()
{
_context.SaveChanges();
}
When I have time work on my own application, I can find out what's the problem? The were several problems. But the main problem I think the not saved changes stays in cache.
And the remark of Gert Arnold is the way I solved it.
This is my code of the GenericRepository. Not an Injection of the context anymore. And create new when needed. You can shoot. But the Import process is not hanging anymore.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace MyData.Repository
{
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private MyDbContext _context;
private DbSet<T> table = null;
public GenericRepository()
{
_context = GetNew();
}
public DbContext Current
{
get { return _context; }
}
public virtual void Reset()
{
_context.Dispose();
_context = GetNew();
}
public MyDbContext GetNew()
{
MyDbContext context = new MyDbContext();
table = context.Set<T>();
return context;
}
public IQueryable<T> Where(Expression<Func<T, bool>> filter)
{
return table.Where(filter);
}
public IEnumerable<T> GetAll()
{
return table.ToList();
}
public async Task<T> GetById(object id)
{
return await table.FindAsync(id);
}
public async Task Insert(T obj)
{
await table.AddAsync(obj);
}
public async Task Update(int id, T obj)
{
var entry = await table.FindAsync(id);
_context.Entry(entry).CurrentValues.SetValues(obj);
}
public async Task Delete(int id)
{
T existing = await table.FindAsync(id);
table.Remove(existing);
}
public async Task Save()
{
try
{
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
Reset();
throw;
}
_context.ChangeTracker.Clear();
}
}
}

How to fix DbContext has been disposed for an async scenario?

We have upgraded our application from NserviceBus v5 to v6 and after that we run into major problem, most of the time receiving the following error.
The operation cannot be completed because the DbContext has been
disposed.
It was not obvious until we got load to our system.
We are running with eight concurrent threads and by that we receive the above error.
public class EndpointInitializer
{
public void Initialize(IKernel container)
{
var endpointConfiguration = new EndpointConfiguration("MyEndpoint");
endpointConfiguration.UseContainer<NinjectBuilder>(
customizations => { customizations.ExistingKernel(container); });
//More settings...
}
}
.
public class MyMessageHandler : IHandleMessages<MyCommand>
{
private readonly IPersonRepository _personRepository;
public MyMessageHandler(IPersonRepository personRepository)
{
_personRepository = personRepository;
}
public async Task Handle(MyCommand message, IMessageHandlerContext context)
{
var person = await _personRepository.GetByIdentifierAsync(message.Identifier).ConfigureAwait(false);
//More code...
await _personRepository.UpdateAsync(person);
}
}
.
[Serializable]
public class MyCommand
{
public string Identifier { get; set; }
}
.
public class DependencyRegistrar
{
public IKernel Container { get; set; }
public void Create()
{
Container = new StandardKernel();
RegisterTypes(Container);
}
public void RegisterTypes(IKernel kernel)
{
kernel.Bind<IPersonRepository>().To<PersonRepository>();
kernel.Bind<DbContext>().To<MyDbContext>().InThreadScope();
//More registrations...
}
}
.
public class MyDbContext : DbContext
{
public MyDbContext() : base("MyConn")
{
}
}
.
public interface IPersonRepository
{
Task<Person> GetByIdentifierAsync(string identifier);
Task UpdateAsync(Person entity);
//More methods...
}
.
public class PersonRepository : IPersonRepository
{
private readonly DbContext _dbContext;
public PersonRepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<Person> GetByIdentifierAsync(string identifier)
{
var personList = await _dbContext.Set<Person>().Where(x => x.Identifier == identifier).ToListAsync().ConfigureAwait(false);
//More code...
return personList.SingleOrDefault();
}
public async Task UpdateAsync(Person entity)
{
//More code...
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
}
}
.
public class Person
{
public int Id { get; set; }
public string Identifier { get; set; }
//More properties...
}
One option that we noticed is working is to pick up DataContext using Particulars example to use UnitOfWorkSetupBehavior. But it does not fit that well in our scenario because we have a complicated setup with services and repositories injecting the DbContext in the constructor throughout our application.
Ie, the (partial) solution for now is to call the method on the repository like;
var person = await _personRepository.GetByIdentifierAsync(context.DataContext(), message.Identifier).ConfigureAwait(false);
But now, when we run inte more complicated scenarios this won¨t suffice.
So, what are we missing? What is really the issue here?
Ninject PerThreadScope uses System.Threading.Thread.CurrentThread. With async in place the thread can change potentially for every continuation (the code that follows an await statement). You can either use a custom named scope, an async local scope or use the InUnitOfWorkScope from NServiceBus.Ninject.

In-memory database doesn't save data

I have a simple web-app with angular on client-side and asp.net core web-api on server-side. I use InMemoryDatabase
services.AddDbContext<ItemsContext>(options => options.UseInMemoryDatabase("ItemsDB"));
to store data for the simplisity of the development. But I've encountered an issue with that. I have one controller on web-api to response for users' requests:
[Route("api/[controller]")]
public class ItemsController : Controller
{
private readonly IApiService apiService;
public ItemsController(IApiService apiService)//using DI from Startup.cs
{
this.apiService = apiService;
}
[HttpPost, Route("addItem")]
public async Task<Response> Add([FromBody]Item item)
{
return await apiService.Add(item);
}
[HttpDelete("{id}")]
public async Task<Response> Delete(int id)
{
return await apiService.Delete(id);
}
[HttpPut]
public async Task<Response> Put([FromBody]Item item)
{
return await apiService.Put(item);
}
}
and the following Startup.cs configurations:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ItemsContext>(options => options.UseInMemoryDatabase("ItemsDB"));
services.AddSingleton<IUnitOfWork, UnitOfWork>(provider => {
var context = services.BuildServiceProvider().GetService<ItemsContext>();
return new UnitOfWork(context);
});
services.AddSingleton<IApiService, ApiService>(provider => {
return new ApiService(services);
});
}
The problem is, that when I add new item, everything goes just fine...but then I post another request to delete this item it may show there there is no such an item at all or sometimes it may delete it...so in other words, the database exists and then disappears and I'm not sure when. Here is some additional code refering to the above
public class ApiService: IApiService
{
private readonly IUnitOfWork database;
private readonly IServiceProvider provider;
public ApiService(IServiceCollection serviceCollection)
{
provider = serviceCollection.BuildServiceProvider();
}
public IUnitOfWork Database
{
get
{
return provider.GetService<IUnitOfWork>();
}
}
public async Task<Response> Add(Item item)
{
Database.Items.Add(item);
await Database.SaveAsync();
var id = Database.Items.LastItem().Id;
return new Response() { Result = true, ItemId = id };
}
public async Task<Response> Delete(int id)
{
var item = await db.Items.Find(id);
Database.Items.Remove(item);
await Database.SaveAsync();
return new Response() { Result = true };
}
public async Task<Response> Put(Item item)
{
Database.Items.Update(item);
await Database.SaveAsync();
return new Response() { Result = true };
}
}
Update:
UnitOfWork Implementation:
public class UnitOfWork: IUnitOfWork
{
private readonly DbContext context;
private IRepository<Item> itemsRepository;
public UnitOfWork(DbContext dbContext)
{
context = dbContext;
}
public IRepository<Item> Items
{
get
{
return itemsRepository ?? (itemsRepository = new Repository<Item>(context));
}
}
public void Dispose()
{
context.Dispose();
}
public void Save()
{
context.SaveChanges();
}
public async Task SaveAsync()
{
await context.SaveChangesAsync();
}
}
Your code has multiple serious problems, let's go through them.
services.AddDbContext adds a Scoped service, meaning that instances will be created and disposed on each request. services.AddSingleton adds a Singleton service, so only a single instance will ever be created. You cannot add a scoped service to a singleton one, because the reference the singleton service uses will be disposed and you will end up with a disposed context.
This code:
return provider.GetService<IUnitOfWork>();
represents the service locator anti-pattern. As you can guess, an anti-pattern is something you want to avoid. I also don't know why you would want a service to build the entire DI container nor why you would want a service to have the responsibility of getting the dependencies it needs itself.
This part here is where your question actually comes from:
Database.SaveAsync();
You are calling an asynchronous function and not awaiting for it to finish. The task may finish or not, it may throw an error or not, you will never know what happened.
The best thing is that all of these could be avoided if people stopped attempting to create a Unit of Work + Repository pattern over yet another Unit of Work and Repository. Entity Framework Core already implements these:
DbContext => Unit of Work
DbSet => Repository (generic)
Why do you want yet another abstraction? Will you really ever throw away EF Core from the project to justify the maintenance cost of your code?
The entire question code could have just been this:
[Route("api/[controller]")]
public class ItemsController : Controller
{
private readonly YourContext _context;
public ItemsController(YourContext context)
{
_context = context;
}
[HttpPost]
public async Task<IActionResult> Add([FromBody]Item item)
{
context.Items.Add(item);
await context.SaveChangesAsync();
return Ok(item.Id);
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var item = await context.Items.FindAsync(id);
context.Items.Remove(item);
await context.SaveChangesAsync();
return Ok();
}
[HttpPut]
public async Task<IActionResult> Put([FromBody]Item item)
{
context.Items.Update(item);
await context.SaveChangesAsync();
return Ok();
}
}

How to fix 'A second operation started on this context before a previous operation completed...' when working with dependency injection?

when reading data from the database I get this error:
A second operation started on this context before a previous operation
completed. Any instance members are not guaranteed to be thread safe.
I have the following ApplicationContext.cs:
public class ApplicationContext : Microsoft.EntityFrameworkCore.DbContext
{
public ApplicationContext(DbContextOptions<ApplicationContext> options)
: base(options)
{ }
public DbSet<MyClass> MyClasses{ get; set; }
}
The following ApplicationContextFactory.cs
public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationContext>
{
public ApplicationContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<ApplicationContext>();
var connection = "myConnectionString";
builder.UseSqlServer(connection);
return new ApplicationContext(builder.Options);
}
}
The following ServiceLoader.cs (where I declare the DI):
public static class ServiceLoader
{
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IRepository, Repository>();
var connection = "myConnectionString";
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connection));
}
}
and finally, the following Repository, where the exception is thrown:
public class Repository : IRepository
{
private ApplicationContext _db;
public Repository (ApplicationContext db)
{
_db = db;
}
public List<MyClass> Get()
{
_db.MyClasses.ToList();
}
}
I have also tried to declare the Repository as Transient instead of Singleton, but a similar error is thrown
'An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
Any idea on how to fix this? Thanks!
In my case I found the following information helpful:
https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
And changed the lifetime scope of my Db Context to transient using the overloaded AddDbContext method in startup:
services.AddDbContext<MyAppDbContext>(options => {
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"));
}, ServiceLifetime.Transient);
You can wrap an async Task around your Get() function and await your results:
public async Task<List<MyClass>> Get()
{
return await _db.MyClasses.ToListAsync();
}
I wrote a solution, which uses a queue. It is still single threaded, but you can call it from different threads.
public class ThreadSafeDataContext
{
private Thread databaseThread;
private Queue<PendingQuery> pendingQueries = new Queue<PendingQuery>();
private DatabaseContext db = new DatabaseContext();
private bool running = true;
public ThreadSafeDataContext()
{
databaseThread = new Thread(new ThreadStart(DoWork));
databaseThread.Start();
}
public void StopService()
{
running = false;
}
private void DoWork()
{
while(running)
{
if (pendingQueries.Count > 0)
{
// Get and run query
PendingQuery query = pendingQueries.Dequeue();
query.result = query.action(db);
query.isFinished = true;
}
else
{
Thread.Sleep(1); // Waiting for queries
}
}
}
public T1 Query<T1>(Func<DatabaseContext, T1> action)
{
Func<DatabaseContext, object> a = (DatabaseContext db) => action(db);
PendingQuery query = new PendingQuery(a);
pendingQueries.Enqueue(query);
while (!query.isFinished) {
Thread.Sleep(1); // Wait until query is finished
}
return (T1)query.result;
}
}
class PendingQuery
{
public Func<DatabaseContext, object> action;
public bool isFinished;
public object result;
public PendingQuery(Func<DatabaseContext, object> action)
{
this.action = action;
}
}
Then you can just run a query from different threads by using:
TeamMembers teamMembers = threadSafeDb.Query((DatabaseContext c) => c.team.ToArray())

C# webapi async context issue

I got two console applications which calls my webapi the same time and I get back in the console application the follow response from my api:
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
So they call at the same time my webapi and then something inside the webapi cannot handle those 2 async calls so this error is returned.
I checked all my code on the webapi project and all methods are async and got await so I cannot see why I get this.
Here is the code of the webapi.
Controller:
public class FederationsController : ApiController
{
private readonly IFederationRepository _federationRepository;
public FederationsController(IFederationRepository federationRepository)
{
_federationRepository = federationRepository;
}
[HttpGet]
[Route("federations", Name = "GetFederations")]
public async Task<IHttpActionResult> GetFederations()
{
var federations = await _federationRepository.GetAllAsync();
return Ok(federations.ToModel());
}
}
Repository
public class FederationRepository : IFederationRepository, IDisposable
{
private Models.DataAccessLayer.CompetitionContext _db = new CompetitionContext();
#region IQueryable
private IQueryable<Models.Entities.Federation> FederationWithEntities()
{
return _db.Federations.Include(x => x.Clubs)
.Where(x => !x.DeletedAt.HasValue && x.Clubs.Any(y => !y.DeletedAt.HasValue));
}
#endregion IQueryable
public async Task<IEnumerable<Models.Entities.Federation>> GetAllAsync()
{
return await FederationWithEntities().ToListAsync();
}
}
Mapper
public static class FederationMapper
{
public static List<Federation> ToModel(this IEnumerable<Models.Entities.Federation> federations)
{
if (federations == null) return new List<Federation>();
return federations.Select(federation => federation.ToModel()).ToList();
}
public static Federation ToModel(this Models.Entities.Federation federation)
{
return new Federation()
{
Name = federation.Name,
FederationCode = federation.FederationCode,
CreatedAt = federation.CreatedAt,
UpdatedAt = federation.UpdatedAt
};
}
}
DbContext
public class CompetitionContext : DbContext
{
public CompetitionContext() : base("ContextName")
{
}
public DbSet<Federation> Federations { get; set; }
}
UnityConfig
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<IFederationRepository, FederationRepository>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
Thank you for all the advices/help.
In your repository you are creating a single CompetitionContext and reusing it. I'm assuming that IoC setup is registring the repository as some kind of single instance, so the same repository is getting used every time. If that's the case you should create a new CompetitionContext for each method call.
Also, probably should make sure it's closed with a using statement.
I'm also not clear from your code snippets why you are returning an IQueryable from that FederationWithEntities, method, do you have other things that are using it?
Anyway, I'd probably change that GetAllMethod to be something like this:
public async Task<IEnumerable<Models.Entities.Federation>> GetAllAsync()
{
using (Models.DataAccessLayer.CompetitionContext _db = new CompetitionContext())
{
return _db.Federations.Include(x => x.Clubs)
.Where(x => !x.DeletedAt.HasValue && x.Clubs.Any(y => !y.DeletedAt.HasValue))
.ToListAsync();
}
}

Categories

Resources