How to use dotnet-ef tools with DI instanciated DbContext? - c#

I'm using a DDD approach and added a persistence layer to my project. The layer publishes an extension method to IServiceCollection which initializes efcore. I only publish repositories instead of the DbContext itself.
IServiceCollection Extension
public static IServiceCollection AddPersistenceLayer(this IServiceCollection services, string connectionString)
{
// internal dependencies
services.AddDbContextFactory<AppDbContext>(
o => o.UseSqlServer(connectionString, p => p.EnableRetryOnFailure()));
services.AddDbContext<AppDbContext>(
o => o.UseSqlServer(connectionString, p => p.EnableRetryOnFailure()));
// public dependencies (consument of DbContext)
services.AddTransient<IBookRepository, BookRepository>();
return services;
}
DbContext
internal sealed class AppDbContext: DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
:base(options)
{
}
public DbSet<Book> Books { get; set; }
}
This works fine so far when using my app. But when I want to use the dotnet-ef tools the efcore wont be initialized because there is no startup project which calls the above extension method.
Is there a way to somehow work around this? I wish I just could pass the connectionstring from the CLI.
Migrations are also located in the persistence layer project if that matters.
What I've tried is to create a startup project which calls the extension method and then use the following command. But without success.
dotnet ef database update `
--project .\src\Persistence\Persistence.csproj `
--startup-project .\src\Persistence.Tools\Persistence.Tools.csproj `
0
I keep getting the error message
Unable to create an object of type 'AppDbContext'.

Maybe this example can help you in what you want to do:
https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/

Related

How do I use dll of .net 5 class library that contain in its class Dependency injection?

In my Asp.net Core 5 API Project
I have a serviceLayer that the controller uses, to get data from a third layer called dataLayer.
I want to use the service layer as a DLL in different projects.
This ServiceLayer Contain dependency Injections like that :
namespace ServiceLayer
{
public class UserService : IUserService
{
IUserRepository userRepository; // (From DataLayer)
public UserService(IUserRepository repository) : base(repository)
{
this.userRepository = repository;
}
public Users GetAllPersonsById(int id)
{
return userRepository.GetById(id);
}
}
public interface IUserService : IService<Users>
{
Users GetAllPersonsById(int id);
}
How can I use the method GetAllPersonsById with the DLL ServiceLayer
can I use it because the dependency Injections
As soon as you reference the DLL / project you can use all classes the same ways as if they were in the project.
To use a class as a service:
Provide the service
Inject the service
There's a lot of documentation available, so I'll keep this short:
// provide in startup.cs
services.AddTransient<IUserService, UserService>();
// Inject where you need it
MyConstructor(IUserService userService) {}
See https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0
Provide Extension Method
If we take a look at other libs, most of them provide a method to setup the services.
Example: Entity framework core
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(...));
}
So you could:
In your lib, create an extension method for IServicesCollection that adds all services of your lib.
In the consuming project, call services.AddMyLibServices().
This could look like so:
public static class ServicesConfiguration
{
public static void AddDataLayer(this IServiceCollection services)
{
services.AddTransient<IUserService, UserService>();
// ... same for all services of your lib
}
}
Here's a tutorial with more details:
https://dotnetcoretutorials.com/2017/01/24/servicecollection-extension-pattern/
Lamar service registries
An optional and alternative approach are service registries. It's very similar to the extension methods but uses a class to do the setup. See https://jasperfx.github.io/lamar/documentation/ioc/registration/registry-dsl/
Composition Root
You may want to read about the composition root pattern, e.g. What is a composition root in the context of dependency injection?
In a simple app, your startup.cs is your composition root. In more complex apps, you could create a separate project to have a single place to configure your apps services.
Create the DLL
There are two ways to create the DLL:
As a project in your solution (so your solution has multiple projects, each will result in a separate DLL)
As a separate solution and as nuget package

How to add DbContext In Startup.cs without using Entity Framework?

I am currently working on a project where I am developing a class library that later on will be uploaded as a nugget package, such that if a user creates a.NET Core application, she/he can download the nugget package and use it accordingly.
Essentially within the class library, Entity Framework, Nethereum and other packages are installed as dependencies. One of my goals is not to require users to add Entity Framework to their application (since the nugget package (, i.e. the class library I am building)) already has it installed. For that reason, there is a DbContext that accepts the database connection string in the class library and builds the options.
public class BEFDbContext: DbContext
{
public BEFDbContext(string connectionString) :
base(SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options) { }
public DbSet<ApplicationEvent> Events { get; set; }
}
Next, the user has to create another class in the application code that extends the BEFDbContext class found in the class library.
public class NewDatabaseContext: BEFDbContext
{
public NewDatabaseContext(string connectionString):base(connectionString){}
}
So far so good, however, at this point, I would like to 'initialise' the NewDatabaseContext class in the Startup.cs class. Generally, one would use Entity Framework and would add the code as such:
services.AddDbContextPool<NewDatabaseContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("defaultconnection"));
});
However, as I mentioned before One of the goals is to not require users/developers to add Entity Framework to the application (once again since we have it in the class library).
So, my question is How I can add the NewDatabaseContext class as DbCcontext in the Startup.cs without using Entity Framework?
Since you wanted the alternative response you can use Extension methods
in your library add the following code
public static class ServiceCollectionExtensions
{
public IServiceCollection AddApplicationDbContext<T>(this IServiceCollection services, IConfiguration configuration) where T : BEFDbContext
{
services.AddDbContextPool<T>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("defaultconnection"));
});
return services;
}
}
then in the startup of application you can use
public void ConfigureServices(IServiceCollection services)
{
...
services.AddApplicationDbContext<NewDatabaseContext>(Configuration);
...
}
You can have variations of this as per your need. Like accepting the connection string instead of the whole Configuration, etc.
This answer uses generics and extension methods. If you want more details then please checkout:
Generic methods: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/generic-methods
Extension Methods: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods

Entity framework migrations errors

I want to create a migration for existing entities.
I have a DataContext class
public class DataContext : DbContext
{
public DataContext()
{
}
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
public DbSet<AppUser> Users { get; set; }
}
And I added it to the services in IServiceCollection extension method -
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(DataContext).Assembly.FullName));
});
return services;
}
}
When I try to add a migration from Entity Framework command tool -
"dotnet ef migrations add InitialCreate" such error occurs:
No database provider has been configured for this DbContext. A
provider can be configured by overriding the 'DbContext.OnConfiguring'
method or by using 'AddDbContext' on the application service provider.
If 'AddDbContext' is used, then also ensure that your DbContext type
accepts a DbContextOptions object in its constructor and
passes it to the base constructor for DbContext.
Can't really understand what's wrong with my code. It actually works by overriding OnConfiguring method but I need a solution for this kind of approach. Any ideas?
Try to remove the default public constructor (the parameterless one).
When you execute the CLI command you have some additional options like --project and --startup-project. By specifying them it will enable the use of the host builder to create your DB context, and you don't need this parameterless constructor.
The ef cli is using reflection to look into your project and then executes the host builder setup in order to have a service builder. From there it will instantiate the DB context. If it doesn't work. It will fall back first to a design time factory. And finally will use the default constructor (which is happening in this case).

Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions'

I am trying to access the databse using EF Core 5.0 on an ASP.NET Core project. For the first migration, I overrode the OnConfiguring() method on the DBContext and updated the database successfully.
For the second migration, I decided to use the dependency injection in ASP.NET Core following the guidelines. Here are the changes I made.
Added services.AddDbContext in my Startup.cs.
Removed the OnConfiguring() method from DBContext.
After running dotnet ef migrations add Posts, I get following error:
Microsoft.EntityFrameworkCore.Design.OperationException:
Unable to create an object of type 'BlogContext'.
For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
If I add the --verbose flag, I get this output:
Build started...
dotnet build blog/app/app.csproj /verbosity:quiet /nologo
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:02.50
Build succeeded.
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider in assembly 'app'...
Finding Microsoft.Extensions.Hosting service provider...
No static method 'CreateHostBuilder(string[])' was found on class 'Program'.
No application service provider was found.
Finding DbContext classes in the project...
Found DbContext 'BlogContext'.
Microsoft.EntityFrameworkCore.Design.OperationException: Unable to create an object of type 'BlogContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
---> System.InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions`1[app.Data.BlogContext]' while attempting to activate 'app.Data.BlogContext'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetServiceOrCreateInstance(IServiceProvider provider, Type type)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.<>c__DisplayClass13_4.<FindContextTypes>b__13()
Unable to create an object of type 'BlogContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
However, the code works as expected when I run the web application, as in the BlogContext is created and injected into my classes by the DI layer, and I can access the database.
Hence, I am guessing the DI layer is not running as expected when running the dotnet ef migrations add command.
Here's my code.
// Program.cs
public class Program
{
public static void Main(string[] args)
{
IHostBuilder builder = Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
IHost host = builder.Build();
host.Run();
}
}
// BlogContext
using app.Models;
using Microsoft.EntityFrameworkCore;
namespace app.Data
{
public class BlogContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
public BlogContext(DbContextOptions<BlogContext> options) : base(options)
{
}
}
}
// Startup.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public string ConnectionString => Configuration.GetConnectionString("Blog");
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BlogContext>(opt => { opt.UseSqlite(ConnectionString); });
}
}
Both the Startup and BlogContext live in the same project.
In my experience, better to create a class (in the same project as the context) that implements IDesignTimeDbContextFactory - as described here. That way, there's not guessing as to which code will get used when using the design time tools such as update-database
Thanks to Ivan and Kirk's comments above and reading the entire verbose output, I figured out the problem. Turned out I was not following the correct pattern in Program.cs.
From the documentation,
The tools first try to obtain the service provider by invoking Program.CreateHostBuilder(), calling Build(), then accessing the Services property.
I had refactored the original Program.cs by moving CreateHostBuilder() inside main(), which broke the ef-core migration.
After modifying the Program.cs to following it works as expected.
public class Program
{
public static void Main(string[] args)
=> CreateHostBuilder(args).Build().Run();
// EF Core uses this method at design time to access the DbContext
public static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(
webBuilder => webBuilder.UseStartup<Startup>());
}

