DbContextScope icon indicating copy to clipboard operation
DbContextScope copied to clipboard

transactions

Open martinRocks opened this issue 10 years ago • 5 comments

I'm trying to use your code with a CreateWithTransaction context scope, but I don't know how to make it work. Can you provide an example?

martinRocks avatar Feb 02 '15 16:02 martinRocks

Have you checked out the DemoApplication in the repo? It's got working examples of most of the features of DbContextScope, including using explicit database transactions.

If what you're trying to do isn't covered there, can you provide more details on what's not working for you? Some code would help.

mehdime avatar Feb 02 '15 22:02 mehdime

I looked at the demo app and didn't see any transaction examples. Where do you think they are?

Martin

On Mon, Feb 2, 2015 at 5:29 PM, Mehdi El Gueddari [email protected] wrote:

Have you checked out the DemoApplication in the repo? It's got working examples of most of the features of DbContextScope, including using explicit database transactions.

If what you're trying to do isn't covered there, can you provide more details on what's not working for you? Some code would help.

— Reply to this email directly or view it on GitHub https://github.com/mehdime/DbContextScope/issues/12#issuecomment-72552290 .

martinRocks avatar Feb 02 '15 22:02 martinRocks

I wanted to tell you more about what I was doing and maybe you could help me get to the right place. Below is the code that I am using. As you can see, the below code does an insert or update to 2 DbContexts, I want both to be in a single transaction. What I don't know how to do is do a transaction.commit. I thought it was contextScope.SaveChanges(), but that throws an error. Am I completely wrong as to how to do this?

using (var contextScope = _dbContextScopeFactory.CreateWithTransaction(IsolationLevel.Serializable)) { using (var dbContext = contextScope.DbContexts.Get<BudgetingModel>()) { var b = dbContext.Table1.SingleOrDefault(x => x.Id == model.id); //set some values

    dbContext.SaveChanges();
}

using (var dbContext = contextScope.DbContexts.Get<ExecutionModel>())
{
    var f = dbContext.Table2.SingleOrDefault(x => x.Id == model.id);
    //set some values

    dbContext.SaveChanges();
}
contextScope.SaveChanges();
return true;

}

Martin

On Mon, Feb 2, 2015 at 5:32 PM, Martin Mikhail [email protected] wrote:

I looked at the demo app and didn't see any transaction examples. Where do you think they are?

Martin

On Mon, Feb 2, 2015 at 5:29 PM, Mehdi El Gueddari < [email protected]> wrote:

Have you checked out the DemoApplication in the repo? It's got working examples of most of the features of DbContextScope, including using explicit database transactions.

If what you're trying to do isn't covered there, can you provide more details on what's not working for you? Some code would help.

— Reply to this email directly or view it on GitHub https://github.com/mehdime/DbContextScope/issues/12#issuecomment-72552290 .

martinRocks avatar Feb 03 '15 14:02 martinRocks

OK, I see.

There are two issues here.

The first one is that it's important to remember when using it that the purpose of DbContextScope is to manage the lifetime of the DbContext instances it contains. DbContextScope is also responsible for saving or rolling back all the changes made to the DbContext instances it manages.

In other words, when using DbContextScope, you need not to worry about managing your DbContext instances at all.

As a result, you must not dispose of the DbContext instances yourself nor call SaveChanges on them.

So your code should be rewritten like this:

public bool SomeMethod()
{
    using (var contextScope =
    _dbContextScopeFactory.CreateWithTransaction(IsolationLevel.Serializable))
    {
        var budgetDbContext = contextScope.DbContexts.Get<BudgetingModel>());
        var b = budgetDbContext.Table1.SingleOrDefault(x => x.Id == model.id);
        //set some values

        var executionDbContext = contextScope.DbContexts.Get<ExecutionModel>());
        var f = executionDbContext.Table2.SingleOrDefault(x => x.Id == model.id);
        //set some values

        // Save changes in all DbContext instances managed by this DbContextScope
        contextScope.SaveChanges();
    }

    return true;
}

With this code, the changes made to both DbContext instances will be saved at the same time at the end of DbContextScope block.

However, the persistence of the changes made to both DbContext instances here will NOT be made using a single database transaction.

Instead, each DbContext instance will be provided with their own database transaction to persist their changes. Which means that there is a possibility that one of you DbContext instances will persist its changes successfully while the other won't.

In its current version, DbContextScope does not support persisting changes made in multiple DbContext instances atomically. If you need to do this, you must use wrap your business transaction in a TransactionScope.

So why isn't DbContextScope using the same database transaction to persist changes in all the DbContext instances it manages? Because those DbContext instances might be using different connections strings and connect to different databases / data servers. Which means that sharing the same DB connection and DB transaction across all DbContext instances might not be possible. Which means that in order to guarantee that DbContextScope.SaveChanges() is always atomic across all the DbContext instances it manages, DbContextScope would need to use a TransactionScope internally. Which means that the DB transactions its would use could automatically get promoted to distributed transactions depending on what DbContext instances it was asked to manage. Which means that anyone using DbContextScope would need to enable MSDTC on all their database clients and servers. A bit too much of an overhead.

