A Command Is Already In Progress - c#

I am attempting to run a background worker for a web app that I am developing. I am using Npgsql as my EF Core provider.
For clarification, I have injected my DbContext with a Transient lifetime, and have allowed Pooling in my connection string, however, whenever I try to test it I get the following error:
Npgsql.NpgsqlOperationInProgressException: A command is already in progress: [My Query Here]
I have my Program Class Set up as such:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
// Get the configuration
IConfiguration config = hostContext.Configuration;
// DbContext
services.AddDbContext<MyDbContext>(options => options.UseNpgsql(config.GetConnectionString("PostgreSQLString")), ServiceLifetime.Transient);
services.AddHostedService<Worker>();
services.AddScoped<IDTOService, BackgroundDTOService>();
});
}
Which then leads to my Worker class
public class Worker : BackgroundService
{
private Logger logger;
public Worker(IServiceProvider services, IConfiguration configuration)
{
this.Services = services;
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSQLString"));
var context = new StatPeekContext(optionsBuilder.Options);
this.logger = new Logger(new LogWriter(context));
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
this.logger.LogInformation("ExecuteAsync in Worker Service is running.");
await this.DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
using (var scope = Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
var dtoService = scope.ServiceProvider.GetRequiredService<IDTOService>();
await dtoService.ProcessJSON(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
this.logger.LogInformation("Worker service is stopping.");
await Task.CompletedTask;
}
}
which leads to my BackGroundDTOService
public class BackgroundDTOService : IDTOService
{
private int executionCount = 0;
private Logger logger;
private MyDbContext context;
private DbContextOptionsBuilder<MyDbContext> optionsBuilder;
public BackgroundDTOService(IConfiguration configuration, MyDbContext context)
{
this.optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
this.optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSQLString"));
this.logger = new Logger(new LogWriter(new MyDbContext(this.optionsBuilder.Options)));
this.context = context;
}
public async Task ProcessJSON(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
this.executionCount++;
this.logger.LogInformation($"DTO Service is working. Count: {this.executionCount}");
this.ProcessTeams();
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
}
}
public void ProcessTeams()
{
// Add Any Franchises that don't exist
var franchiseDumps = this.context.RequestDumps.Where(rd => rd.Processed == false && rd.DumpType == "leagueteams");
foreach (RequestDump teamDump in franchiseDumps)
{
var league = this.context.Leagues.Include(l => l.Franchises).FirstOrDefault(l => l.Id == teamDump.LeagueID);
var teams = Jeeves.GetJSONFromKey<List<DTOTeam>>(teamDump.JsonDump, "leagueTeamInfoList");
foreach (DTOTeam team in teams)
{
this.UpdateFranchise(team, league);
}
this.logger.LogInformation($"DTO Service Processed League Teams on count {this.executionCount}");
}
this.context.SaveChanges();
}
The error appears to occur immediately after snagging franchiseDumps when it tries to get league

Could you try materialising the query:
var franchiseDumps = this.context.RequestDumps.Where(rd => rd.Processed == false && rd.DumpType == "leagueteams").ToList();

I had the same issue and I realized that PostgreSQL doesn't support Multiple Active Result Sets (MARS) compare to MSSQL.
An alternative way that worked for me to fetch objects with foreign key:
IEnumerable<Expense> objList = _db.Expenses;
foreach (var obj in objList.ToList())
{
obj.ExpenseCategory = _db.ExpenseCategories.FirstOrDefault(u => u.Id == obj.ExpenseCategoryId);
}

Related

a consumer consumes the same message many times - masstransit

there are 2 apps which are work as consumers and producers.
app1 and app2. both of them are asp.net core web api.
app1 or app2 publish a message and both app1 and app2 consume the message.
but sometimes app1 consumes the same message many times ( from 2 to 478) but the same message is consuming by app2 just one time as expected. the consume method is very simple just one select and one insert operation.
at first, I thought after insert operation some exceptions occurred so masstransit enqueued it and tried to consume it again. but there is no code to make exceptions after insert operation. even if there is, we send exceptions to the sentry but there is nothing about exception in sentry.
according to aws sqs logs there are abnormal message counts in a specific period of time
app2's queue is normal
there are my configurations and codes below. so what could cause this?
configurations
app1
masstransit configuration
public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
{
services.AddTransient<OrderCompletedIntegrationEvent>();
var options = configuration.GetSection("AmazonSqs").Get<AmazonSqsOptions>();
var massOptions = configuration.GetSection("Masstransit").Get<MasstransitOptions>();
IBusControl CreateBus(IServiceProvider serviceProvider)
{
var secretManagerService = serviceProvider.GetService<ISecretManagerService>();
var secrets = secretManagerService.GetSecrets();
var bus = Bus.Factory.CreateUsingAmazonSqs(cfg =>
{
cfg.Host(options.Region, h =>
{
h.AccessKey(secrets.AccessKeyId);
h.SecretKey(secrets.SecretKey);
});
cfg.Durable = true;
cfg.WaitTimeSeconds = 10;
cfg.ReceiveEndpoint(options.QueueName, q =>
{
// RetryCount: 1
// RetryWaitIntervalInSeconds: 1
q.UseMessageRetry(r => r.Interval(massOptions.RetryCount, TimeSpan.FromSeconds(massOptions.RetryWaitIntervalInSeconds)));
q.ConfigureConsumer<OrderCompletedIntegrationEventHandler>(serviceProvider);
});
});
return bus;
}
// local function to configure consumers
void ConfigureMassTransit(IServiceCollectionConfigurator configurator)
{
configurator.AddConsumer<OrderCompletedIntegrationEventHandler>();
}
// configures MassTransit to integrate with the built-in dependency injection
services.AddMassTransit(configurator =>
{
configurator.AddBus(CreateBus);
ConfigureMassTransit(configurator);
});
services.AddMassTransitHostedService();
return services;
}
consumer
public class OrderCompletedIntegrationEventHandler : IConsumer<OrderCompletedIntegrationEvent>
{
private readonly MasstransitOptions options;
private readonly IServiceProvider serviceProvider;
private readonly ICoinService coinService;
private readonly IAccountService accountService;
private readonly ILogger<OrderCompletedIntegrationEventHandler> _logger;
public OrderCompletedIntegrationEventHandler(
IConfiguration configuration,
IServiceProvider serviceProvider,
ICoinService coinService,
IAccountService accountService)
{
options = configuration.GetSection("Masstransit").Get<MasstransitOptions>();
this.serviceProvider = serviceProvider;
this.coinService = coinService;
this.accountService = accountService;
}
public async Task Consume(ConsumeContext<OrderCompletedIntegrationEvent> context)
{
var #event = context.Message;
var retry = context.GetRetryAttempt();
try
{
await Task.Run(() =>
{
var coin = coinService.TlToCoin(#event.Total);
var coinToAdd = coin * 25 / 1000;
var user = accountService.FindByEmail(#event.Email);
if (user != null) coinService.AddBonus(user.Id, coinToAdd);
});
}
catch (Exception ex)
{
if (retry == options.RetryCount)
{
using (var scope = serviceProvider.CreateScope())
{
var hub = scope.ServiceProvider.GetService<IHub>();
hub.CaptureException(new IntegrationEventException(data: #event, ex));
}
}
throw ex;
}
}
}
app2
masstransit configuration
public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<OrderCompletedIntegrationEventHandler>();
var options = configuration.GetSection("AmazonSqs").Get<AmazonSqsOptions>();
var massOptions = configuration.GetSection("Masstransit").Get<MasstransitOptions>();
IBusControl CreateBus(IServiceProvider serviceProvider)
{
return Bus.Factory.CreateUsingAmazonSqs(cfg =>
{
cfg.UseAmazonSqsMessageScheduler();
cfg.Host(options.Region, h =>
{
h.AccessKey(options.AccessKeyId);
h.SecretKey(options.SecretKey);
});
cfg.Durable = true;
cfg.WaitTimeSeconds = 20;
cfg.ReceiveEndpoint(options.QueueName, q =>
{
// RetryCount: 5
// RetryWaitIntervalInSeconds: 300
q.UseMessageRetry(r => r.Interval(massOptions.RetryCount, TimeSpan.FromSeconds(massOptions.RetryWaitIntervalInSeconds)));
q.ConfigureConsumer<OrderCompletedIntegrationEventHandler>(serviceProvider);
});
});
}
// local function to configure consumers
void ConfigureMassTransit(IServiceCollectionConfigurator configurator)
{
configurator.AddConsumer<OrderCompletedIntegrationEventHandler>();
}
//configures MassTransit to integrate with the built -in dependency injection
services.AddMassTransit(x =>
{
x.AddBus(CreateBus);
ConfigureMassTransit(x);
});
services.AddMassTransitHostedService();
return services;
}
consumer
public class OrderCompletedIntegrationEventHandler : IConsumer<OrderCompletedIntegrationEvent>
{
private readonly MasstransitOptions options;
private readonly IServiceProvider serviceProvider;
private IUserService _userService;
private IScoreService _scoreService;
public OrderCompletedIntegrationEventHandler(
IConfiguration configuration,
IServiceProvider serviceProvider, IUserService userService, IScoreService scoreService)
{
options = configuration.GetSection("Masstransit").Get<MasstransitOptions>();
this.serviceProvider = serviceProvider;
this._userService = userService;
this._scoreService = scoreService;
}
public async Task Consume(ConsumeContext<OrderCompletedIntegrationEvent> context)
{
var #event = context.Message;
var retry = context.GetRetryAttempt();
try
{
var user = _userService.GetUserByEmail(#event.Email);
if (user != null)
{
var activity = _scoreService.GetActivityTypeByName("buy-book");
var scoreToAdd = Convert.ToInt32(#event.Total * 150);
var coinToAdd = Convert.ToInt32(#event.Total * 100 * 2.5M / 100);
var scoreModel = new ScoreModel
{
ActivityType = new ActivityType
{
Id = activity.Id
},
Name = activity.Name,
Description = $"{#event.Total:F2} TL'lik bir sipariş verildi. Sipariş no: {#event.OrderId}",
UserId = (int)user.ID,
BranchId = null,
GainedScore = scoreToAdd,
GainedCoins = coinToAdd,
CreateDate = DateTime.UtcNow.TurkeyTime()
};
await _scoreService.AddActivity(scoreModel);
}
}
catch (System.Exception ex)
{
if (retry == options.RetryCount)
{
using (var scope = serviceProvider.CreateScope())
{
var hub = scope.ServiceProvider.GetService<IHub>();
hub.CaptureException(ex);
}
}
throw ex;
}
}
}

TypeLoadException when injecting Context in Worker

I currently have a Razor page application (Core 3.1) connected to a SQL Database with a Context using IdentityDbContext. When trying to use this same context in my Worker Service, I continue to get this error:
System.TypeLoadException: 'Method 'Create' in type
'Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerSqlTranslatingExpressionVisitorFactory'
from assembly 'Microsoft.EntityFrameworkCore.SqlServer,
Version=3.1.10.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'
does not have an implementation.'
Here is my Context
public class WarzoneTrackerContext : IdentityDbContext<IdentityUser>
{
public WarzoneTrackerContext(DbContextOptions<WarzoneTrackerContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//if (!optionsBuilder.IsConfigured)
//{
// optionsBuilder.UseSqlServer("data source=SQLCLDevComm;initial catalog=ComQueMDS;integrated security=True;");
//}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//modelBuilder.Entity<Player>().HasData(Seed.Players());
foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
modelBuilder.HasDefaultSchema("dbo");
base.OnModelCreating(modelBuilder);
}
public DbSet<Player> Players { get; set; }
public DbSet<Match> Matches { get; set; }
public DbSet<PlayerLifetimeStats> PlayerLifetimeStats { get; set; }
public DbSet<PlayerMatchStats> PlayerMatchStats { get; set; }
} // End of Class
Here is my Program.cs
public class Program
{
public static Microsoft.Extensions.Configuration.IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", optional: true)
.Build();
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
})
.ConfigureServices((hostContext, services) =>
{
IConfiguration configuration = hostContext.Configuration;
var optionsBuilder = new DbContextOptionsBuilder<WarzoneTrackerContext>();
services.AddTransient<IMatchManager, MatchManager>();
services.AddTransient<IPlayerManager, PlayerManager>();
services.AddTransient<IRapidManager, RapidManager>();
//services.AddTransient<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
//services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false).AddRoles<IdentityRole>()
// .AddEntityFrameworkStores<WarzoneTrackerContext>();
//services.AddScoped<WarzoneTrackerContext>(s => new WarzoneTrackerContext(optionsBuilder.Options));
//services.AddScoped<WarzoneTrackerContext>(services => new WarzoneTrackerContext(optionsBuilder.Options));
services.AddHostedService<WarzoneWorker>();
services.AddDbContext<WarzoneTrackerContext>(options => options.UseSqlServer(Configuration.GetConnectionString("WarzoneConnection")));
}).UseWindowsService();
}
And here is my worker
public class WarzoneWorker : BackgroundService
{
private readonly IServiceProvider services;
private readonly ILogger<WarzoneWorker> _logger;
public WarzoneWorker(IServiceProvider _services, ILogger<WarzoneWorker> logger)
{
services = _services;
_logger = logger;
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
await base.StartAsync(cancellationToken);
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
// DO YOUR STUFF HERE
await base.StopAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<WarzoneTrackerContext>();
var matchManager = scope.ServiceProvider.GetService<IMatchManager>();
var playerManager = scope.ServiceProvider.GetService<IPlayerManager>();
var rapidManager = scope.ServiceProvider.GetService<IRapidManager>();
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(300000, stoppingToken);
}
}
}
public override void Dispose()
{
// DO YOUR STUFF HERE
}
}
Hopefully someone can help walk me through this.
Please go to Package Manager Console and run the dotnet restore command.
Check the versions of the EF packages you are using. Check your package versions in .csproj. If you are using preview versions, upgrade all packages to the latest stable version.

In a .net core 3.0 BackgroundService app, why is my configuration object empty when running as service, but not as console app?

I have a .net Core 3.0 BackgroundService application that works fine when running in console mode, but once i deploy as a service the configuration object that should be loaded from appsettings.json is empty. What gives?
Program.cs
public class Program
{
public static async System.Threading.Tasks.Task Main(string[] args)
{
var hostbuilder = new HostBuilder()
.UseWindowsService()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location))
.AddJsonFile("appsettings.json");
})
.ConfigureLogging(
options => options.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Information))
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Importer>().Configure<EventLogSettings>(config =>
{
config.LogName = "Application";
config.SourceName = "Importer";
});
});
#if (DEBUG)
await hostbuilder.RunConsoleAsync();
#else
await hostbuilder.RunAsServiceAsync();
#endif
}
}
Extension Method for IhostBuilder to run service
public static class ServiceBaseLifetimeHostExtensions
{
public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
}
public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
{
return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
}
}
ServiceBaseLifetime class to handle service lifecycle
public class ServiceBaseLifetime : ServiceBase, IHostLifetime
{
private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
public ServiceBaseLifetime(IHostApplicationLifetime applicationLifetime)
{
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
}
private IHostApplicationLifetime ApplicationLifetime { get; }
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
cancellationToken.Register(() => _delayStart.TrySetCanceled());
ApplicationLifetime.ApplicationStopping.Register(Stop);
new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
return _delayStart.Task;
}
private void Run()
{
try
{
Run(this); // This blocks until the service is stopped.
_delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
}
catch (Exception ex)
{
_delayStart.TrySetException(ex);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
Stop();
return Task.CompletedTask;
}
// Called by base.Run when the service is ready to start.
protected override void OnStart(string[] args)
{
_delayStart.TrySetResult(null);
base.OnStart(args);
}
// Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync.
// That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion.
protected override void OnStop()
{
ApplicationLifetime.StopApplication();
base.OnStop();
}
}
The actual implementation of the service is irrelevant other than the constructor, which takes the logger and configuration through DI.
private readonly ILogger<Importer> _logger;
private readonly IConfiguration _configuration;
public Importer(IConfiguration configuration, ILogger<Importer> logger)
{
_logger = logger;
_configuration = configuration;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"Why is {_configuration["Key1"]} empty?");
}
appsettings.json
{
"Key1":"some value"
}
When i run through debug the console app starts up and runs and logs and has the configuration loaded from appsettings. When i deploy as a service the configuration object is empty.
Notes: The appsettings file is being read, i can tell this by changing the name of it and it throws an exception for file not found. The appsettings file is also not empty.
My issue appears to be some kind of async race condition problem (I am guessing, not positive). The first tick through ExecuteAsync the configuration is not loaded, but the second time through it is. I had the service dying if it encountered that exception, so i never got it to tick a second time.
This appears to be an XY problem and is worthy of a refactor
Create a strongly typed model to hold the desired settings
public class ImporterSettings {
public string Key1 { get; set; }
}
Refactor hosted service to depend on the settings since tightly coupling services to IConfiguration is a code smell in my opinion
private readonly ILogger<Importer> _logger;
private readonly ImporterSettnigs settings;
public Importer(ImporterSettnigs settings, ILogger<Importer> logger) {
_logger = logger;
this.settings = settings;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
_logger.LogInformation($"This is Key1: {settings.Key1}");
}
Now properly configure start up to use the provided configuration
public class Program {
public static async System.Threading.Tasks.Task Main(string[] args) {
var hostbuilder = new HostBuilder()
.UseWindowsService()
.ConfigureAppConfiguration((hostingContext, config) => {
var path = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
config
.SetBasePath(path)
.AddJsonFile("appsettings.json");
})
.ConfigureLogging(
options => options.AddFilter<EventLogLoggerProvider>(level =>
level >= LogLevel.Information)
)
.ConfigureServices((hostContext, services) => {
//get settings from app configuration.
ImporterSettings settings = hostContext.Configuration.Get<ImporterSettings>();
services
.AddSingleton(settings) //add to service collection
.AddHostedService<Importer>()
.Configure<EventLogSettings>(config => {
config.LogName = "Application";
config.SourceName = "Importer";
});
});
#if (DEBUG)
await hostbuilder.RunConsoleAsync();
#else
await hostbuilder..Build().RunAsync();
#endif
}
}

