I was working with SignalR, and created timer that will execute following code where I need to get the number of unread messages from database
[Route("api/[controller]")]
[ApiController]
public class PorukeHubController : ControllerBase
{
private readonly IHubContext<PorukeHub> _hub;
private readonly TimerManager _timer;
private DnevnikContext _context { get; set; }
public PorukeHubController(IHubContext<PorukeHub> hub,
TimerManager timer, DnevnikContext context)
{
_hub = hub;
_timer = timer;
_context = context;
}
[HttpGet]
public IActionResult Get()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() =>
{
var unreadMessages = _context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
_hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
});
return Ok(new { Message = "Request Completed" });
}
Unfortunately, I get the following error when trying to access _context:
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. ObjectDisposed_ObjectName_Name'
I'm very confused on what steps should I take to solve this, I'm not yet familiar that much with DI too
Any help would be appreciated
You should turn your method into an async method and await the database call.
public async Task<IActionResult> Get()
{
...
var unreadMessages = await _context.Messages.Where(p => p.RecieverID == currentUserId && p.isRead == false).Count();
...
}
A DbContext only lives for a short period of time and its lifetime ends at the end of the web request for which it is created.
The delegate, however, that you use to initialize the TimerManager stores a reference to that DbContext likely for the duration of the application (assuming that TimerManager is a Singleton). But since that DbContext is disposed of soon after you initialized the TimerManager it becomes unusable.
In general, you should prevent moving injected dependencies from thread to thread (except in case those threads are part of a sequentially executed asynchronous operation), because the consuming code (i.e. your controller) doesn't know whether or not it is safe to do so.
This means that, instead of reusing the same DbContext, the timer should get its own instance and, preferable, get a fresh DbContext instance every time the timer is triggered. You should see each timer tick as a new request, just like every call to a controller is a new (web) request. And the general rule of thumb is that each new request gets its own container scope.
What this means is that, instead of reusing the same DbContext instance, you should wrap each tick in a container scope / IServiceScope and resolve the DbContext from that scope.
For instance:
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public PorukeHubController(TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
[HttpGet]
public IActionResult Get()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() =>
{
using (var scope = this.provider.CreateScope())
{
var sp = scope.ServiceProvider;
var context = sp.GetRequiredService<DnevnikContext>();
var hub = sp.GetRequiredService<IHubContext<PorukeHub>>();
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
});
return Ok(new { Message = "Request Completed" });
}
Although the code above fixes the initial problem of letting the DbContext go out of scope, it poses a new problem, which is more fundamental in nature, which is that application components shouldn't depend on the (IServiceProvider) DI Container. This is called the Service Locator anti-pattern.
This means that you shouldn't do this type of initialization inside your controllers, but instead move this to the application startup path; a.k.a. the Composition Root.
This can be done, for instance, by introducing a new abstraction:
private readonly IMessageInitializer _initializer;
public PorukeHubController(IMessageInitializer initializer)
{
__initializer = _initializer;
}
[HttpGet]
public IActionResult Get()
{
_messageInitializer.Initialize();
return Ok(new { Message = "Request Completed" });
}
In this code example the new IMessageInitializer hides the complexity of initialization of the timer, the querying of the database and the calling of the hub from the controller.
Inside your Composition Root you can now define an implementation with the original code:
public class ScheduledMessageInitializer : IMessageInitializer
{
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public ScheduledMessageInitializer(
TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
public void Initialize()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() => {
using (var scope = this.provider.CreateScope())
{
var sp = scope.ServiceProvider;
var context = sp.GetRequiredService<DnevnikContext>();
var hub = sp.GetRequiredService<IHubContext<PorukeHub>>();
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
});
}
}
This class can be registered as follows:
// This assumes that TimerManager is a singleton as well.
services.AddSingleton<IMessageInitializer, ScheduledMessageInitializer>();
This still poses a (smaller) design issue, which is that with DI you should strive to keep the application's business logic out of the Composition Root. It should only contain the required infrastructural code for the application to execute. The querying of the database and sending it to the hub can be considered business logic; not infrastructure.
That would mean that one last refactoring is in place: you should extract that logic out of the ScheduledMessageInitializer and place it in a new application component:
public class UnreadMessageChecker
{
private readonly DnevnikContext _context;
private readonly IHubContext<PorukeHub> _hub;
public UnreadMessageChecker(DnevnikContext context, IHubContext<PorukeHub> hub)
{
_context = context;
_hub = hub;
}
public async Task CheckAsync()
{
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
// I noticed how you called SendAsync. You should await it, otherwise
// you'll get into the same trouble as where you started out with:
// with an ObjectDisposedExcetion.
await hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
}
services.AddTransient<UnreadMessageChecker>();
This new component can be resolved from the ScheduledMessageInitializer, which reduces it to the following:
public class ScheduledMessageInitializer : IMessageInitializer
{
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public ScheduledMessageInitializer(
TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
public void Initialize()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(async () =>
{
using (var scope = this.provider.CreateScope())
{
var checker = scope.ServiceProvider
.GetRequiredService<UnreadMessageChecker>();
await checker.CheckAsync();
}
});
}
}
There might still be other issues with your code. For instance, it seems weird to me that you have a currentUserId (which is runtime data, changing on each request), while using it to initialize the timer with; unless that timer isn't a Singleton. But if the timer isn't a singleton, that would mean that would be initializing an endless number of timers, which likely isn't a good idea as well.
Another issue is that, if the TimerManager is indeed singleton, there might be a race condition while initializing. Is the TimerManager thread-safe? What would happen when it gets initialized twice simultaneously? Would that cause problems. I, unfortunately, can't answer this.
Related
Using the code you posted in your new answer. But same error when I add a file and the method UpdateVergadering is called.
vergaderingRepository:
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public VergaderingRepository(IDbContextFactory<ApplicationDbContext> dbContextFactory, IDbContextFactory<ApplicationDbContext> factory)
{
_factory = factory;
}
public async ValueTask<int> UpdateVergadering(Vergadering vergadering)
{
using var dbContext = _factory.CreateDbContext();
dbContext.Set<Vergadering>().Update(vergadering);
return await dbContext.SaveChangesAsync();
}
public async ValueTask<Vergadering> GetVergaderingVoorLiveNotulenAsync (int vergaderingId)
{
using var dbContext = _factory.CreateDbContext();
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await dbContext.Set<Vergadering>().SingleOrDefaultAsync(x => x.Id == vergaderingId);
}
The error I get:
System.InvalidOperationException: 'The instance of entity type 'Bestuurslid' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
You code never completes the component render, you loop within OnInitialized. You also confuse OnInitialized and OnInitializedAsync.
Here's a demo page that shows how to use the System.Timers.Timer with an event handler hooked up to the timer to handle the data get and UI update. OnInitializedAsync does the initial data get, sets up the timer, wires up the event handler and completes.
#page "/"
#implements IDisposable
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<div class="alert alert-success">
#_message
</div>
#code {
private string? _message = "Not Set";
private System.Timers.Timer _timer = new System.Timers.Timer(2000);
protected async override Task OnInitializedAsync()
{
// Initial data get
_message = await GetData();
// set uo the timer and hook up the event handler
_timer.AutoReset = true;
_timer.Elapsed += this.OnTimerElapsed;
_timer.Start();
}
// Event handler for the timer
private async void OnTimerElapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
_message = await GetData();
// must call this like this as the timer may be running on a different thread
await this.InvokeAsync(StateHasChanged);
}
private async ValueTask<string> GetData()
{
// emulate an async call to a Db or API
await Task.Delay(100);
return DateTime.Now.ToLongTimeString();
}
// Dispose of the event handler when the Renderer has finished with the component
public void Dispose()
=> _timer.Elapsed -= this.OnTimerElapsed;
}
Update on DbContexts and Async behaviour
Set up a DbContextFactory:
services.AddDbContextFactory<MyDbContext>(
options =>
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test"));
And then use the factory to get Db context instances as you need them.
public sealed class MeetingBroker
{
private readonly IDbContextFactory<MyDbContext> _factory;
public MeetingBroker(IDbContextFactory<MyDbContext> factory)
{
_factory = factory;
}
public ValueTask<Vergadering> GetVergaderingByIdAsync(int vergaderingId)
{
using var dbContext = _factory.CreateDbContext();
// if you aren't editing the data then you don't need tracking. Imporves performance
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await dbContext.Set<TRecord>().SingleOrDefaultAsync(x => x.Id == vergaderingId));
}
DbContextFctory Update
You've implemented the factory, but not the "Unit of Work" pattern. You're implementation uses the same context for all activity within the repository and will cause usage clashes.
Blazor lives in an async world so you need to code for situations where you have parallel processes running on the same resources.
Your Repository Pattern should look like this:
private readonly IDbContextFactory<ApplicationDbContext> _factoy;
public VergaderingRepository(IDbContextFactory<ApplicationDbContext> dbContextFactory)
{
// assigned the factory not a context
_factory = dbContextFactory;
}
public async ValueTask<Vergadering> GetVergaderingVoorLiveNotulenAsync (int vergaderingId)
{
// creates a context for each transaction
using dbContext = dbContextFactory.CreateDbContext();
return await dbContext.Set<Vergadering>().SingleOrDefaultAsync(x => x.Id == vergaderingId);
}
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public VergaderingRepository(IDbContextFactory<ApplicationDbContext> dbContextFactory, IDbContextFactory<ApplicationDbContext> factory)
=> _factory = factory;
public async ValueTask<Vergadering> GetVergaderingVoorLiveNotulenAsync (int vergaderingId)
{
using var dbContext = _factory.CreateDbContext();
// Turning off tracking as this is only a query.
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await dbContext.Set<Vergadering>().SingleOrDefaultAsync(x => x.Id == vergaderingId);
}
public async ValueTask<int> UpdateVergadering(Vergadering vergadering)
{
using var dbContext = _factory.CreateDbContext();
// Tracking is required for updates
//dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
dbContext.Set<Vergadering>().Update(vergadering);
return await dbContext.SaveChangesAsync();
}
I had the following Middleware, the main idea is that check for every request (throught View or Ajax) if the user is not blocked in the database.
internal class UserMiddleware
{
private readonly RequestDelegate next;
private readonly IUsuariosRepository userRepository;
public UserMiddleware(RequestDelegate next, IUsuariosRepository userRepository)
{
this.next = next ?? throw new ArgumentNullException(nameof(next));
this.userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
}
public async Task Invoke(HttpContext httpContext)
{
string guid = httpContext.User.Claims?.FirstOrDefault(x => x.Type.Equals("theClaimGuid"))?.Value;
bool isBlocked = await userRepository.CheckUserBlockByGUID(guid);
if (isBlocked)
{
await httpContext.Response.WriteAsync("You are blocked.");
return;
}
await next(httpContext);
}
}
The call to the database with the context is made in the CheckUserBlockByGUID method:
public class UsuariosRepository : IUsuariosRepository
{
private readonly nContext _context;
private readonly IWebHostEnvironment _hostingEnvironment;
public UsuariosRepository(nContext context, IWebHostEnvironment hostingEnvironment)
{
_context = context;
_hostingEnvironment = hostingEnvironment;
}
public async Task<bool> CheckUserBlockByGUID(string guid) {
bool isOK = await _context.Usuarios.Where(u => u.Guid == guid && u.Deleted == 0 && u.blocked == 0).AnyAsync();
return !isOK;
}
...
and the Middleware is called inside Startup.cs / Config as:
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<UserMiddleware>();
All my calls to the database are used with async / await but with this one, I get the following error:
A second operation started on this context before a previous operation
completed. This is usually caused by different threads using the same
instance of DbContext. For more information on how to avoid threading
issues with DbContext, see
https://go.microsoft.com/fwlink/?linkid=2097913.
I understand that I'm calling without waiting for the close operation of the other one. But I can't find that call. I don't know even if the error is this one or the problem is another one that I'm not seeing right now.
Any help or tip will be preciated.
Also please ask me in order to clarify this question or update it.
Thanks in advance and sorry for my english.
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.
In my test I have to have access to database context (real database, it's not in memory). Because after post I want to ensure that record has been saved in database.
This is what I have, it works perfectly but I don't have enough experiance to be sure that this is correct approach for getting context.
public class ValuesControllerTests : TestHostFixture
{
[Theory]
[InlineData("/values/sample")]
public async Task Sample_WhenCreatingSampleData_ThenIsAddedToDatabase(string url)
{
// given
var command = new AddSampleCommand { Name = "TestRecord" };
// when
var httpResponse = await Client.PostAsJsonAsync(url, command);
// then
httpResponse.EnsureSuccessStatusCode();
using (var dbContext = GetContext())
{
dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord").ShouldNotBeNull();
}
}
}
public abstract class TestHostFixture : IClassFixture<CustomWebApplicationFactory<Startup>>
{
protected readonly HttpClient Client;
private readonly CustomWebApplicationFactory<Startup> _factory;
protected TestHostFixture()
{
_factory = new CustomWebApplicationFactory<Startup>();
Client = _factory.CreateClient();
}
protected MyContext GetContext()
{
return _factory.Server.Host.Services.CreateScope().ServiceProvider.GetService<MyContext>();
}
}
So just to sum up - in test I'm getting Context by:
using (var dbContext = GetContext())
{
dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord").ShouldNotBeNull();
}
And GetContext method:
protected MyContext GetContext()
{
return _factory.Server.Host.Services.CreateScope().ServiceProvider.GetService<MyContext>();
}
Please let me know if this is fine, or maybe I should refactor it somehow because of some potential issue in future.
Generally, this is fine: You definitely should make use of the host’s DI container to retrieve the database context. Since the database context is scoped, it is also correct to create a new service scope to retrieve the context.
However, since the DI container is what usually manages the lifetime of the objects it creates, you should leave the disposal of the database context up to the DI container, and instead dispose of the service scope.
While it will probably not matter much in a unit test, which will be cleaned up quickly anyway (and since you are disposing the context you also won’t leak database connections), it’s still a better style and safer in the long run.
So dispose the service scope instead:
// …
httpResponse.EnsureSuccessStatusCode();
using (var scope = Host.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<MyContext>();
var item = dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord");
item.ShouldNotBeNull();
}
Improving on the implementation provided by poke, you could consider creating a delegate to handle the proper disposal of the created scope.
For example
protected void GetContext(Action<MyContext> test) {
using(var scope = _factory.Server.Host.Services.CreateScope()) {
var context = scope.ServiceProvider.GetRequiredService<MyContext>();
test(context);
}
}
When exercising your test simple call the delegate
[Theory]
[InlineData("/values/sample")]
public async Task Sample_WhenCreatingSampleData_ThenIsAddedToDatabase(string url) {
// given
var command = new AddSampleCommand { Name = "TestRecord" };
// when
var httpResponse = await Client.PostAsJsonAsync(url, command);
// then
httpResponse.EnsureSuccessStatusCode();
GetContext(dbContext => {
var item = dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord");
item.ShouldNotBeNull();
});
}
I have a WebApi controller that has services injected by AutoFac in the OWIN Startup class
builder.Register(c => new MyEntities()).InstancePerRequest();
I have also tried
builder.Register(c => new MyEntities()).InstancePerLifetimeScope();
In a controller action I call a service method to create a new record, pass the id created to an external api through HttpClient to get some more data, then update the new record with some return data.
[HttpPost, Route("")]
public async Task<IHttpActionResult> MyControllerAction(MyModel model)
{
var id = await _MyService.CreateNewThing(model.SomeId);
var externalData = await CallExternalApiThroughHttpClient(id);
await _MyService.UpdateNewThing(id, externalData);
return Ok();
}
service code
public class MyService : IMyService
{
private MyEntities _context;
public MyService(MyEntities context)
{
_context = context;
}
public async Task<int> CreateNewThing(int someId)
{
var thing = new Thing
{
SomeId = someId
};
_context.Things.Add(thing);
await _context.SaveChangesAsync();
return thing.Id;
}
public async Task UpdateNewThing(int id, string externalDataField)
{
var thing = await _context.Things.SingleOrDefaultAsync(o => o.Id == id);
if (thing == null)
{
throw new ServiceNotFoundException("Thing " + transactionId + " not found");
}
thing.ExternalDataField= externalDataField;
await _context.SaveChangesAsync();
}
}
But I get an InvalidOperationException in UpdateNewThing var thing = await _context.Things.SingleOrDefaultAsync(o => o.Id == id);
System.InvalidOperationException: The connection was not closed. The connection's current state is connecting.
It seems like I have to give up either injecting the context, async/await or use something like a contextfactory; unless anyone can spot something simple I have missed that would let me continue with this design.
Your code looks fine in a single-threaded context. However, DbContext is not thread safe, and I suspect what is happening is you're executing CreateNewThing() on one thread, and the task scheduler is in this case executing UpdateNewThing() on a different thread.
Either way, a better metaphor is to use a context factory, which you inject into your IMyService in this case, and then for every IMyService method you create a new MyEntities context in a using() block.
DbContext's are cheap to create and this is how they are intended to be used; long-lived contexts are almost always incorrect usage.
Edit 1 - example context factory as requested. I tend to implement a generic factory that can create multiple contexts, but that's probably moving outside the scope of this question.
public interface IMyEntitiesFactory
{
MyEntities Create();
}
public class MyEntitiesFactory : IMyEntitiesFactory
{
MyEntities IMyEntitiesFactory.Create()
{
return new MyEntities();
}
}
// For use with unit tests; e.g. pass a mock object to the constructor.
public class TestMyEntitiesFactory : IMyEntitiesFactory
{
private readonly MyEntities _value;
public TestMyEntitiesFactory(MyEntities value)
{
_value = value;
}
MyEntities IMyEntitiesFactory.Create()
{
return _value;
}
}