How to get visibility to inserted records where DbContext is not saved yet

111 views Asked by At

I am implementing a service layer where I need to make sure that certain number of operations across multiple tables happen in a transaction. Here is the work flow.

  1. I get an instance of HistoricalData object that need to be stored in HistoricalData table. This is accomplished in AddHistoricalData method.
  2. I need to retrieve all records from HistoricalData table that include what’s inserted in #1 but can have more records. This is done in ProcessAllData method.
  3. After processing all those records, the results are stored in two other tables, ProcessStatus and ProcessResults. If anything goes wrong, I need to rollback transaction include what’s inserted in operation #1.

This is how its implemented.

public class HistoricalDataService : IHistoricalDataService
    {
        private MyDbContext dbContext;
        public HistoricalDataService(MyDbContext context)
        {
            this.dbContext = context;
        }

        void AddHistoricalData(HistoricalData hData)
        {
            // insert into HistoricalData table
        }

        void ProcessAllData()
        {
            // Here we process all records from HistoricalData table insert porcessing results into two other tables
        }

        void SaveData()
        {
            this.dbContext.SaveChanges();
        }
    }

Here is how this class methods are been called.

HistoricalDataService service = new HistoricalDataService (dbcontext);
service.AddHistoricalData(HistoricalData instance);
service.ProcessAllData();
service.SaveData();

Problem with this approach is that whatever is inserted in HistoricalData table during call to AddHistoricalData method is not visible in the ProcessAllData call, because dbContext.SaveChanges is called only at the end. I am thinking that I need to somehow bring transaction scope in here but not sure how to expose a function where that transaction scope should be started?

2

There are 2 answers

0
Frozenthia On BEST ANSWER

There are different ways you can do this. Try this (untested, but POC)

public class HistoricalDataService : IHistoricalDataService
{
    private DbContext dbContext;
    private DbContextTransaction dbContextTransaction;

    public HistoricalDataService(DbContext context)
    {
        this.dbContext = context;
    }

    void AddHistoricalData(HistoricalData hData)
    {
        if (dbContext.Database.CurrentTransaction == null)
        {
            dbContextTransaction = dbContext.Database.BeginTransaction();
        }

        // insert into HistoricalData table
        dbContext.SaveChanges();
    }

    void ProcessAllData()
    {
        // Here we process all records from HistoricalData table insert porcessing results into two other tables
    }

    void Rollback()
    {
        if (dbContextTransaction != null)
        {
            this.dbContextTransaction.Rollback();
        }
    }

    void SaveData()
    {
        this.dbContextTransaction.Commit();
        this.dbContextTransaction.Dispose();
    }
}

Use as such:

HistoricalDataService service = new HistoricalDataService (dbcontext);

try 
{
     service.AddHistoricalData(HistoricalData instance);
     service.ProcessAllData();
     service.SaveData();
}
catch (Exception ex)
{
     service.Rollback();
}
0
Judy007 On

I would recommend refactoring the code so that the service has a single method (at a higher level of abstraction) and a single responsibility: handling the use case.

then, the client class would have

private readonly IHistoricalDataService _historicalDataService;

_historicalDataService.RearrangeSeatingArrangement(); //High level abstraction

the purpose of doing the above is to ensure your service class has all the operations occur within a single method, wrapped with either a transaction scope if using raw ADO.NET or context object if using EF. Dont have the client class call three methods when it could just call ONE. This is the purpose of the service class in the first place: Handle use cases and return response to client class ( perhaps controller in your case).

Now, when it comes to ensuring that some part of your code is aware of data that has been persisted, it brings up some additional questions: The commands that occur during ProcessAllData() above, why does it split the data exactly? Can that data that is split be split in memory ( in another domain class), added to the context, and saved in the SaveChanges() method? This would ensure you only make one call to the database ( which is the purpose of Entity Framework Unit Of Work. That is: accumulate changes in the context, adds, delete, udpates, then in one operation, talk with database).