Image for post
Image for post

A pattern for SaaS products in EF

Entity Framework is an object-relational mapper (ORM) that enables .NET developers to work with a database using .NET objects. It eliminates the need for most of the data-access code that developers usually need to write. In some contexts like SaaS products you may need to apply a specific filter for any operation that happens on the database level, and you don’t want to include that in all your repository layer functions, so in this article, I will discuss an easy way to implement that.

SaaS Product Scenario

In your SaaS product, users can create companies, each company has its own resources, and the user should not affect in any way the other companies' resources while he doesn’t have access to their companies. So usually what happens is that you will add where condition by company Id whenever you query any entity from the database, or whenever you update any entity. And also you should specify always the company id whenever you create any entity.

That means a lot of duplication and error-prone code. The best idea is to find a way to pass the company id one time, and be used automatically in any query.

How to implement this Pattern

Let’s say we have a database that only contains companies and users table where each company has multiple users, but when we log in to the system I should not have any access to the other companies users, of course as we said before, we can prevent that in the service layer, but when we have a large number of entities then we will need to repeat that in all repository layer functions or at least in the service layer.

Our pattern can start by making the context company id as a dependency to the DBContext like the next example:

So we will have some normal DbSet collections for neutral entities like Companies here in our example, logs, application settings, or entities that are be related to other entities with composite relation (Child entity cannot exist independently of the parent). For users I used FilteredDbSet instead of DbSet which is a new implementation of DbSet that will consider ContextCompanyId in any operation on the related entity as next:

I used a decorator pattern to add this functionality to the normal DbSet.

To know more about decorator pattern you can check the next link: https://www.dofactory.com/net/decorator-design-pattern

Please note that we are using a BaseEntity which should have a ContextCompanyId field in it, and any Entity that should be owned by a specific company should inherit from that BaseEntity.

So this FilteredDbSet is injecting additional where clause for any query applied on that entity. And when creating a new entity it's also setting the Entity ContextCompanyId.

So what left for you is to use dependency injection in your repository layer to inject an instance of DBContext with a context company id according to how you want that context company id to come from the front-end (Either through a token, a session, or cache map … etc).

Maybe you can use a controller filter or a middleware to decide the context company id, and then you can implement your own resolver to resolve the DbContext instance with that company id as a dependency and inject this DbContext as a dependency to your repository base class.

And while in repository pattern when you want to update or delete an entity usually you are finding the entity by id first then continue the update or delete operation using the DbContext, then that will solve our problem cause the user will not be able to find the Entity if it's not owned by the Context Company.

Please note that I did this solution on .NET framework and Entity Framework 6. I think that will work for other versions but you will need to check FilteredDbSet implementation.

I hope I could explain that pattern in a good and clear way, however, I am always opened to questions if exists.

I am a Software Architect and AI engineer that have a great passion for integrating technology with businesses and human life.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store