Entity framework with Scoped Hosted Services

I have the following interface
internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
and implementation
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await Task.CompletedTask;
}
}
This code is from official Microsoft documentation. Background scoped services
And like in the documentation I have ScopedProcessingService but a few more difficult. Here is the code:
internal class ScopedProcessingService : IScopedProcessingService
{
private int _executionCount;
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
private readonly IPushRepository _pushRepository;
private readonly IPushTemplateRepository _pushTemplateRepository;
private readonly ISenderLogRepository _senderLogRepository;
private readonly IDistributionRepository _distributionRepository;
// services
private readonly IPushTemplateService _pushTemplateService;
private readonly ISendPushService _sendPushService;
public ScopedProcessingService(
ILogger<ConsumeScopedServiceHostedService> logger,
IPushTemplateService pushTemplateService, ISendPushService sendPushService,
IPushRepository pushRepository,
ISenderLogRepository senderLogRepository, IDistributionRepository distributionRepository,
IPushTemplateRepository pushTemplateRepository)
{
_logger = logger;
_pushTemplateService = pushTemplateService;
_sendPushService = sendPushService;
_pushRepository = pushRepository;
_senderLogRepository = senderLogRepository;
_distributionRepository = distributionRepository;
_pushTemplateRepository = pushTemplateRepository;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_executionCount = _senderLogRepository.SenderLogs.Count();
var logMessage = new StringBuilder();
logMessage.AppendLine($"Начинаю рассылку № {_executionCount}.");
// get all templates. THIS CALL IS A SOURCE OF PROBLEMS
var templates = _pushTemplateRepository.PushTemplates.Where(x => x.isActive)
.Include(x => x.Messages)
.ThenInclude(x => x.PushLang)
.Include(x => x.Category)
.Include(x => x.AdvertiserPushTemplates)
.ThenInclude(x => x.Advertiser)
.ToList();
}
}
In the Startup.cs class I use the following code to inject it:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
The problem with this line var templates = _pushTemplateRepository.PushTemplates.Where(x => x.isActive). If I make some changes with a PushTemplate this changes will not have effect in the background task. And I will process old data. I mean, If I change name
for a PushTemplate, for example, with id = 15 from Name_1 to Name_2 than I will have Name_1 in the background task.
How to inject EF in a Scoped background service correctly? I not use clear EF context. I have repository layer.
public interface IPushTemplateRepository
{
IQueryable<PushTemplate> PushTemplates { get; }
void Save(PushTemplate pushTemplate);
void Delete(int templateid);
}
And implementation
public class PushTemplateRepository : IPushTemplateRepository
{
private readonly ApplicationDbContext _applicationContext;
public PushTemplateRepository(ApplicationDbContext applicationContext)
{
_applicationContext = applicationContext;
}
public IQueryable<PushTemplate> PushTemplates => _applicationContext.PushTemplates;
public void Save(PushTemplate pushTemplate)
{
// ...
}
public void Delete(int templateid)
{
// ...
}
}
The issue is the captured DbContext in that single scope that has an infinite loop.
The scope is never disposed so will retain the data it had when the scope was created.
Refactor to move the loop out a level and create a new scope each time the desired functionality is required.
ConsumeScopedServiceHostedService
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
_logger.LogInformation("Consume Scoped Service Hosted Service is working.");
while (!stoppingToken.IsCancellationRequested) {
using (var scope = Services.CreateScope()) {
IServiceProvider serviceProvider = scope.ServiceProvider;
var service = serviceProvider.GetRequiredService<IScopedProcessingService>();
await service.DoWork(stoppingToken);
}
//Add a delay between executions.
await Task.Delay(SomeIntervalBetweenCalls, stoppingToken);
}
}
ScopedProcessingService
//...
public async Task DoWork(CancellationToken stoppingToken) {
_executionCount = _senderLogRepository.SenderLogs.Count();
var logMessage = new StringBuilder();
logMessage.AppendLine($"Начинаю рассылку № {_executionCount}.");
// get all templates.
var templates = _pushTemplateRepository.PushTemplates.Where(x => x.isActive)
.Include(x => x.Messages)
.ThenInclude(x => x.PushLang)
.Include(x => x.Category)
.Include(x => x.AdvertiserPushTemplates)
.ThenInclude(x => x.Advertiser)
.ToList();
//...
}