I don't see this restriction as being a issue as different DbContext are typically used to model either different bounded contexts (if using DDD) or different databases. And a business transaction should typically not attempt to persist changes atomically across bounded contexts, let alone across databases. Eventual consistency is usually a much better tool for the job here.

That being said, the real world is not always amenable to best-practices when it comes to software design. So it's entirely possible that you would find yourself in a situation when you must persist changes across multiple bounded contexts / multiple databases atomically. In this case, you should wrap the whole business transaction in a TransactionScope. This would make it explicit to the maintainer that a distributed transaction is a possibility.

There are of course ways in which this scenario could be facilitated by DbContextScope. I could for example use a TransactionScope by default to persist changes across DbContext instances atomically but disable the automatic promotion to a distributed transaction. The net effect would be that DbContextScope.SaveChanges() would either save changes atomically across all DbContext instances or throw if doing so would require promoting the transaction to a distributed one. One could perhaps explicitly enable distributed transactions if needed.

Any comments / suggestions welcome.

mehdime avatar Feb 09 '15 09:02 mehdime

Thank you, that was very helpful. I now understand.

Martin

On Mon, Feb 9, 2015 at 4:32 AM, Mehdi El Gueddari [email protected] wrote:

OK, I see.

There are two issues here.

The first one is that it's important to remember when using it that the purpose of DbContextScope is to manage the lifetime of the DbContext instances it contains. DbContextScope is also responsible for saving or rolling back all the changes made to the DbContext instances it manages.

In other words, when using DbContextScope, you need not to worry about managing your DbContext instances at all.

As a result, you must not dispose of the DbContext instances yourself nor call SaveChanges on them.

So your code should be rewritten like this:

public bool SomeMethod() { using (var contextScope = _dbContextScopeFactory.CreateWithTransaction(IsolationLevel.Serializable)) { var budgetDbContext = contextScope.DbContexts.Get<BudgetingModel>()); var b = budgetDbContext.Table1.SingleOrDefault(x => x.Id == model.id); //set some values

    var executionDbContext = contextScope.DbContexts.Get<ExecutionModel>());
    var f = executionDbContext.Table2.SingleOrDefault(x => x.Id == model.id);
    //set some values

    // Save changes in all DbContext instances managed by this DbContextScope
    contextScope.SaveChanges();
}

return true;

}

With this code, the changes made to both DbContext instances will be saved at the same time at the end of DbContextScope block.

However, the persistence of the changes made to both DbContext instances here will NOT be made using a single database transaction.

Instead, each DbContext instance will be provided with their own database transaction to persist their changes. Which means that there is a possibility that one of you DbContext instances will persist its changes successfully while the other won't.

In its current version, DbContextScope does not support persisting changes made in multiple DbContext instances atomically. If you need to do this, you must use wrap your business transaction in a TransactionScope .

So why isn't DbContextScope using the same database transaction to persist changes in all the DbContext instances it manages? Because those DbContext instances might be using different connections strings and connect to different databases / data servers. Which means that sharing the same DB connection and DB transaction across all DbContext instances might not be possible. Which means that in order to guarantee that DbContextScope.SaveChanges() is always atomic across all the DbContext instances it manages, DbContextScope would need to use a TransactionScope internally. Which means that the DB transactions its would use could automatically get promoted to distributed transactions depending on what DbContext instances it was asked to manage. Which means that anyone using DbContextScope would need to enable MSDTC http://en.wikipedia.org/wiki/M%20icrosoft%20_Distributed_Transaction_Coordinator on all their database clients and servers. A bit too much of an overhead.

I don't see this restriction as being a issue as different DbContext are typically used to model either different bounded contexts (if using DDD) or different databases. And a business transaction should typically not attempt to persist changes atomically across bounded contexts, let alone across databases. Eventual consistency is usually a much better tool for the job here.

That being said, the real world is not always amenable to best-practices when it comes to software design. So it's entirely possible that you would find yourself in a situation when you must persist changes across multiple bounded contexts / multiple databases atomically. In this case, you should wrap the whole business transaction in a TransactionScope. This would make it explicit to the maintainer that a distributed transaction is a possibility.

There are of course ways in which this scenario could be facilitated by DbContextScope. I could for example use a TransactionScope by default to persist changes across DbContext instances atomically but disable the automatic promotion to a distributed transaction. The net effect would be that DbContextScope.SaveChanges() would either save changes atomically across all DbContext instances or throw if doing so would require promoting the transaction to a distributed one. One could perhaps explicitly enable distributed transactions if needed.

Any comments / suggestions welcome.

— Reply to this email directly or view it on GitHub https://github.com/mehdime/DbContextScope/issues/12#issuecomment-73479731 .

martinRocks avatar Feb 09 '15 14:02 martinRocks