Wanting to get into .NET Core, I created a WEB API that takes a file upload and then saves the transactions in the file into a DB table. I'm using .NET Core 2 with Entity Framework Core. I created my context using the example from here.
My problem is that I get the error "System.ObjectDisposedException Cannot access a disposed object" when it tries to save to the context object in my repository. It's a simple stack, so I'm hoping someone can help me out.
My container setup looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<SyncFinContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<ITransactionProcessor, TransactionProcessor>();
services.AddScoped<ITransactionRepository, TransactionRepository>();
}
My DBInitializer which I also got from the link above:
public static class DbInitializer
{
public static async Task Initialize(SyncFinContext context)
{
await context.Database.EnsureCreatedAsync();
// Look for any students.
if (context.Transactions.Any())
{
return; // DB has been seeded
}
var ts = new Transaction[]
{
// insert test data here
};
await context.SaveChangesAsync();
}
}
My DB Context:
public class SyncFinContext : DbContext
{
public SyncFinContext(DbContextOptions<SyncFinContext> options) : base(options)
{
}
public DbSet<Transaction> Transactions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Transaction>().ToTable("Transactions");
}
}
My Controller looks like this:
[Produces("application/json")]
public class TransactionController : Controller
{
ITransactionRepository _transactionRepository { get; set; }
ITransactionProcessor _transactionProcessor { get; set; }
public TransactionController(ITransactionRepository m, ITransactionProcessor p) : base()
{
_transactionRepository = m;
_transactionProcessor = p;
}
// POST: transaction/import
[HttpPost]
public async void Import(List<IFormFile> files)
{
if (files == null || files.Count == 0)
{
throw new FileNotFoundException("No file was received.");
}
// copy file to temp location so that it can be processed
var filepath = Path.GetTempFileName();
using (var stream = new FileStream(filepath, FileMode.Create))
{
await files[0].CopyToAsync(stream);
}
ImportTransactionRequest input = new ImportTransactionRequest
{
FilePath = filepath
};
var transactions = await _transactionProcessor.ReadDocument(filepath);
await _transactionRepository.AddBulk(transactions);
}
}
And my repository looks like this:
public class TransactionRepository : ITransactionRepository
{
// TODO: move the context
private SyncFinContext _context;
public TransactionRepository(SyncFinContext context)
{
_context = context;
}
public async Task AddBulk(List<Transaction> transactions)
{
foreach(var t in transactions)
{
await _context.Transactions.AddAsync(t);
}
_context.SaveChanges();
}
}
For full transparency, the transaction Processor just gets a list of rows from a csv:
public async Task<List<Transaction>> ReadDocument(string filepath)
{
try
{
var ret = new List<Transaction>();
var lines = await File.ReadAllLinesAsync(filepath);
foreach (var line in lines)
{
var parts = line.Split(',');
var tx = new Transaction
{
PostedDate = DateTime.Parse(parts[0]),
TransactionDate = DateTime.Parse(parts[1]),
Description = parts[2],
Deposit = ParseDecimal(parts[3]),
Withdrawal = ParseDecimal(parts[4]),
Balance = ParseDecimal(parts[5])
};
ret.Add(tx);
}
return ret;
}
catch(Exception e)
{
throw;
}
}
I've read where the whole stack must be async in order for the db context instance to be available, and, unless I'm doing it wrong, I seem to be doing that, as you can see above.
My expectations are that AddDbContext() will indeed properly scope the context to be available throughout the stack unless I explicitly dispose of it. I have not found anything to make me think otherwise.
I've tried hard-coding data in my DB Initializer also, as I read that may be a factor, but that does not solve the problem. Not sure what else to try. If someone can give me some ideas I would appreciate it.
The Import()
action method needs to have a return type of Task
. MVC will await execute an action method with a return type of Task
.
Also, probably best to get in the habit of returning an IActionResult
on your action methods. The task based equivalent is Task<IActionResult>
. This makes your controllers easier to test.
Since the AddBulk(List<Transaction> transactions)
method is public async Task
, the DbContext will be disposed if any part returns void (not awaited) at any point.
Try changing _context.SaveChanges();
To await _context.SaveChangesAsync();
This would ensure a Task
is being returned and not void.
https://stackoverflow.com/a/46308661/3062956
https://learn.microsoft.com/en-us/ef/core/saving/async
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With