This is Part 3 in a 3 part series:
- Add .NET Core DI and Config Goodness to AWS Lambda Functions
- IDesignTimeDbContextFactory and Dependency Injection: A Love Story
- Use EF Core with AWS Lambda Functions (this post)
In a previous post I demonstrated how to set up Dependency Injection and Configuration for AWS Lambda Functions that are written in C# and use .NET Core. The purpose of this post is to provide an example and some best practices for using Entity Framework Core with AWS Lambda.
Note: You can download or clone the code for this post here: https://github.com/tonysneed/net-core-lambda-di-config-ef
One of the benefits of adding DI and Config to a Lambda function is that you can abstract away persistence concerns from your application, allowing you greater flexibility in determining which concrete implementation to use. For example, you may start out with a relational data store (such as SQL Server, PostgreSQL or SQLite), but decide later to move to a NoSQL database or other non-relational store, such as Amazon S3. This is where the Repository Pattern comes in.
Note: If you’re going to get serious about using Repository and Unit of Work patterns, you should use a framework dedicated to this purpose, such as URF (Unit of Work and Repository Framework): https://github.com/urfnet/URF.Core
A Simple Example
Let’s start with a simple example, a Products repository. You would begin by defining an interface, for example,
IProductRepository. Notice in the code below that the
GetProduct method returns a
Task. This is so that IO-bound operations can execute without blocking the calling thread.
You’re going to want to place this interface in a .NET Standard class library so that it can be referenced separately from specific implementations. Then create a
ProductRepository class that implements
IProductRepository. This can go in a .NET Standard class library that includes a package reference to an EF Core provider, for example, Microsoft.EntityFrameworkCore.SqlServer. You will want to add a constructor that accepts a
At this point you’re going to want to add a .NET Standard class library that can be used for dependency resolution. This is where you’ll add code that sets up DI and registers services that are used by classes in your application, including the
DbContext that is used by your
There are parts of this class that are worthy of discussion. First, notice that the constructor accepts a delegate for registering services. This is so that classes using
DependencyResolver can pass it a method for adding other dependencies. This is important because the application will register dependencies that are of no interest to the EF Core CLI.
Another thing to point out is the code that sets the
CurrentDirectory of the
ConfigurationService. This is required in order to locate the appsettings.*.json files residing at the root of the main project.
Lastly, there is code that registers the
DbContext with the DI system. This calls an overload of
AddTransient that accepts an
IServiceProvider, which is used to get an instance of the
ConfigurationService that supplies a connection string to the
UseSqlServer method of the
DbContextOptionsBuilder. This code can appear somewhat obtuse if you’re not used to it, but the idea is to use the
IServiceProvider of the DI system to resolve services that are required for passing additional parameters to constructors.
To use DI with a Lambda function, simply add a constructor to the
Function class that creates a
DependencyResolver, passing a
ConfigureServices method that registers
IProductRepository with the DI system. The
FunctionHandler method can then use the repository to retrieve a product by id.
Notice the second constructor that accepts an
IProductRepository. This is to support unit tests that pass in a mock
IProductRepository. For example, here is a unit test that uses Moq to create a fake
IProductRepository. This allows for testing logic in the
FunctionHandler method without connecting to an actual database, which would make the test fragile and slow.
EF Core CLI
In a previous post I proposed some options for implementing an interface called
IDesignTimeDbContextFactory, which is used by the EF Core CLI for to create code migrations and apply them to a database.
This allows the EF Core tooling to retrieve a connection string from the appsettings.*.json file that corresponds to a specific environment (Development, Staging, Production, etc).
Here is a sample
DbContext factory that uses a
DependencyResolver to get a
DbContext from the DI system.
To set the environment, simply set the
ASPNETCORE_ENVIRONMENT environment variable.
Then run the
dotnet-ef commands to add a migration and create a database with a schema that mirrors entity definitions and their relationships. You’ll want to do this twice: once for the Development environment and again for Production.
Try It Out!
Once you have created the database, you can press F5 to launch the AWS.NET Mock Lambda Test Tool, which you can use to develop and debug your Lambda function locally. Simply enter a value of 1 for Function Input and click the Execute Function button.
You should see JSON for Product 1 from the database.
When you’re confident everything works locally, you can throw caution to the wind and upload your Lambda function to AWS.
Make sure that the
ASPNETCORE_ENVIRONMENT environment variable is set appropriately.
You can then bravely execute your deployed Lambda function.
One of the benefits of using C# for AWS Lambda functions is built-in support for Dependency Injection, which is a first-class citizen in .NET Core and should be as indispensable to developers as a Jedi’s light saber. The tricky part can be setting up DI so that it can be used both at runtime by the Lambda function and at development-time by the EF Core CLI. With the knowledge you now possess, you should have no trouble implementing a microservices architecture with serverless functions that are modular and extensible. Cheers!