Reputation: 157
I want to make a timer running in the server that runs a method() every 60 seconds
now i have done -sort of- that using the code below
public class Alarm
{
public Alarm(AppDbContext _db)
{
db = _db;
}
private static Timer aTimer;
private AppDbContext db;
public void StartTimerEvent()
{
// Create a timer and set a 60 second interval.
aTimer = new Timer();
aTimer.Interval = 60000;
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += (source, e) => CheckDBEvents(source, e);
// Have the timer fire repeated events (true is the default)
aTimer.AutoReset = true;
// Start the timer
aTimer.Enabled = true;
}
private void CheckDBEvents(Object source, ElapsedEventArgs e)
{
//get data from db with matching queries
List<Grocery> DataList = db.Grocery.Where(G => G.basic).Select(G => new Grocery
{
Id = G.Id,
Timeout = G.Timeout
}).ToList();
}
}
the method() is CheckDBEvents() and what it does is it accesses the dbcontext instance and looks for some data to save to to a local constant variable Called DataList
problem : every time i try passing the context (Dbcontext) instance --in the controller or any other class-- to the CheckDBEvents() method, the context is disposed -DbContext Disposed Exception. the caller
var t = new Alarm(db);
t.StartTimerEvent();
My tires :-
Now if i can do that It would be amazing ... but can not operate on DbContext since you can't call instance on DbContext in a static class you have to pass it from who ever calls it, which leads to the same problem :db is Disposed and don't get passed
public static void StartTimerEvent(AppDbContext db)
{
.....
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += (source, e) => CheckDBEvents(source, e, db
//DbContext is Disposed here and
//don't get passed'
);
also constant classes and Dbcontext don't get along very well from what i have read.
Long Story Short -I want to keep instance of dbContext in another class rather than controllers. without it being disposed.
if anyone have and idea what how to do this or have a source code to server side timer or anything like that please comment, I have been stuck for 2 days
Upvotes: 2
Views: 2303
Reputation: 157
finally I have found the problem after many test,
I needed to take advantage of the strong Dependency Injection that asp.net has, and add the class as a service. Also, I used IHostedService
as an interface for my service class, here is an example of the service FinalTest (renamed Alarm to FinalTest)
internal class FinalTest : IHostedService, IDisposable
{
private Timer aTimer;
public static List<Grocery> List;
private AppDbContext db;
// variable to test that timer really works
public static int test2;
public FinalTest( AppDbContext _db )
{
db = _db;
}
//This method runs at the start of the application once only as FinalTest was set as Singleton in services
public Task StartAsync(CancellationToken cancellationToken)
{
test2 = 1;
aTimer =new Timer(CheckDBEvents, null , TimeSpan.Zero , TimeSpan.FromSeconds(10) );
return Task.CompletedTask;
}
//Method runs every TimeSpan.FromSeconds(10)
private void CheckDBEvents(object state)
{
var users = from u in db.Grocery where u.basic == true select u;
List = users.ToList();
//increase test2 To see changes in the real world
test2++;
}
//--------shutdown operations---------//
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public void Dispose()
{
aTimer?.Dispose();
}
}
Now if I injected this in the service services.AddSingleton(FinalTest)
as it is I would get a scoped exception because using AppDbContext which is a scoped service in Singleton service is not good and effectively promote AppDbContext to Singleton which is goint to cause problems in the future, so I had to create another constructor for AppDbContext.
public class AppDbContext : DbContext
{
//Scoped constructor
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
//Singletone constructor
public AppDbContext(DbContextOptions<AppDbContext> options,string connection)
{
connectionString = connection;
}
private string connectionString;
//this is an override to OnConfiguring that's
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (connectionString != null)
{
var config = connectionString;
optionsBuilder.UseSqlServer(config);
}
base.OnConfiguring(optionsBuilder);
}
//DbSet
public DbSet<Grocery> Grocery { get; set; }
}
finally the adding both AppDbContext
and FinalTest
to services
var connection = @"Server=(localdb)\mssqllocaldb;Database=FridgeServer.AspNetCore.NewDb;Trusted_Connection=True;ConnectRetryCount=0";
services.AddDbContext<AppDbContext>(
options => {
//options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
options.UseSqlServer(connection);
//var config = Configuration["Data:DefaultConnection:ConnectionString"];
//options.UseInMemoryDatabase("Grocery")
}
);
services.AddSingleton<IHostedService,FinalTest>(s => new FinalTest(new AppDbContext(null, connection) ));
Now that's my experience and it was all in all a fun experience reading all about Dependency injection and Ioc and other concept and pattern of programing if anyone face some of those problem or even want to know more about asp.net, here are some that helped ,the first one is the most important
http://deviq.com/category/principles/ http://deviq.com/dependency-inversion-principle/ https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1 Use DbContext in ASP .Net Singleton Injected Class https://blogs.msdn.microsoft.com/cesardelatorre/2017/11/18/implementing-background-tasks-in-microservices-with-ihostedservice-and-the-backgroundservice-class-net-core-2-x/
thanks to @KienChu for telling me about IHostedService , I Hope this helps someone
Upvotes: 4
Reputation: 339
I would be weary of injecting DbContext directy into the hosted service.
My advice would be to inject IServiceScopeFactory
. And every time your alarm goes off, create a scope, and resolve the DbContext just for that CheckDBEvents
action.
Even your links to other questions recommend doing so. This way you manage the DbContext lifetime a bit more gracefully.
Something like this:
var scope = serviceFactory.CreateScope();
await using var dbContext = scope.ServiceProvider.GetService<MyDbContext>();
Upvotes: 1