asp.net core - Integration test and view components

I'm facing a strange issue since I created a view component in my app.
All my integration tests using an HttpClient start failing with response code "Internal Server Error".
Here the test configuration :
public class BaseTest<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
private readonly bool _hasUser;
private readonly HttpClient _client;
protected BaseTest(bool hasUser = false)
{
_hasUser = hasUser;
_client = CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
}
protected async Task<HttpResponseMessage> GetPageByPath(string path)
{
return await _client.GetAsync(path);
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
if (_hasUser)
{
services.AddScoped<IAuthenticationService, AuthenticationServiceStub>();
services.AddSingleton<IStartupFilter, FakeUserFilter>();
services.AddMvc(options =>
{
options.Filters.Add(new AllowAnonymousFilter());
options.Filters.Add(new AuthenticatedAttribute());
});
}
});
builder.ConfigureServices(services =>
{
// Create a new service provider.
ServiceProvider serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database
// context (ApplicationDbContext).
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var logger = scopedServices
.GetRequiredService<ILogger<BaseTest<TStartup>>>();
}
});
}
}
}
And a usage example :
public class BasicTest : BaseTest<Startup>
{
public BasicTest() : base()
{
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/Users/SignOut")]
[Trait("Category", "Integration")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Act
var response = await GetPageByPath(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
If you need the component code let me know.
I already rollback code to check when this start happening, and it's start after the commit with the new Component called in several pages.

Categories