I am writing an integration test for my ASP.NET Core MVC application. The test is about to send a POST request to controller and then check if database was updated correctly.
I have a CustomWebApplicationFactory where I am trying to configure SQLite in-memory database, but probably I do something wrong.
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
private SqliteConnection Connection;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
Connection = new SqliteConnection("DataSource=:memory:");
Connection.Open();
builder.UseEnvironment("Development");
builder.ConfigureTestServices(services =>
{
// Unregister existing database service (SQL Server).
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<AppDbContext>));
if (descriptor != null) services.Remove(descriptor);
// Register new database service (SQLite In-Memory)
services.AddDbContext<AppDbContext>(options => options.UseSqlite(Connection));
});
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Connection.Close();
}
}
My test looks like this:
public class OrderControllerTests : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _httpClient;
private readonly AppDbContext _context;
public OrderControllerTests(CustomWebApplicationFactory<Startup> factory)
{
_httpClient = factory.CreateDefaultClient();
var scopeFactory = factory.Services.GetService<IServiceScopeFactory>();
using var scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<AppDbContext>();
}
[Fact]
public async Task Create_Post_OrderIsCreated()
{
// ...
_context.Customers.Add(customer);
_context.SaveChanges();
// ...
}
}
When I run the test, the line _context.Customers.Add(customer); triggers the CustomWebApplicationFactory.Dispose() method and I get an error:
System.ObjectDisposedException : Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'AppDbContext'.
The error message is very descriptive, but I don't know how to solve it. Why is the database context getting disposed?
I think I've figured it out, the issue is that _context is available only in the _scope, so I removed using and now I have shared database between class tests. Each test have also the option to empty and populate the database.
private readonly HttpClient _httpClient;
private readonly AppDbContext _context;
private readonly IServiceScope _scope;
public OrderControllerTests(CustomWebApplicationFactory<Startup> factory)
{
_httpClient = factory.CreateDefaultClient();
_scope = (factory.Services.GetRequiredService<IServiceScopeFactory>()).CreateScope();
_context = _scope.ServiceProvider.GetRequiredService<AppDbContext>();
// database is now shared across tests
_context.Database.EnsureCreated();
}
#Muflix I couldn't add to comment after your comment question as too long so here it is instead...
I'm using xUnit and Shouldly:
Rather than create the scoped service in the constructor and then access from the test(s) I have been injecting the factory (stored as private readonly CustomWebApplicationFactory<Startup> _factory;) then in the tests I use the factory to create a scope and access a scoped service (such as dbcontext) from within each test. The tests share the database due to the fact that the WebApplicationFactory is a class fixture or collection fixture and this maintains a single database connection through its use of the DatabaseFixture member (note that this is not strictly being used as a fixture here it is simply instantiated as a member of the web application factory, it is so called because it is used elsewhere in my code as a unit testing fixture). The constructor in the WebApplicationFactory correctly instantiates the DatabaseFixture class which in turn opens the database connection and only closes it when the WebApplicationFactory (and therefore the DatabaseFixture) is disposed.
My Test:
public class MyControllerTests : IClassFixture<MySQLAppFactory<Startup>> // see below for MySQLAppFactory
{
private readonly MySQLAppFactory<Startup> _factory; // shared for ALL tests in this class (classFixture)
public MyControllerTests(MySQLAppFactory<Startup> factory)
{
_factory = factory;
}
[Theory] //example of many tests all of which use SAME _factory instance
[JsonArrayData("Path-to-Somewhere/MyData.json")] // pass some data
public async Task Post_should_do_whatever(MyRequest request) // I'm using Shouldly
{
var client = _factory.CreateClient(); // create a client form the single instance of webApplicationFactory
var httpContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
var response = await client.PostAsync($"api/my", httpContent);
response.StatusCode.ShouldBe(HttpStatusCode.Whatever);
}
}
The MySQLAppFactory creates its own single DatabaseFixture (TestDatabase) during construction (it removes the existing database context from the services and replaces with a context that uses this DatabaseFixture). This same database class persists (the connection is held open) and it is used throughout integration tests. Each time a new context is created is is attached to the same database connection. Its the same base class as used for unit testing (but in the case of unit testing I mostly use a SQLite derived database, rather than MySQL).
public class MySQLAppFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
private readonly DatabaseFixture _databaseFixture; // THIS factory has THE single Database Fixture instance here
private bool _disposed;
public MySQLAppFactory()
{
_databaseFixture = new MySQLFixture(); // Create the single instance of MySQL fixture here
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder
.UseEnvironment("Testing")
.ConfigureServices(services => //also (as of ASP NET Core 3.0) runs after TStartup.ConfigureServices
{
// remove DbcontextOptions from original API project
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<MyContext>)); services.Remove(descriptor);
// add the test database context instead
services.AddScoped<MyContext>(_ => _databaseFixture.GetContext());
var sp = services.BuildServiceProvider();
});
}
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_databaseFixture?.Dispose();
}
}
base.Dispose(disposing);
_disposed = true;
}
~MySQLAppFactory() => Dispose(false);
}
The Fixture (base):
public abstract class DatabaseFixture : IDisposable
{
private readonly object _seedLock = new object();
private bool _disposed = false;
public DbConnection dbConnection { get; protected set; }
protected abstract bool IsInitialized(bool init = false);
protected abstract DbContextOptions<IMAppContext> GetBuildOptions();
// Note that DbContext instances created in this way should be disposed by the
// calling code (typically with a using {} block).
public MyTestContext GetContext(DbTransaction transaction = null)
{
var context = new MyTestContext(GetBuildOptions());
if (transaction != null)
{
context.Database.UseTransaction(transaction);
}
return context;
}
protected void Seed()
{
// lock and seed here
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// dispose managed state (managed objects) that implement IDisposable
dbConnection?.Dispose();
}
// free unmanaged resources (unmanaged objects) and override a finalizer below.
////if anything exists here then the finalizer is required
// set large fields to null to help release them faster.
_disposed = true;
}
}
}
The Fixture (MySQLDerived - as I also use others):
public sealed class MySQLFixture : DatabaseFixture
{
private bool _initialised = false;
private bool _disposed = false;
private readonly string _connectionString;
private readonly string _databaseName;
public MySQLFixture()
{
_databaseName = "some name possibly derived from config or guid etc";
_connectionString = "using _databasename and possibly config, build or environment etc";
dbConnection = new MySqlConnection(_connectionString);
Seed();
}
protected override bool IsInitialized(bool init = false)
{
if (!init)
{
return _initialised;
}
else
{
_initialised = init;
return _initialised;
}
}
protected override DbContextOptions<IMAppContext> GetBuildOptions()
{
return new DbContextOptionsBuilder<IMAppContext>().UseMySQL(dbConnection).Options;
}
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// dispose managed state (managed objects) that implement IDisposable
}
// free unmanaged resources (unmanaged objects) and override a finalizer below.
////if anything exists here then the this.finalizer is required below
dbConnection.Open();
var command = dbConnection.CreateCommand();
command.CommandText = $"DROP SCHEMA IF EXISTS `{_databaseName.ToLower()}`";
command.ExecuteNonQuery();
// set large fields to null to help release them faster.
}
base.Dispose(disposing);
_disposed = true;
}
~MySQLFixture() => Dispose(false);
}
Related
How can I pass the DBContext when initializing in CustomWebApplicationFactory to make tests in the xUnit file or correct the error ?
public class CustomWebApplicationFactory : WebApplicationFactory<quest_web.Startup>
{
public APIDbContext Context;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(ConfigureServices)
.UseEnvironment("Development");
}
private void ConfigureServices(WebHostBuilderContext webHostBuilderContext, IServiceCollection serviceCollection)
{
var dbContextService = serviceCollection.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<APIDbContext>));
if(dbContextService != null)
{
serviceCollection.Remove(dbContextService);
}
var provider = serviceCollection
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
serviceCollection.AddDbContext<APIDbContext>(contextOptions => {
contextOptions.UseInMemoryDatabase("quest_web");
contextOptions.UseInternalServiceProvider(provider);
});
var sp = serviceCollection.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
Context = scopedServices.GetRequiredService<APIDbContext>();
Context.Database.EnsureCreated();
Context.Database.EnsureDeleted();
}
}
}
When calling the test the context returns the error
public class Test
{
private readonly APIDbContext _context;
private readonly HttpClient _client;
public Test()
{
factory = new CustomWebApplicationFactory();
_client = factory.CreateClient();
_context = factory.Context;
}
[Fact]
private async void Test_dbcontext()
{
User user = new User("username", "password");
_context.User.Add(user);
_context.SaveChanges();
}
}
There is my feeback error
System.ObjectDisposedException : Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
[UPDATE]
Use Singleton and not scoped lifetime
I need some help to understand what it's wrong in my configuration of the container.
I based this implementation by using this example.
Basically i need to implement some use case as database command based on that interface
public interface IDatabaseCommand<TResult, TParam>
{
TResult Execute(TParam commandParam);
}
and i want to use a decorator that add the transaction safe functionality.
Every command need to use a dedicated DbContext and the transaction has to be executed on that context
To do this i have implemented
Transactional Decorator:
public class TransactionDatabaseCommandDecorator
: IDatabaseCommand<DatabaseResult, BusinessCommandParams1>
{
private readonly Container _container;
private readonly Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>>
_decorateeFactory;
public TransactionDatabaseCommandDecorator(
Container container,
Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>> decorateeFactory)
{
_container = container;
_decorateeFactory = decorateeFactory;
}
public DatabaseResult Execute(BusinessCommandParams1 commandParam)
{
DatabaseResult res;
using (AsyncScopedLifestyle.BeginScope(_container))
{
var _command = _decorateeFactory.Invoke();
var factory = _container
.GetInstance<IDesignTimeDbContextFactory<WpfRadDispenserDbContext>>();
using (var transaction = factory.CreateDbContext(
new[] { "" }).Database.BeginTransaction())
{
try
{
res = _command.Execute(commandParam);
transaction.Commit();
}
catch (Exception e)
{
Console.WriteLine(e);
transaction.Rollback();
throw;
}
}
}
return res;
}
}
Example of implementation:
public class WpfRadDispenserUOW : IUnitOfWork<WpfRadDispenserDbContext>
{
private readonly IDesignTimeDbContextFactory<WpfRadDispenserDbContext> _factory;
private WpfRadDispenserDbContext _context;
private IDbContextTransaction _transaction;
public bool IsTransactionPresent => _transaction != null;
public WpfRadDispenserUOW(IDesignTimeDbContextFactory<WpfRadDispenserDbContext> fact)
{
_factory = fact ?? throw new ArgumentNullException(nameof(fact));
}
public WpfRadDispenserDbContext GetDbContext() =>
_context ?? (_context = _factory.CreateDbContext(null));
public IDbContextTransaction GetTransaction() =>
_transaction ?? (_transaction = GetDbContext().Database.BeginTransaction());
public void RollBack()
{
_transaction?.Rollback();
_transaction?.Dispose();
}
public void CreateTransaction(IsolationLevel isolationLevel) => GetTransaction();
public void Commit() => _transaction?.Commit();
public void Persist() => _context.SaveChanges();
public void Dispose()
{
_transaction?.Dispose();
_context?.Dispose();
}
}
Some command:
public class BusinessCommand1 : IDatabaseCommand<DatabaseResult, BusinessCommandParams1>
{
private readonly IUnitOfWork<WpfRadDispenserDbContext> _context;
public BusinessCommand1(IUnitOfWork<WpfRadDispenserDbContext> context)
{
_context = context;
}
public DatabaseResult Execute(BusinessCommandParams1 commandParam)
{
//ToDo: use context
return new DatabaseResult();
}
}
Registration of container:
var container = new Container();
container.Options.DefaultScopedLifestyle = ScopedLifestyle.Flowing;
container.Register<IDesignTimeDbContextFactory<WpfRadDispenserDbContext>>(() =>
{
var factory = new WpfRadDispenserDbContextFactory();
factory.ConnectionString =
"Server=.\\SqlExpress;Database=Test;Trusted_Connection=True";
return factory;
});
container.Register<IUnitOfWork<WpfRadDispenserDbContext>, WpfRadDispenserUOW>(
Lifestyle.Scoped);
container
.Register<IUnitOfWorkFactory<WpfRadDispenserDbContext>, WpfRadDispenserUOWFactory>();
//Command registration
container.Register<
IDatabaseCommand<DatabaseResult, BusinessCommandParams1>,
BusinessCommand1>();
//Command Decorator registration
container.RegisterDecorator(
typeof(IDatabaseCommand<DatabaseResult, BusinessCommandParams1>),
typeof(TransactionDatabaseCommandDecorator),Lifestyle.Singleton);
The problem is that when i try to execute
var transactionCommandHandler =
_container.GetInstance<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>>();
usecase.Execute(new BusinessCommandParams1());
i receive correctly an instance of TransactionDatabaseCommandDecorator but when the i try to get the instance from the factory i receive this error
SimpleInjector.ActivationException: WpfRadDispenserUOW is registered using the 'Scoped' lifestyle, but the instance is requested outside the context of an active (Scoped) scope. Please see https://simpleinjector.org/scoped for more information about how apply lifestyles and manage scopes.
in SimpleInjector.Scope.GetScopelessInstance(ScopedRegistration registration)
in SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration registration, Scope scope)
in SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope)
in WpfRadDispenser.DataLayer.Decorator.TransactionDatabaseCommandDecorator.Execute(BusinessCommandParams1 commandParam) in C:\Work\Git\AlphaProject\WpfRadDispenser\WpfRadDispenser.DataLayer\Decorator\TransactionDatabaseCommandDecorator.cs: riga 29
in WpfRadDispenser.Program.Main() in C:\Work\Git\AlphaProject\WpfRadDispenser\WpfRadDispenser\Program.cs: riga 47
The problem here is that i want to use a dbcontext that it's created and controlled by his decorator.
But the constructor injection it's handled by container so how i can inject the context created by the decorator inside the command?
Basically i want to having something like that made by the decorator of the command
var context = ContextFactory.GetContext();
try
{
var transaction = context.database.GetTransaction();
var command = new Command(context);
var commandParams = new CommandParams();
var ret = command.Execute(commandParams);
if (!ret.Success)
{
transaction.Discard();
return;
}
transaction.Commit();
}
catch
{
transaction.Discard();
}
but made with DI and Simple Injector
Maybe there is some issue or several issue on my design but i'm new on DI and i want to understand better how the things works.
Just to recap i need to use a lot of command database in which every command has to have an isolated context and the functionality of transaction has to be controlled by an extra layer inside the decorator.
The problem is caused by the mixture of both flowing/closure scoping vs ambient scoping. Since you are writing a WPF application, you choose to use Simple Injector's Flowing scopes feature. This allows you to resolve instances directly from a scope (e.g. calling Scope.GetInstnace).
This, however, doesn't mix with Ambient Scoping, as is what AsyncScopedLifestyle.BeginScope does.
To fix this, you will have to change the implementation of your decorator to the following:
public class TransactionDatabaseCommandDecorator
: IDatabaseCommand<DatabaseResult, BusinessCommandParams1>
{
private readonly Container _container;
private readonly Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>>
_decorateeFactory;
public TransactionDatabaseCommandDecorator(
Container container,
Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>> decorateeFactory)
{
_container = container;
_decorateeFactory = decorateeFactory;
}
public DatabaseResult Execute(BusinessCommandParams1 commandParam)
{
DatabaseResult res;
using (Scope scope = new Scope(_container))
{
var command = _decorateeFactory.Invoke(scope);
var factory = scope
.GetInstance<IDesignTimeDbContextFactory<WpfRadDispenserDbContext>>();
...
}
return res;
}
}
Note the following about the decorator above:
It gets injected with a Func<Scope, T> factory. This factory will create the decoratee using the provided Scope.
The execute method now creates a new Scope using new Scope(Container) instead of relying on the ambient scoping of AsyncScopedLifestyle.
The Func<Scope, T> factory is provided with the created scope.
The IDesignTimeDbContextFactory<T> is resolved from the Scope instance, instead of using the Container.
I want to know if there is a better to way to handle this.
I've set up Unity for dependency injection for our project. The project itself is an ASP.NET application that uses Web API.
I have the following packages installed.
Unity
Unity.ASPNet.WebAPI
I see no option to close/dispose the DBContext right after fetching the data.
My controller
public class NinjasController : ApiController
{
public Ninja Get(int id)
{
INinjaRepository repository = UnityConfig.Container.Resolve(typeof(INinjaRepository), null) as INinjaRepository;
Ninja ninja = repository.GetNinjaById(id);
repository.CanBeDisposed = true;
repository = null;
UnityConfig.PerRequestLifetimeManager.Dispose();
return ninja;
}
}
UnityConfig
public static class UnityConfig
{
private static Lazy<IUnityContainer> container =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer Container => container.Value;
public static PerRequestLifetimeManager PerRequestLifetimeManager;
public static void RegisterTypes(IUnityContainer container)
{
PerRequestLifetimeManager = new PerRequestLifetimeManager();
container.RegisterType<INinjaRepository, NinjaRepository>(PerRequestLifetimeManager);
}
}
Lifetime Manager
public class PerRequestLifetimeManager : TransientLifetimeManager, IDisposable
{
private static List<IBaseRepository> list = new List<IBaseRepository>();
public override void SetValue(object newValue, ILifetimeContainer container = null)
{
base.SetValue(newValue, container);
IBaseRepository disposable = newValue as IBaseRepository;
if (disposable != null)
list.Add(disposable);
}
public void Dispose()
{
foreach (IBaseRepository item in list.FindAll(item => item.CanBeDisposed))
{
if (item != null)
{
try
{
item.Dispose();
}
catch (Exception)
{
// log exception and continue
}
}
}
list.RemoveAll(item => item.CanBeDisposed);
}
}
Repository
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
internal DbContext _context;
internal DbSet<TEntity> _dbSet;
public bool CanBeDisposed { get; set; }
public GenericRepository(DbContext context)
{
_context = context;
_dbSet = context.Set<TEntity>();
}
protected void Dispose(bool disposing)
{
if (disposing)
{
if (_context != null)
{
_context.Dispose();
_context = null;
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
First you might want to add one more Unity bootstrapper to your project Unity.AspNet.Mvc
https://msdn.microsoft.com/en-us/library/dn507440(v=pandp.30).aspx
To use the PerRequestLifetimeManager class in an ASP.NET Web API application, you must also add the the Unity bootstrapper for ASP.NET MVC NuGet package to your project.
Unity.Mvc and Unity.AspNet.WebApi will register your controllers for DI.
UnityConfig.cs
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<INinjaContext, NinjaContext>(new PerRequestLifetimeManager());
container.RegisterType<INinjaRepository, NinjaRepository>(new PerRequestLifetimeManager());
}
UnityWebApiActivator.cs Uncomment the line...
public static void Start()
{
// Use UnityHierarchicalDependencyResolver if you want to use
// a new child container for each IHttpController resolution.
var resolver = new UnityHierarchicalDependencyResolver(UnityConfig.Container);
...
}
UnityMvcActivator.cs Uncomment the line...
public static void Start()
{
...
// TODO: Uncomment if you want to use PerRequestLifetimeManager
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
}
Your controller is simply
public class NinjasController : ApiController
{
private readonly INinjaRepository repository;
public NinjasController(INinjaRepository repository)
{
this.repository = repository;
}
public Ninja Get(int id)
{
var ninja = repository.GetNinjaById(id);
return ninja;
}
}
With PerRequestLifetimeManager Unity will take care of disposal after the request is complete.
I have an example here https://github.com/jasenhk/MovieStar
If you are using OWIN see Unity IoC does not inject dependency into Web API Controller
I am new to .net core and dependency injection concept. I want to inject service interface in Web API constructor, Service interface and implementation is in different project. Please find below layers of my application,
In startup.cs, I already add below line,
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddSingleton<IEntriesService, EntriesService>();
}
My controller,
public class EntriesController : Controller
{
IEntriesService entryService;
public EntriesController(IEntriesService _entryService)
{
entryService = _entryService;
}
// GET: api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Problem is, When I execute my API application, It is not hitting to my constructor and shows me blank page as below,
Without adding constructor, Application is working fine.
My IEntriesService,
public interface IEntriesService
{
RepeatEntries Get(int Id);
}
My EntriesService,
public class EntriesService : IEntriesService
{
IUnitOfWork _unitOfWork;
public EntriesService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public bool Add(RepeatEntries entity)
{
_unitOfWork.EntryRepository.Add(entity);
return true;
}
}
My IUnitOfWork,
public interface IUnitOfWork : IDisposable
{
IEntriesRepository EntryRepository { get; }
void Complete();
}
My UnitOfWork,
public class UnitOfWork : IUnitOfWork
{
private readonly IEntriesRepository _entryRepository;
public UnitOfWork(IEntriesRepository entryRepository)
{
_entryRepository = entryRepository;
}
public IEntriesRepository EntryRepository
{
get
{
return _entryRepository;
}
}
void IUnitOfWork.Complete()
{
throw new NotImplementedException();
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~UnitOfWork() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
void IDisposable.Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
What else I need to add to make it work? Is it possible or I need to change my approach?
You have to register all dependencies with the composition root, making sure to register them with the correct lifetime i.e:(Scoped, Transient, Singleton) to avoid problems in future.
public void ConfigureServices(IServiceCollection services) {
// Add framework services.
services.AddMvc();
services.AddSingleton<IEntriesService, EntriesService>();
services.AddTransient<IUnitOfWork, UnitOfWork>();
services.AddTransient<IEntriesRepository, EntriesRepository>();
services.AddSingleton<IConnectionFactory, ConnectionFactory>();
//...add other dependencies.
}
Take some time and check out the documentation:
Introduction to Dependency Injection in ASP.NET Core
I am working on an asp.net mvc web application. now i have created multiple repositories classes, for example i have the following abstract repository classes:-
public interface ISkillRepository : IDisposable
{//code goes here..
&
public interface IStaffRepository : IDisposable
{//code goes here
and the model Repositories:-
public class SkillRepository : ISkillRepository , IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
//code goes here
&
public class StaffRepository : IStaffRepository , IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
now inside y controller i am intializing and creating the repo as follow:-
public class SkillController : Controller
{
private ISkillRepository skillRepository;
public SkillController() : this(new SkillRepository()) {}
public SkillController(ISkillRepository repository)
{
skillRepository = repository;
}
but currently i got the following error inside my application:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
and the problem is that i need to be passing the same context accross the repos and controllers. so can anyone adivce on this:-
how i can inside one model repo to reference another repo using the same context class. for example inside the Staff repositoryto referecne the skill repository?
how i can inside a controller class to refer multiple repos , but at the same time pass the same context object among them , so if i issue a save() it will wrap all the statements inside one transaction. for example insie my skillController to reference both the skill & staff repos using the same context object ?
Thanks
Edit
I have created the following Unit of work class:-
public class UnitOfWork : IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
private SkillRepository skillRepository;
private StaffRepository staffRepository;
private SecurityRoleRepository securityroleRepository;
public SkillRepository SkillRepository
{
get
{
if (this.skillRepository == null)
{
this.skillRepository = new SkillRepository(context);
}
return skillRepository;
}
}
public StaffRepository StaffRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new StaffRepository(context);
}
return staffRepository;
}
}
public SecurityRoleRepository SecurityRoleRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new SecurityRoleRepository(context);
}
return securityroleRepository;
}
}
public async Task Save()
{
await context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
and then inside my repo i did the following:-
public class SecurityRoleRepository : ISecurityRoleRepository , IDisposable
{
private SkillManagementEntities context;// = new SkillManagementEntities();
public SecurityRoleRepository(SkillManagementEntities context)
{
this.context = context;
and on the controller class i will be referencing the UnitOfWork as follow:-
public class SecurityRoleController : Controller
{
private UnitOfWork unitOfWork = new UnitOfWork();
public async Task<ActionResult> Index(string filter = null, int page = 1, int pageSize = 20, string sort = "Name", string sortdir = "ASC")
{
try
{
var records = new PagedList<SecurityRole>();
ViewBag.filter = filter;
records.Content = await unitOfWork.SecurityRoleRepository.GetSecurityRoleForGrid(filter, page, pageSize, sort, sortdir).ToListAsync();
now i am facing a problem is that how i can referecne a repo from another Repo ? for example how i can reference the Skill repo inside the SecurityRole repo ?
EDIT Final
i did the following steps:-
1. i install
Install-Package Ninject.MVC5
2. then i created the following dependency class:-
public class YourDependencyResolverClass : IDependencyResolver
{
private IKernel kernel;
public YourDependencyResolverClass()
{
kernel = new StandardKernel();
AddBindings();
}
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
private void AddBindings()
{
kernel.Bind<ISkillRepository>().To<SkillRepository>();
kernel.Bind<IStaffRepository>().To<StaffRepository>();
kernel.Bind<ISecurityRoleRepository>().To<SecurityRoleRepository>();
kernel.Bind<ICustomerRepository>().To<CustomerRepository>();
kernel.Bind<ISkillVersionHistoryRepository>().To<SkillVersionHistoryRepository>();
}
}
}
3.now inside my SkillRepository class i will be referencing the StaffRepository as follow:-
public class SkillRepository : ISkillRepository , IDisposable
{
private SkillManagementEntities context ;
private IStaffRepository staffrepo = (IStaffRepository)DependencyResolver.Current.GetService(typeof(IStaffRepository));
public SkillRepository(SkillManagementEntities context)
{
this.context = context;
}
Finally inside my action method i will be calling the Uiteofwork class as follow:-
public class StaffController : Controller
{
//private SkillManagementEntities db = new SkillManagementEntities();
UnitOfWork unitofwork = new UnitOfWork();
public async Task<ActionResult> AutoComplete(string term)
{
var staff = await unitofwork.StaffRepository.GetAllActiveStaff(term).Select(a => new { label = a.SamAccUserName }).ToListAsync();
return Json(staff, JsonRequestBehavior.AllowGet);
and the unite of work class is :-
public class UnitOfWork : IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
private SkillRepository skillRepository;
private StaffRepository staffRepository;
private SecurityRoleRepository securityroleRepository;
private CustomerRepository customerRepository;
private SkillVersionHistoryRepository SVH;
public SkillRepository SkillRepository
{
get
{
if (this.skillRepository == null)
{
this.skillRepository = new SkillRepository(context);
}
return skillRepository;
}
}
public StaffRepository StaffRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new StaffRepository(context);
}
return staffRepository;
}
}
public CustomerRepository CustomerRepository
{
get
{
if (this.customerRepository == null)
{
this.customerRepository = new CustomerRepository(context);
}
return customerRepository;
}
}
public SecurityRoleRepository SecurityRoleRepository
{
get
{
if (this.securityroleRepository == null)
{
this.securityroleRepository = new SecurityRoleRepository(context);
}
return securityroleRepository;
}
}
public SkillVersionHistoryRepository SkillVersionHistoryRepository
{
get
{
if (this.SVH == null)
{
this.SVH = new SkillVersionHistoryRepository(context);
}
return SVH;
}
}
public async Task Save()
{
await context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
So can you adivce if my approach of using unitefwork and DI will guarantee that all my statements will be warped inside a single DB transaction ? thnaks?
We handle this by sharing a context using a singleton that is scoped to the request using HttpContext:
public MyContext GetContext()
{
if (System.Web.HttpContext.Current.Items["MyScopedContext"] == null)
{
System.Web.HttpContext.Current.Items["MyScopedContext"] = new MyContext();
}
return (MyContext)System.Web.HttpContext.Current.Items["MyScopedContext"];
}
The context object (repository) itself essentially houses a Unit of Work. The code I added above just gives you a way to share a single repository across all code running within a request. If your repository classes are defined in the scope of a web application, you can just replace your direct instantiation of SkillManagementEntities() with a call to a GetContext() method.
On the other hand if your repositories are defined in a layer shared by heterogeneous applications, you may need to get your context from a factory object that you can inject as needed. Either way, creating a new context object per repository is what's causing your issue.
Not an answer: this "use DI" suggestion answers a bit different question - OP is looking for "unit-of-work" pattern - while basic case (lifetime of unit of work matches lifetime of request/controller) can easily be solved with any DI framework, managing multiple units of work or units of work with longer lifetime is much harder and dedicated "unit of work factory" (sample usage) is likely the solution.
Usually when you go that far with interfaces/repositories and constructor dependency injection you have some Dependency Injection framework. There is a good chance that one you are using already provides "per HTTP request" resolution or allows to easily add one.
I.e. if you using Unity there is PerRequestLifetime lifetime manager that makes all .Resolve calls for the same interface/object to return the same instance for given request. See more info in DI with Unity MSDN article.
Approximate sample:
container.RegisterType<ISkillRepository, SkillRepository>();
container.RegisterType<IOtherRepository, OtherRepository>();
container.RegisterType<TheContext, TheContext>(new PerRequestLifetime());
With such registration and assuming you've configured ASP.Net MVC to use Unity to resolve types when controller is created it will get necessary dependencies (new instances as registered with default lifetime), but both will share the same context (assuming each depends on TheContext class either via constructor or property injection).