I'm writing a simple application that uses Asp.NET 6 And This method update the database schema in run time but my problem is when migration throw an exception transaction.Rollback() not work
what should I do to solve this problem
this is my method
public async Task Initialize()
{
using (var serviceScope = _scopeFactory.CreateScope())
{
using (var context = serviceScope.ServiceProvider.GetService<PMOracleDbContext>())
{
using ( var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
await context.Database.MigrateAsync();
dbContextTransaction.Commit();
}
catch (Exception e)
{
await dbContextTransaction.RollbackAsync();
Console.WriteLine(e);
throw;
}
}
}
}
}
and this my Program.cs
#region PmInitialDb
services.AddScoped<IPmDbInitializer, PmDbInitializerService>();
var dbInitializer = services.BuildServiceProvider().GetService<IPmDbInitializer>();
dbInitializer.Initialize();
dbInitializer.SeedData();
#endregion
and I use the OracelDb
Related
I am using transactionscope for a multiple DB context call. Something similar to this.
using (TransactionScope scope = new TransactionScope())
{
//Do something with context1
//Do something with context2
//Save and discard changes
context1.SaveChanges();
//Save and discard changes
context2.SaveChanges();
//if we get here things are looking good.
scope.Complete();
}
But in my case each context is created in a repository that is being called via this service. Each context is initialized via DI, so its scope is limited to the repository.
var empRepo;
var cityTravelRepo;
using (TransactionScope scope = new TransactionScope())
{
try
{
var empId = await empRepo.CreateEmp("Soul");
await cityTravelRepo.LogTravelling(listLocation, empId);
//if we get here things are looking good.
scope.Complete();
scope.Dispose();
}
catch (Exception ex)
{
scope.Dispose();
return BadRequest(ex.Message);
}
}
//empRepo
private readonly WritableMasterDbContext db;
public EmployeeRepository(WritableMasterDbContext db)
{
this.db = db;
}
public async Task<long> CreateEmp(string name)
{
if (!AlreadyExists(outlet.Id))
{
var emp = new Employee() { Name : name};
db.Employee.Add(emp);
await db.SaveChangesAsync();
return emp.Id;
}
return 0;
}
//cityTravelRepo
private readonly TransactionDbContext db;
public CityTravelRepository(TransactionDbContext db)
{
this.db = db;
}
public async Task LogTravelling(list<long> locationId, long empId)
{
foreach (var location in locationId)
{
var travelEvent = new TravelEvent();
travelEvent.locationId = location;
travelEvent.EmployeeId = empId;
travelEvent.CreatedAt = DateTime.UtcNow;
db.FieldEvents.Add(travelEvent);
}
await db.SaveChangesAsync();
}
For my case:
var empId = await empRepo.CreateEmp("Soul");
await cityTravelRepo.LogTravelling(listLocation, empId); //Some Error occures
scope.Complete();
}
catch (Exception ex)
{
scope.Dispose(); //This will dispose only LogTravelling, but not CreateEmp
return BadRequest(ex.Message);
}
Is it possible to dispose of the whole transaction?
Reason for using Transactionscope https://stackoverflow.com/a/542704/11519765
Reason for thinking only the second will be rolled back https://stackoverflow.com/a/35775480/11519765
I'm attempting to write a unit test for my repository class for its Create method that uses DbContext and the Add method.
My idea was to get a count of the number of existing records. Call the Create method. Get the new count of records and check that its incremented by 1.
However, when running the unit test, it errors when calling the Add method with the following error:
{"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.\r\nObject name: 'DbContext'."}
I'm trying to understand why is this and how to overcome this?
public class MyDatabaseContext : DbContext
{
public MyDatabaseContext(DbContextOptions<MyDatabaseContext> options) : base(options)
{
}
public DbSet<Record> Records { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Record>();
}
}
public class Repository : IRepository
{
private readonly MyDatabaseContext _dbContext;
public Repository(MyDatabaseContext dbContext)
{
_dbContext = dbContext;
}
public Record Create(Record record)
{
try
{
using (_dbContext)
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
}
catch (Exception ex)
{
return null;
}
}
public IEnumerable<Record> GetAll()
{
try
{
using (_dbContext)
{
return _dbContext.Records.ToList();
}
}
catch (Exception ex)
{
return null;
}
}
}
public interface IRepository
{
Record Create(Record record);
IEnumerable<Record> GetAll();
}
Startup.cs:
services.AddDbContext<MyDatabaseContext>(opt => opt.UseInMemoryDatabase("memoryDb"));
services.AddScoped<IRepository, Repository>();
Unit test:
[TestMethod]
public async Task Create_Successfully()
{
var repository = new Repository(await GetDbContext());
var existingRecords = repository.GetAll();
repository.Create(new Record());
var newRecords = repository.GetAll();
Assert.AreEqual(3, existingRecords.Count());
Assert.AreEqual(4, newRecords.Count());
}
private async Task<DbContext> GetDbContext()
{
var options = new DbContextOptionsBuilder<DbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
var context = new DbContext(options);
context.Database.EnsureCreated();
if (await context.Records.CountAsync() <= 0)
{
for (int i = 1; i <= 3; i++)
{
context.Records.Add(new Records());
await context.SaveChangesAsync();
}
}
return context;
}
You need to remove the using statements in the GetAll and Create methods:
public Record Create(Record record)
{
try
{
using (_dbContext)
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
}
catch (Exception ex)
{
return null;
}
}
To:
public Record Create(Record record)
{
try
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
catch (Exception ex)
{
return null;
}
}
You don't need to worry about disposing the service as the conatiner will do that for you in production. In your tests you can do this if you want to clean things up:
[TestMethod]
public async Task Create_Successfully()
{
using (var context = await GetDbContext())
{
var repository = new Repository(context);
var existingRecords = repository.GetAll();
repository.Create(new Record());
var newRecords = repository.GetAll();
Assert.AreEqual(3, existingRecords.Count());
Assert.AreEqual(4, newRecords.Count());
}
}
I have something similar to this:
public async Task Task1()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
//Code goes here
await OtherTask();
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
public async Task OtherTask()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
//Code goes here
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
The problem that I'm having is that when I call OtherTask() and it tries to do the BeginTransactionAsync(), it says that one transaction already exist. Is there anyway I can fix this? I want to be able to call multiple functions, that when executed alone can have their own transaction, but when being called from a function which already has a transaction, to use that instead.
The easiest way to fix this is to refactor the logic into a separate method:
public async Task Task1()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
//Code goes here
await OtherTask(transaction);
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
public async Task OtherTask(Transaction transaction)
{
//Code goes here
}
public async Task OtherTask()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
await OtherTask(transaction);
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
I am assuming you are using EntityFramework Core.
DbContext.Database has a property CurrentTransation that will be null if neither BeginTransactionAsync nor UseTransaction have been called before.
public async Task Task1()
{
await using var transaction = _context.Database.CurrentTransaction == null
? await _context.Database.BeginTransactionAsync()
: _context.Database.UseTransaction(_context.Database.CurrentTransaction);
try
{
//Code goes here
await OtherTask();
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
public async Task OtherTask()
{
await using var transaction = _context.Database.CurrentTransaction == null
? await _context.Database.BeginTransactionAsync()
: _context.Database.UseTransaction(_context.Database.CurrentTransaction);
try
{
//Code goes here
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
I have a wcf api and wish to wrap all requests inside a transaction
Currently my code looks like this in each endpoint
public MyCompleteList ReadOrganisations()
{
MyCompleteList resp = new MyCompleteList ();
try
{
using (TransactionScope scope = new TransactionScope())
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
DC_Base browser_request = new DC_Base(PROJECT);
browser_request.cmd_user_id = coreDb.GetUserIDFromLoginName(PROJECT,
HttpContext.Current.User.Identity.Name);
resp =
new MyCompleteList (coreSc.User_Read_All_Organisations(browser_request, utils,
validation, coreSc, coreDb));
scope.Complete();
}
else
{
resp.SetResponseNotLoggedIn();
}
}
}
catch (TransactionAbortedException ex)
{
resp.SetResponseServerError();
}
catch (ApplicationException ex)
{
resp.SetResponseServerError();
}
return resp;
}
As you can see if I am to use the "using" transaction scope part in every endpoint (approx 300) its going to be a lot of duplicated code.
is there anyway to reduce the amount of duplication?
You can write a helper method, that handles the transaction logic while calling your actual code as a lambda.
public static T Execute<T>(Func<T> func, TransactionExecutionOptions options = null)
{
options = options ?? TransactionExecutionOptions.Default;
T res;
using (var tx = new TransactionScope(options))
{
res = func();
tx.Complete();
}
return res;
}
Depending on your needs you can provide additional arguments to the Func argument; for example, the Execute method could also open a database connection and pass that to the func (then having Func<IDbConnection, T> as parameter type). YMMV.
For your example:
public MyCompleteList ReadOrganisations()
{
MyCompleteList resp = new MyCompleteList ();
try
{
resp = Execute(() => {
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
DC_Base browser_request = new DC_Base(PROJECT);
browser_request.cmd_user_id = coreDb.GetUserIDFromLoginName(PROJECT,
HttpContext.Current.User.Identity.Name);
resp =
new MyCompleteList (coreSc.User_Read_All_Organisations(browser_request, utils,
validation, coreSc, coreDb));
scope.Complete();
}
else
{
resp.SetResponseNotLoggedIn();
}
});
}
catch (TransactionAbortedException ex)
{
resp.SetResponseServerError();
}
catch (ApplicationException ex)
{
resp.SetResponseServerError();
}
return resp;
}
If possible, you can also factor the SetResponse*() methods out into a base class or interface (say IMyResponse), thus making it possible to handle this aspect inside the Execute method as well.
public static T Execute<T>(Func<T> func, TransactionExecutionOptions options = null) where T : IMyResponse
{
options = options ?? TransactionExecutionOptions.Default;
T res;
try
{
using (var tx = new TransactionScope(options))
{
res = func();
tx.Complete();
}
}
catch (TransactionAbortedException ex)
{
res.SetResponseServerError();
}
catch (ApplicationException ex)
{
res.SetResponseServerError();
}
return res;
}
1- Create a ServiceBase class as follows
public class ServiceBase
{
protected void ExecuteOperation(Action codetoExecute)
{
try
{
using (TransactionScope scope = new TransactionScope())
{
codetoExecute.Invoke();
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
// handle exception
}
catch (ApplicationException ex)
{
// handle exception
}
}
}
2- Each new service must inherits from ServiceBase and call ExecuteOperation instead. Code as follows:
ExecuteOperation(() =>
{
// Custom code here
});
3- Atomic transactions are useful when executing operations that dont expect results in return.
public async Task<ActionResult> Index()
{
var service = new CoreServiceFactory().GetImpersonatingService();
try
{
var data = new Impersonation()
{
ImpersonatingId = "dac733c3-01ad-447b-b0df-3a7c21fef90b",
UserId = "dac733c3-01ad-447b-b0df-3a7c21fef90b"
};
var imp = await service.Add(data);
}catch(Exception ex) { throw ex; }
return View();
}
Above is one of my controllers action method. And this works fine when the insertion is successful. This should fail if the data already exists in database(unique constraints). So when i intentionally try to make it fail(i manually add the same record in the db and then try to add it again via this action method) the action method goes into a loop or something, the exception is never thrown , chrome keeps me showing me the loading icon , looks like it went into some deadlock state. Can someone please help me understand why it goes into deadlock state when exception is thrown and how can i handle it?
Below are the reference methods
service.Add(data)
public async Task<Impersonation> Add(Impersonation t)
{
if (ValidateData(t))
{
using (var uow = GetUnitOfWork())
{
var r = GetRepository(uow);
var item = r.Add(t);
try
{
var ret = await uow.Save();
if (ret > 0)
{
return item;
}
else
{
return null;
}
}
catch (Exception ex)
{
throw ex;
}
}
}
else
{
throw new ValidationException(null, "error");
}
}
uow.Save()
public class BaseUnitOfWork : IUnitOfWork
{
public DbContext _Context { get; private set; }
public BaseUnitOfWork(DbContext context)
{
this._Context = context;
}
public async Task<int> Save()
{
try
{
var ret = await this._Context.SaveChangesAsync();
return ret;
}catch(Exception ex)
{
throw ex;
}
}
}
Here is my suggestion: in uow.Save, log the error in the catch block and return zero (do not throw any exceptions).
public class BaseUnitOfWork : IUnitOfWork
{
public DbContext _Context { get; private set; }
public BaseUnitOfWork(DbContext context)
{
this._Context = context;
}
public async Task<int> Save()
{
try
{
var ret = await this._Context.SaveChangesAsync();
return ret;
}catch(Exception ex)
{
// log the error here
return 0;
}
}
}
I'm not sure if returning the null in the Add service is a good idea or not, you might need to handle that differently too.