Friday, 24 March 2023

Using Entity Framework Core with Existing Database

We can use an existing database with Entity Framework Core (in this example is a MSSQL Database and EF Core 7). Say we need to create a new app that is accessing data from an established database. First we need to generate models and context class from our database. Then our codes can interact with these generated classes.

First of all, install Microsoft.EntityFrameworkCore, Microsoft.EntityFrameworkCore.Tools and Microsoft.EntityFrameworkCore.SqlServer packages to our project.

Then run Scaffold-DbContext command to generate models and context class. I would like to structure my projects like this:

To have this, I need to use some flags when running the command:
Scaffold-DbContext "Server=[SERVER_NAME];Database=[DATABASE_NAME];TrustServerCertificate=True;Trusted_Connection=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir "[MY_APP_DIRECTORY]\StudentApp\Model\Entity" -Namespace Model.Entity -Context StudentAppContext -ContextDir . -ContextNamespace Repository -Tables Student,School -Force
In my case, I use an Active Directory account that can access my database, so I use 'Server=[SERVER_NAME];Database=[DATABASE_NAME];TrustServerCertificate=True;Trusted_Connection=True'.
The flags used here:
- OutputDir - folder location where the generated model classes will be put
- Namespace - the namespace of the generated model classes
- Context - the name of the context file to be generated
- ContextDir - folder location where the context file will be put. I use '.' for current directory (I run the command from Repository project).
- ContextNamespace - namespace of the context file
- Tables - specify all table names in the database that we want to map
- Force - useful when we want to add new model(s) to be generated or simply to generate the whole thing again if we made mistake

Later when we want to add another model(s) from different table(s), we run the same command again with the new table name(s) added. For example if we want to add Teacher and Subject models:
Scaffold-DbContext "Server=[SERVER_NAME];Database=[DATABASE_NAME];TrustServerCertificate=True;Trusted_Connection=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir "[MY_WORK_DIRECTORY]\StudentApp\Model\Entity" -Namespace Model.Entity -Context StudentAppContext -ContextDir . -ContextNamespace Repository -Tables Student,School,Teacher,Subject -Force

Tuesday, 14 March 2023

.NET Core Built In Dependency Injection

.NET has built in tool for dependency injection. 'Microsoft.Extensions.DependencyInjection' library has common features needed for IoC that is usually good enough for most applications. However if you need more advanced features, consider using other DI tools.

To use, first install 'Microsoft.Extensions.DependencyInjection' package in our project.

To register the interfaces and objects, use either AddTransient(), AddScoped() or AddSingleton() methods. Their lifetimes are:
  • AddTransient - new instance is created each time requested
  • AddScoped - new instance is created per client request/session
  • AddSingleton - created once only through application life, subsequent requests will access the same instance
To understand the lifetimes better, please check this article .

Then we use GetService() method to get an instance.

An example of how to use the tool in a unit test:
[TestClass]
public class ExampleTest
{
    private readonly IStudentRepository studentRepository;

    public ExampleTest()
    {
        var services = new ServiceCollection();
        services.AddTransient<IStudentRepository, StudentRepository>();

        var serviceProvider = services.BuildServiceProvider();

        studentRepository = serviceProvider.GetService<IStudentRepository>();
		
		. . .
    }
}

To use it in different projects, we can create an extension method of IServiceCollection in the particular project. For example, in service project:
public static class IServiceCollectionExtension
{
	public static IServiceCollection AddServicesConfiguration(this IServiceCollection services)
	{
		services.AddTransient<IStudentService, StudentService>();
		return services;
	}
}
and in repository project:
public static class IServiceCollectionExtension
{
	public static IServiceCollection AddRepositoriesConfiguration(this IServiceCollection services)
	{
		services.AddTransient<IStudentRepository, StudentRepository>();
		return services;
	}
}
Some people like to do this approach but then a project that wants to use the extension method(s) needs to directly reference the other project(s). For example; a web project will need to reference service and repository projects.

Personally, I prefer to create a bootstrap project that contains all DI mappings and has references to all projects needed. Then a project just need to reference the bootstrap project.

In this example, the WebAPI project just need to reference Bootstrap project, not Service and Repository projects.

All DI configurations are in one place:
public static class BootstrapConfig
{
	public static IServiceCollection RegisterRepositories(this IServiceCollection services)
	{
		services.AddTransient<IStudentRepository, StudentRepository>();
		return services;
	}

	public static IServiceCollection RegisterServices(this IServiceCollection services)
	{
		services.AddTransient<IStudentService, StudentService>();
		return services;
	}
}