Architecture of Business Layer – Calling multiple business methods in one HTTP request

Last Updated: July 31, 2020 | Created: April 25, 2015

In this article I explore the issues and problems of calling business logic in a web application. The aim is to supplement my last article,  Architecture of Business Layer working with Entity Framework, which looks at the design of the business layer by describing how a web application user interface or RESTful service might call the business logic to provide some useful function.

In this article I look at a specific issue around calling multiple business methods, some of which write to the database using Microsoft’s Entity Framework (EF) data access technology. My aim is to both show you what issues we need to consider and describe a solution that overcomes these issues. At the end I give your some further reading for the more advanced situations.

Calling business logic

In most web applications we have some data access methods and we have some business logic. Sometimes they are closely linked and sometimes they are separated. When building an application with EF as your database access technology  then your choices are constrained. You can go the pure Domain-Driven Design (DDD) route and beef up your EF classes, or you can separate your EF data Entity classes into a Data Layer and place your Business Logic in a separate Layer (in this article I use ‘Layer’ to mean a separate .NET assembly).

I don’t want to get into the whole argument about what is the base way, but I have chosen what is a fairly normal arrangement where I keep my business logic in a separate layer to my data classes.  This is not a pure DDD designer but I do design my Business Classes to it treat the database as far as possible as an in-memory collection. Because of this I architect my business logic in a certain way and use a private library, called GenericActions, for handling calls. (see Architecture of Business Layer working with Entity Framework article for more on this).

Everything was going fine until I had to call multiple business methods in sequence to achieve the result I needed. It is something I have just come across in a project I am working on and have developed a solution, so its a hot topic for me. This is the story of why I needed to do this, what the problems were and how I implemented my final solution.

Calling multiple business methods to achieve one business goal

I am working on a e-commerce site and I came across one business process that needed two parts: the first stage checked the data input by the user and wrote the top level data out to the database. The second stage then imported some files and used the primary keys of the data written by the first stage as a reference name for the files. That meant that the first stage needed to complete and EF .SaveChanges() needed to be called so that the primary keys are in place before the second stage started.

Couldn’t I combine the two stages into one business method? Well, if you read my other article on Business Layer Architecture you will know that I don’t let me business logic call .SaveChanges(). I could break my own rule, but I have found this level of isolation really useful (read the other article for the full story). Anyway, even if I did that I still have the problem that that if the second half failed then I would be left with a bad, half completed database entry.

Now there are other ways round this particular issue (like use a GUID), but the idea of having to run multiple business methods in series is one that I thought might happen. When the first case occurred I hand-coded a solution, but when I came across another case it was time to create a proper solution.

Solution design

I implemented a solution inside my GenericActions library. While this is private I can outline my approach so you can implement your own version if you need to.

Because my solution uses EFs transactions control, I called the key internal class ‘Transactional Runner’, referred to as TR from now on. The primary feature of TR is of course EF’s V6 transaction control methods. When running multiple business methods GenericServices creates a DbContextTransaction using the context.Database.BeginTransaction() method. This means that all writing to the database is controlled and not committed until the DbContextTransaction.Commit() method is called. If a problem is found than the DbContextTransaction.Rollback() method can be called and all database writes done within the transaction scope are undone.

The diagram below shows it running three business methods in turn. The first two write to the database, but the third fails. The runner then rolls back the changes using EF Rollback(). If they had all succeeded then it would have called EF’s Commit() to make the changes permanent and available to other application/threads.


The simplified code for this would be:

using(var context = new MyDbContext())
    using (var dbContextTransaction = context.Database.BeginTransaction())
            var biz1Result = CallBizMethod1(input);
            var biz2Result = CallBizMethod1(biz1Result);
            var biz3Result = CallBizMethod1(biz2Result);
            return biz3Result;
        catch (Exception)

If you look at the code above you see that I pass data between each business method. This allows the previous method to pass information onto the next, and so on. If you think back to my e-commerce problem that started it off then the first business method creates the database entities, which are written to the database. However the second method needs to know which entity to use, which is why the first method passes on the class, with its newly created primary key, to the second class.

As you would expect my generic solution is quite a bit more complicated than that, but the code above should give you the idea. If you are writing a solution you might like to consider that some business methods are really simple, with no database writing and maybe just a few lines of code, while some do a lot, with async writes to the database and maybe async outside requests. It is worth a bit of thought to optimise for each type. This leads to two things I do.

  • I have normal synchronous TR and also an async TR, which is what I mainly use. However I have written the Async TR version so that it can take a mix of sync and async business methods and only runs the async ones in async mode. This means small, code-only business methods are not burdened with unnecessary Tasking which makes writing/debugging them simpler and they run more quickly.
  • Not all of my business methods write to the database, so I have an attribute on the business classes that access the database and only call SaveChanges() on those methods.

Warning: use of DbContext needs careful thought

In looking at this I had to think long and hard if my final solution would work properly in a web application. It will, but only because of specific constraints I have placed on my libraries. It is worth spelling out why this is so that you understand the issues if you come to write your own version.

The sample code I gave you will work fine in all cases, but in the real world you would not normally create the DbContext in that way. I use Autofac Dependency Injection to create my DbContext and it has an .InstancePerLifetimeScope() on it. This means that a single instance of DbContext is created for each HTTP request. This is important for the following reasons:

  • If two business methods look at the same DbContext then any changes done by one method will be seen by the second method. If they each had there own DbContext then, depending on when the DbContexts accessed the database, they might be out of step.
  • If a method writes to the database, but then fails and .SaveChanges is not called then the writes are discarded before the next HTTP request. Because I control when .SaveChanges() is called by using either my open-source GenericService (database access) or private GenericActions (business logic calling) then I know a stray .SaveChanges() will not happen once a method has returned an error. This means any data written to the database from a failed method will not be persisted.