.NET Core 2.0 Modular Dependency Injection

I am trying to build a library that has core and extensions packages like Entity Framework and its database providers.
What I am trying to do is when I register that library with dependency injection, I want to give specific implementation as a parameter.
Think EF. In order to use sql provider on EF we need to register it with SQL provider passed as option parameter like the following.
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"]);
});
I would like to build similar structure. Lets say my framework will provide film producer. It will have producer.core package for framework related classes and two extensions package called Producer.Extensions.Hollywood and Producer.Extensions.Bollywood.
If I want to use Hollywood provider, I need to install core package and Hollywood extension package. On registration it should look like
services.AddFilmProducer(options =>
{
options.UseHollywoodProducer();
});
I could not find even a keyword that will point me a direction. I tried to read entity framework's source code but it is too complicated for my case.
Is there anyone who could point me a direction?
Thanks in advance.
I'm not sure if I completely understand your requirements, but DI and extensions are an easy thing in .net core.
Let's say you want this in your Startup.cs
services.AddFilmProducer(options =>
{
options.UseHollywoodProducer();
});
To implements this, create your library and add a static extension class
public static class FilmProducerServiceExtensions
{
public static IServiceCollection AddFilmProducer(this IServiceCollection services, Action<ProducerOptions> options)
{
// Create your delegate
var producerOptions = new ProducerOptions();
options(producerOptions);
// Do additional service initialization
return services;
}
}
where your ProducerOptions implementation might look like
public class ProducerOptions
{
public void UseHollywoodProducer()
{
// Initialize hollywood
}
public void UseBollywoodProducer()
{
// Initialize bollywood
}
}
If you wish to use the passed ProducerOptions in your service, there are two ways to do it. Either use dependency injection again, or directly access the service by using service provider in your extension method
var serviceProvider = services.BuildServiceProvider()
IYourService service = sp.GetService<IYourService>();
And now you have the original Use piece of initialization working.
Hope it helps.
Edit:
To clarify. To inject your options in the service, you can use
services.Configure(ProducerOptions);
in your extension method, and pass to your service constructor via
public YourService(IOptions<ProducerOptions>)
You can then simplify or complicate your options as much as you want.
A useful link for this kind of extensions might be the CORS repository for .net core: https://github.com/aspnet/CORS
Edit after comments:
I think I've got it now. You want packages to extend and implement specific options, kind of like what serilog does with different sinks. Piece of cake.
Scrap the ProducerOptions implementation.
Lets say you have a base package with initial empty structures (BaseProducer library) and an interface
public interface IProducerOptions
{
// base method signatures
}
Your service extension now becomes
public static class FilmProducerServiceExtensions
{
public static IServiceCollection AddFilmProducer(this IServiceCollection services, Action<IProducerOptions> options)
{
// Do additional service initialization
return services;
}
}
Now you create a new package with specific "Hollywood producer" options and you want to extend the base option set
public static class HollyWoodExtensions
{
public static void UseHollywoodProducer(this IProducerOptions options)
{
// Add implementation
}
}
Create as many packages and IProducerOptions extensions as you like, and the added methods will start appearing in your Startup.cs
services.AddFilmProducer(options =>
{
options.UseHollywoodProducer();
});

Categories