Mosta
Mosta

Reputation: 157

Server side timer with DbContext

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

Answers (2)

Mosta
Mosta

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

charlybones
charlybones

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

Related Questions