However in another application I ran a long running task, and then I have to create a separate DbContext. Why was that? The primary reason is DbContext is not Thread Safe, so you would get an error if you used the same instance in multiple threads. Getting an error is a good thing because it would cause all sorts of problems elsewhere if it didn’t.

So, if I am running multiple methods, some of which are async, so why does my solution work with a single instance of DbContext? It is because they all run in the same thread. Async simply releases the thread when busy – it does not run things in parallel, so all is fine.

I was recommended a great article called ‘Managing DbContext the right way with Entity Framework 6: an in-depth guide‘ on this whole subject by one of my readers, khalil. I found this very helpful and did consider using his library. However I am clear that in my current design the limits I have placed on my libraries , for now, everything will work reliably. Maybe I will have to look at this later if I need to extend to parallel running, but that is a fight for another day.


I have shown that there are cases where multiple business logic, one or more of which write the database, need to be run is series. I have then described an approach using Entity Framework where you can ensure that the database is only updated if the whole series of business methods finished successfully. While the solution is fairly simple there are both performance and tasking issues to consider.

Hopefully this article will have given you some pointers on what the problem is, what sort of solution you could use and what to watch out for. If anything is unclear then please leave a comment and I will try and I will try and improve the information.

Happy coding!

0 0 votes
Article Rating
Notify of
Oldest Most Voted
Inline Feedbacks
View all comments
Piotr Bielecki
Piotr Bielecki
6 years ago

Hello Jon,

Thank you for this nice article. Such a pitty that there is no example to download (or I couldn’t find :))
Just want to inform you that first link in article doesn’t work, because it redirects to admin panel.

“In this article I explore the issues and problems of calling business logic in a web application. The aim is to supplement my last article, ****Architecture of Business Layer working with Entity Framework, ***** “

Jon P Smith
6 years ago
Reply to  Piotr Bielecki

Hi Piotr,

Sorry about that – I have fixed the link. Glad you liked the article. There are other articles about business logic in this site. Now that the link has been fixed you should be able to find them more easily.

JD Buck Savage
JD Buck Savage
8 years ago


Did you end up giving any thought to how GenericServices could/would play nicely with the work that Mehdi did in DbContextScope? I find myself tackling it right now and although achievable with working with the source I was hoping to maintain the usage of the package. The reason I am pursuing this is the reusability of the business layer in more than one solution, one of which will include worker roles consuming queues and running in parallel. Mehdi’s approach solves this issue but *how* it obtains the context is at odds with the DI approach of GenericServices.



Jon Smith
8 years ago
Reply to  JD Buck Savage

Hi Jim,

Ok, lots of parts to your question, so I will answer in chunks.

Firstly I have run separate worker roles and handled this by have separate instances of DbContext. As Mehdi points out DbContext is not thread-safe so you have to do that anyway. His solution doesn’t really help here apart from it works out if you are in a different Thread and generates a new DbContext (I think – its complicated so I might be wrong).

Therefore the question is – what with Mehdi’s approach give you that just creating a DbContext wouldn’t do for you? Maybe I am missing something but If you are injecting the DbContext and can differentiate between Threads/Worker Tasks then I can’t see Mehdi’s approach would help much. Am I missing something?

Mehdi’s approach is very good if you have one task that can do random writes, i.e. called to .SaveChanges(), inside itself. I have build GenericServices and GenericActions so that only one place is allowed to do that (other that the clever use of transactions that this article describes, and even then only the GenericActions library calls .SaveChanges().

Your question about handling multiple DbContext’s is more interesting. I have planned to change GenericServices to have a signature that allows the type of the DbContext to be set, thus causing the injection of the appropriate DbContext for a usage of GenericServices. I know how to do this, but it needs a very large refactoring of the code so I have put it off as I am quite busy.

Finally a comment that I find I don’t use GenericService in worker tasks. GenericService is really about shaping data for the front-end, either web pages or API. For small worker tasks I use direct EF calls. For larger ones I might use GenericActions (which isn’t open-source – sorry).

I hope that helps, and come back to me if anything I have said is wrong/silly – its a big topic so any extra input is welcome.

Jon Smith
8 years ago
Reply to  Jon Smith

Hi Jim,

I went to the gym and thought of a another thing that might help you.

GenericServices doesn’t use, nor know about, Dependency Injection (DI). Sure, all my examples use DI, but that is because it’s my default way of injecting services in MVC actions. GenericServices just expects something which conforms to the interface IGenericServicesDbContext.

This means you could simply wrap a call to a GenericService class, say IListService, and deliver any DbContext that has IGenericServicesDbContext added to it (IGenericServicesDbContext is very simple so all DbContexts will fit). This means you could:

1. Have a different wrapper for each context, e.g. IListServiceContextA, IListServiceContextB. For a few lines of code that solves your multiple DbContexts issue.
2. Supply a DbContext provided by Mehdi’s approach, or a transactional approach. That allows you to mix some of your own logic alongside GeneriServices (I don’t recommend that, but as you haven’t got GenericActions it might be a solution).

I hope that makes sense. Might solve your immediate problem quickly. If this isn’t clear then drop me another line.