i created my own dbcontextfactory and now i don't know how to correctly register it in di. Can you somebody help me please? IApplicationDbContext is just interfaces with db sets.
I have register ma DbContext as pooled db context factory
builder.Services.AddPooledDbContextFactory<MyContext>(options =>
{
....
});
Interface of my db factory
interface IApplicationDbContextFactory
{
IApplicationDbContext CreateDbContext();
}
Implementation db factory
public class MyContextFactory<TContext> : IApplicationDbContextFactory where TContext : DbContext, IApplicationDbContext
{
private readonly IDbContextFactory<TContext> _dbContextFactory;
public MyContextFactory(IDbContextFactory<TContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
public IApplicationDbContext CreateDbContext()
{
return _dbContextFactory.CreateDbContext();
}
}
How can i correctly register my factory to di?
Thank you
Lifetime for the factory registered by AddPooledDbContextFactory is Singleton. Just register it with builder.Services.AddSingleton<IApplicationDbContextFactory, MyContextFactory<MyContext>>(); (though Scoped and Transient should also work just as fine):
var serviceCollection = new ServiceCollection();
serviceCollection.AddPooledDbContextFactory<SomeContext>(builder => builder.UseSqlite($"Filename={nameof(SomeContext)}.db"));
serviceCollection.AddSingleton<IApplicationDbContextFactory, MyContextFactory<SomeContext>>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var dbContextFactory = serviceProvider.GetRequiredService<IDbContextFactory<SomeContext>>();
using (var scope = serviceProvider.CreateScope())
{
var applicationDbContextFactory = serviceProvider.GetRequiredService<IApplicationDbContextFactory>();
var applicationDbContext = applicationDbContextFactory.CreateDbContext();
}
public class SomeContext : DbContext, IApplicationDbContext
{
public SomeContext(DbContextOptions<SomeContext> options) : base(options)
{
}
public DbSet<MyEntity> Entities { get; set; }
}
interface IApplicationDbContextFactory
{
IApplicationDbContext CreateDbContext();
}
public interface IApplicationDbContext
{
}
public class MyContextFactory<TContext> : IApplicationDbContextFactory where TContext : DbContext, IApplicationDbContext
{
private readonly IDbContextFactory<TContext> _dbContextFactory;
public MyContextFactory(IDbContextFactory<TContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
public IApplicationDbContext CreateDbContext()
{
return _dbContextFactory.CreateDbContext();
}
}
Related
I've created an injectable dbcontext
Startup.cs:
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddDbContext<DBContext>(options => options.UseSqlServer("Server=localhost;Database=mydb;Trusted_Connection=True;"));
}
UnitOfWork:
public class UnitOfWork : IUnitOfWork {
private readonly DBContext _context;
public UnitOfWork(DBContext context) {
_context = context;
}
The injection working fine in the controller:
public class UserController : ControllerBase {
private readonly IUnitOfWork unitOfWork;
public UserController(IUnitOfWork unitOfWork) {
this.unitOfWork = unitOfWork;
}
}
How can I create custom class that takes IUnitOfWork in the constructor and call it from main program?
CustomClass :
public class CustomClass {
private readonly IUnitOfWork unitOfWork;
public CustomClass(IUnitOfWork unitOfWork) {
this.unitOfWork = unitOfWork;
}
}
Main program:
public class Program {
public static void Main(string[] args) {
var unitOfWork=new UnitOfWork() // Here I don't want to pass new DBContext I want to reach the same injected DBContext
var customClass =new CustomClass (unitOfWork);
}
}
Firstly, move out your code from ConfigureServices to some shared library that can be used both by Web and Console project. Create extension method to configure all your services.
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp13
{
public static class ConfigureServicesExtensions
{
public static void ConfigureMyServices(this IServiceCollection serviceCollection)
{
serviceCollection.AddDbContext<ApplicationDbContext>();
serviceCollection.AddScoped<IUnitOfWork, UnitOfWork>();
serviceCollection.AddScoped<CustomClass>();
}
}
}
This is how your Console app will look like
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp13
{
class Program
{
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.ConfigureMyServices();
using var serviceProvider = serviceCollection.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var myService = scope.ServiceProvider.GetService<CustomClass>();
}
}
}
And your web project
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureMyServices();
}
What you can do is just add your class to the dependecy injection container and inject it into your constructor, you wouldn't need to inject the IUnitOfWork in you controller.
services.AddScoped<CustomClass>();
and then in your constroller constructor
public class UserController : ControllerBase {
private readonly CustomClass _CustomClass;
public UserController(CustomClass customClass) {
_CustomClass = customClass;
}
}
after that you are able to use this class in your class methods
I would try something like this:
public class Program {
public static void Main(string[] args) {
var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
optionsBuilder.UseSqlServer(connectionstring);
using(DbContext dbContext = new DbContext(optionsBuilder.Options))
{
var unitOfWork=new UnitOfWork(dbContext)
var customClass =new CustomClass (unitOfWork);
.....
}
}
}
I have implemented unit of work in the next way in Entity Framework Core.
Context:
public class DaleContext : DbContext, IDaleContext
{
private readonly IConnectionStringProvider _connectionStringProvider;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionStringProvider.ConnectionString);
base.OnConfiguring(optionsBuilder);
}
public DaleContext(IConnectionStringProvider connectionStringProvider)
{
_connectionStringProvider = connectionStringProvider;
}
public DbSet<ProductProducts { get; set; }
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
var modifiedEntries = ChangeTracker.Entries()
.Where(x =x.State == EntityState.Added || x.State == EntityState.Modified).ToList();
return base.SaveChanges();
}
}
Unit of work:
public class UnitOfWork : IUnitOfWork
{
public UnitOfWork(DbContext dbContext)
{
DbContext = dbContext;
}
public DbContext DbContext { get; set; }
public int Commit()
{
return DbContext.SaveChanges();
}
public async Task<intCommitAsync()
{
return await DbContext.SaveChangesAsync();
}
}
Repository:
public class Repository<TEntity: IDisposable, IRepository<TEntity>
where TEntity : class
{
private readonly UnitOfWork _unitOfWork;
public Repository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork as UnitOfWork;
}
public void Dispose()
{
_unitOfWork.DbContext.Dispose();
}
public void Create(TEntity entity)
{
_unitOfWork.DbContext.Entry(entity).State = EntityState.Added;
}
}
I have injected all with autofac:
public static class Container
{
public static ContainerBuilder RegisterInfraestructure(this ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
return containerBuilder;
}
}
public static class Container
{
public static ContainerBuilder RegisterDataResources(this ContainerBuilder containerBuilder)
{
var configurationBuilder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
var configuration = configurationBuilder.Build();
containerBuilder.Register(x =new ConnectionStringProvider(configuration.GetConnectionString("Fgcm.Dale"))
).As<IConnectionStringProvider>();
containerBuilder.RegisterType<DaleContext>().As<DbContext>().As<IDaleContext>();
return containerBuilder;
}
}
public static class Container
{
public static ContainerBuilder RegisterRepository(this ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<CustomerRepository>().As<ICustomerRepository>();
containerBuilder.RegisterType<ProductRepository>().As<IProductRepository>();
containerBuilder.RegisterType<SaleDetailRepository>().As<ISaleDetailRepository>();
containerBuilder.RegisterType<SaleRepository>().As<ISaleRepository>();
containerBuilder.RegisterDataResources();
containerBuilder.RegisterInfraestructure();
return containerBuilder;
}
}
public static class Container
{
public static ContainerBuilder RegisterApplicationServiceResources(this ContainerBuilder
containerBuilder)
{
containerBuilder.RegisterRepository();
containerBuilder.RegisterType<DaleApplicationService>().As<IDaleApplicationService>();
return containerBuilder;
}
}
When I try to save data it doesn't works (doesn't insert data) ... I would like to know why ? Here are when I try to save:
public Product Create(Product product)
{
try
{
_productRepository.Create(product);
_unitOfWork.Commit();
return product;
}
catch (Exception e)
{
Debug.WriteLine(e);
return null;
}
}
And of course all are injected:
private readonly ICustomerRepository _customerRepository;
private readonly IProductRepository _productRepository;
private readonly ISaleDetailRepository _saleDetailRepository;
private readonly ISaleRepository _saleRepository;
private readonly IUnitOfWork _unitOfWork;
public DaleApplicationService(IProductRepository productRepository, ICustomerRepository customerRepository,
ISaleRepository saleRepository, ISaleDetailRepository saleDetailRepository, IUnitOfWork unitOfWork)
{
_productRepository = productRepository;
_customerRepository = customerRepository;
_saleRepository = saleRepository;
_saleDetailRepository = saleDetailRepository;
_unitOfWork = unitOfWork;
}
What am I missing?
PS: all this works with .NET Core Web Api.
As #zolty13 sort of hinted the Instance scope of your DbContext (DaleContext) is probably incorrect. By default Autofac sets the instance scope to Instance per dependency (also known as a "transient" lifetime) which means a new instance of DaleContext is created for every class that depend on it. So your UnitOfWork receives a different instance of DaleContext than IProductRepository. So changes in IProductRepository are not reflected in UnitOfWork.
One way to solve this is to avoid this convoluted wrapping of your DbContext like #Igor suggest. Do you really need this UnitOfWork? Instead, use a repository class that has one instance of DaleContext and make all the DB changes in there and save them.
Alternatively (if you really think you need a UnitOfWork) you can register your DaleContext with an instance per request scope. Do note: Entity Framework's DbContext is not thread safe, so if you need to do concurrent work, this is not a safe approach.
Otherwise, read up on Instance scope.
How to get DbContext instance in application layer?
I tried:
1.
public class SeedingTestDataAppService : MyAppServiceBase
{
private readonly MyDbContext _ctx;
public SeedingTestDataAppService
(
MyDbContext context // Error
)
{
_ctx = context;
}
}
2.
public class SeedingTestDataAppService : MyAppServiceBase
{
private readonly MyDbContext _ctx;
public SeedingTestDataAppService
(
IDbContextProvider<MyDbContext> dbContextProvider
)
{
_ctx = dbContextProvider.GetDbContext();
// Error: unitOfWork is null
}
}
I am not good at ABP. Where and how can I take a DbContext instance and inject it?
Do not call dbContextProvider.GetDbContext() in the constructor.
Implement _ctx as a getter and use it wherever you actually need the context.
public class SeedingTestDataAppService : MyAppServiceBase
{
private MyDbContext _ctx => _dbContextProvider.GetDbContext();
private readonly IDbContextProvider<MyDbContext> _dbContextProvider;
public SeedingTestDataAppService(IDbContextProvider<MyDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
}
}
I have a console application that Autofac DI is used to inject data and service layer from web application project.
here is the setup on console application:
public static class ContainerConfig
{
public static IContainer Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<DbFactory>().As<IDbFactory>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<Application>().As<IApplication>();
builder.RegisterType<DataRepository>().As<IDataRepository>();
builder.RegisterType<DataService>().As<IDataService>();
return builder.Build();
}
}
public interface IApplication
{
void Run();
}
public class Application : IApplication
{
private readonly IDataService _dataService;
public Application(IDataService dataService)
{
_dataService = dataService;
}
public void Run()
{
var data = _dataService.GetDataById(1);
var task = new TestTask("test");
data.AddTask(task);
_dataService.Update(data);
_dataService.SaveChanges();
}
}
main Program class:
class Program
{
static void Main(string[] args)
{
var container = ContainerConfig.Configure();
using (var scope = container.BeginLifetimeScope())
{
var app = scope.Resolve<IApplication>();
app.Run();
}
}
}
When the application is run loading the data works fine. However, saving a new entry does not seem to do the work.
However, when I remove DI and use simple class initializing in the Run method as below the save works fine:
IDbFactory dbFactory = new DbFactory();
IDataRepository dataRepository = new DataRepository(dbFactory);
var unitOfWork = new UnitOfWork(dbFactory);
IDataService service = new DataService(dataRepository, unitOfWork);
var data = service.GetDataById(1);
var task = new TestTask("test");
data.AddTask(task);
service.Update(data);
service.SaveChanges();
Am I missing something while I setup the autofac? It seems to access the data fine but when it comes to save it does not save the data. I debugged to see any issue but the program runs fine with no error. How can I debug this sort of issues to find more details?
Updated
public interface IDataService
{
void Add(TestTask task);
void SaveChanges();
}
public class DataService : IDataService
{
private readonly IDataRepository _dataRepository;
private readonly IUnitOfWork _unitOfWork;
public DataService(IDataRepository dataRepository, IUnitOfWork unitOfWork)
{
_dataRepository = dataRepository;
_unitOfWork = unitOfWork;
}
public void Add(TestTask task)
{
_dataRepository.Add(task);
}
public void SaveChanges()
{
_unitOfWork.Commit();
}
}
public class UnitOfWork : IUnitOfWork
{
private readonly IDbFactory _dbFactory;
private ApplicationDbContext _dbContext;
public UnitOfWork(IDbFactory dbFactory)
{
this._dbFactory = dbFactory;
}
public ApplicationDbContext DbContext => _dbContext ?? (_dbContext = _dbFactory.Init());
public void Commit()
{
DbContext.Commit();
}
}
After reading autofac scopes here
I found out that default scope is Instance Per Dependency. Which means that a unique instance will be returned from each request for a service. DbFactory should be for InstancePerLifetimeScope.
So changing configuration below fixes the issue:
public static class ContainerConfig
{
public static IContainer Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<Application>().As<IApplication>();
builder.RegisterType<DataRepository>().As<IDataRepository>();
builder.RegisterType<DataService>().As<IDataService>();
return builder.Build();
}
}
I'm learning on doing repository with unit of work. I also know how to do DI/IOC. But my problem is I can't figure out where to apply Unit of Work in my code. Below is a Generic Repository.
public abstract class Repository<T, C> : //IDisposable,
IRepository<T> where T : class
where C : DbContext, new()
{
private C entities = new C();
public C Context
{
get
{
return this.entities;
}
set
{
this.entities = value;
}
}
public virtual void Insert(T entity)
{
this.entities.Set<T>().Add(entity);
}
// remove some code for brevity
}
What I had tried so far:
Make a Unit of Work class
public class UnitOfWork : IUnitOfWork
{
private readonly FooContext _dbContext;
public UnitOfWork()
{
_dbContext = new DataContext;
}
public void Save()
{
_dbContext.SaveChanges();
}
// Dispose method
}
In my service:
public class ProductService : Repository<Product, FooContext>, IProductService
{
private readonly IProductRepository _prodRepo;
private readonly IUnitOfWork _uow;
public ProductService(IUnitOfWork uow, IProductRepository prodRepo)
{
_uow = uow;
_prodRepo = prodRepo;
}
public override void Insert(Item entity)
{
base.Insert(entity);
Save();
}
public void Save()
{
uow.Save();
}
// remove some code for brevity
}
There's no error when I build it. And when I try it apply in my Controller it doesn't give me some error. But when I try to run and debug it, in the Intellitrace, It doesn't give me an Insert statement and again, it does not give me an error.. Where have I gone wrong?
Any help would be much appreciated. Thanks!
We saw together you should separate your service from your repository.
Here, your problem seems to come from the fact you use two different dbcontext instances.
one in your repository and one in your UnitOfWork.
Instead, you should inject the same Dbcontext instance in your repository and your UnitOfWork.
EDIT:
You should write your different layers like this:
public class ProductService
{
private readonly IProductRepository productRepository;
private readonly IUnitOfWork unitOfWork;
public ProductService(IProductRepository productRepository, IUnitOfWork unitOfWork)
{
this.productRepository = productRepository;
this.unitOfWork = unitOfWork;
}
public IEnumerable<Product> GetCurrentProductsOnOrderForCustomer(int customerId)
{
// etc.
}
}
The controller layer should do this:
public class ProductController : Controller
{
private readonly IProductService prodService;
public ProductController(IProductService prodService)
{
this.prodService = prodService;
}
}
Here is your corrected UnitOfWork:
public class UnitOfWork : IUnitOfWork
{
private readonly IDbContext _dbContext;
public UnitOfWork(IDbContext dbContext)
{
_dbContext = dbContext;
}
public void Save()
{
_dbContext.SaveChanges();
}
// Dispose method
}
Here is an example of Repository
public class ProductRepository
{
private readonly IDbContext _context;
public ProductRepository(IDbContext dbContext)
{
this._context = context;
}
public virtual void Insert(T entity)
{
this._context.Products.Add(entity);
}
// remove some code for brevity
}
And you create a context class that inherits from DbContext, and that you inject in UoW and Repos.
public class MyApplicationContext : DbContext
{
public MyApplicationContext(string connectionString)
{
// Configure context as you want here
}
public DbSet<Product> Products { get; set; }
}