From 420b4452ec2534e0122cda264ab1e7e9a08a5b84 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 19 Jan 2026 15:04:18 +0800 Subject: [PATCH 1/3] Add Hangfire periodic worker adapter options. https://github.com/abpframework/abp/pull/24666 --- ...ePeriodicBackgroundWorkerAdapterOptions.cs | 10 ++++ .../HangfireBackgroundWorkerManager.cs | 37 ++++++++------ ...HangfirePeriodicBackgroundWorkerAdapter.cs | 19 ++++--- .../DemoAppHangfireModule.cs | 33 ++++++++++++- .../TestWorker.cs | 22 +++++++++ ...Abp.BackgroundJobs.DemoApp.HangFire.csproj | 2 + .../Migrations/20201013055401_Initial.cs | 41 ---------------- ....cs => 20260119064307_Initial.Designer.cs} | 40 +++++++++------ .../Migrations/20260119064307_Initial.cs | 49 +++++++++++++++++++ .../DemoAppDbContextModelSnapshot.cs | 37 ++++++++------ 10 files changed, 194 insertions(+), 96 deletions(-) create mode 100644 framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs create mode 100644 modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/TestWorker.cs delete mode 100644 modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20201013055401_Initial.cs rename modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/{20201013055401_Initial.Designer.cs => 20260119064307_Initial.Designer.cs} (73%) create mode 100644 modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20260119064307_Initial.cs diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs new file mode 100644 index 0000000000..8d2aa0cc22 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.BackgroundWorkers.Hangfire; + +public class AbpHangfirePeriodicBackgroundWorkerAdapterOptions +{ + public TimeZoneInfo TimeZone { get; set; } = TimeZoneInfo.Utc; + + public string Queue { get; set; } = null!; +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs index fe9a8ad983..0822d18131 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Hangfire; using Hangfire.Common; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; @@ -30,7 +31,7 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet BackgroundJobServer = ServiceProvider.GetRequiredService(); } - public async override Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default) + public override async Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default) { var abpHangfireOptions = ServiceProvider.GetRequiredService>().Value; var defaultQueuePrefix = abpHangfireOptions.DefaultQueuePrefix; @@ -57,27 +58,31 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet case AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase: { int? period = null; - string? CronExpression = null; + string? cronExpression = null; - if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase) + switch (worker) { - period = asyncPeriodicBackgroundWorkerBase.Period; - CronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression; - } - else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase) - { - period = periodicBackgroundWorkerBase.Period; - CronExpression = periodicBackgroundWorkerBase.CronExpression; + case AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase: + period = asyncPeriodicBackgroundWorkerBase.Period; + cronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression; + break; + case PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase: + period = periodicBackgroundWorkerBase.Period; + cronExpression = periodicBackgroundWorkerBase.CronExpression; + break; } - if (period == null && CronExpression.IsNullOrWhiteSpace()) + if (period == null && cronExpression.IsNullOrWhiteSpace()) { + var logger = ServiceProvider.GetRequiredService>(); + logger.LogError( + $"Cannot add periodic background worker {worker.GetType().FullName} to Hangfire scheduler, because both Period and CronExpression are not set. " + + "You can either set Period or CronExpression property of the worker." + ); return; } - var adapterType = typeof(HangfirePeriodicBackgroundWorkerAdapter<>).MakeGenericType(ProxyHelper.GetUnProxiedType(worker)); - var workerAdapter = (Activator.CreateInstance(adapterType) as IHangfireBackgroundWorker)!; - + var workerAdapter = (ServiceProvider.GetRequiredService(typeof(HangfirePeriodicBackgroundWorkerAdapter<>).MakeGenericType(ProxyHelper.GetUnProxiedType(worker))) as IHangfireBackgroundWorker)!; Expression> methodCall = () => workerAdapter.DoWorkAsync(cancellationToken); var recurringJobId = !workerAdapter.RecurringJobId.IsNullOrWhiteSpace() ? workerAdapter.RecurringJobId : GetRecurringJobId(worker, methodCall); @@ -85,7 +90,7 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet recurringJobId, workerAdapter.Queue.IsNullOrWhiteSpace() ? defaultQueue : defaultQueuePrefix + workerAdapter.Queue, methodCall, - CronExpression ?? GetCron(period!.Value), + cronExpression ?? GetCron(period!.Value), new RecurringJobOptions { TimeZone = workerAdapter.TimeZone @@ -98,7 +103,7 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet } } - private readonly static MethodInfo? GetRecurringJobIdMethodInfo = typeof(RecurringJob).GetMethod("GetRecurringJobId", BindingFlags.NonPublic | BindingFlags.Static); + private static readonly MethodInfo? GetRecurringJobIdMethodInfo = typeof(RecurringJob).GetMethod("GetRecurringJobId", BindingFlags.NonPublic | BindingFlags.Static); protected virtual string? GetRecurringJobId(IBackgroundWorker worker, Expression> methodCall) { string? recurringJobId = null; diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfirePeriodicBackgroundWorkerAdapter.cs b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfirePeriodicBackgroundWorkerAdapter.cs index 43e9b4a95c..cf5945aaf1 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfirePeriodicBackgroundWorkerAdapter.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfirePeriodicBackgroundWorkerAdapter.cs @@ -1,7 +1,9 @@ -using System.Reflection; +using System; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Volo.Abp.BackgroundWorkers.Hangfire; @@ -11,14 +13,17 @@ public class HangfirePeriodicBackgroundWorkerAdapter : HangfireBackgrou private readonly MethodInfo _doWorkAsyncMethod; private readonly MethodInfo _doWorkMethod; - public HangfirePeriodicBackgroundWorkerAdapter() + public HangfirePeriodicBackgroundWorkerAdapter(IOptions options) { + TimeZone = options.Value.TimeZone; + Queue = options.Value.Queue; + RecurringJobId = BackgroundWorkerNameAttribute.GetNameOrNull(); + _doWorkAsyncMethod = typeof(TWorker).GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic)!; _doWorkMethod = typeof(TWorker).GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic)!; - RecurringJobId = BackgroundWorkerNameAttribute.GetNameOrNull(); } - public async override Task DoWorkAsync(CancellationToken cancellationToken = default) + public override async Task DoWorkAsync(CancellationToken cancellationToken = default) { var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider, cancellationToken); var worker = ServiceProvider.GetRequiredService(); @@ -26,13 +31,11 @@ public class HangfirePeriodicBackgroundWorkerAdapter : HangfireBackgrou switch (worker) { case AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorker: - await (Task)(_doWorkAsyncMethod.Invoke(asyncPeriodicBackgroundWorker, new object[] { workerContext })!); + await (Task)(_doWorkAsyncMethod.Invoke(asyncPeriodicBackgroundWorker, [workerContext])!); break; case PeriodicBackgroundWorkerBase periodicBackgroundWorker: - _doWorkMethod.Invoke(periodicBackgroundWorker, new object[] { workerContext }); + _doWorkMethod.Invoke(periodicBackgroundWorker, [workerContext]); break; } } - - } diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/DemoAppHangfireModule.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/DemoAppHangfireModule.cs index cd74c10f82..61a034c0a0 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/DemoAppHangfireModule.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/DemoAppHangfireModule.cs @@ -1,17 +1,23 @@ -using Hangfire; +using System; +using System.Threading.Tasks; +using Hangfire; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Autofac; using Volo.Abp.BackgroundJobs.DemoApp.Shared; using Volo.Abp.Modularity; using Microsoft.Extensions.Configuration; using Volo.Abp.BackgroundJobs.Hangfire; +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.BackgroundWorkers.Hangfire; +using Volo.Abp.Hangfire; namespace Volo.Abp.BackgroundJobs.DemoApp.HangFire; [DependsOn( typeof(DemoAppSharedModule), typeof(AbpAutofacModule), - typeof(AbpBackgroundJobsHangfireModule) + typeof(AbpBackgroundJobsHangfireModule), + typeof(AbpBackgroundWorkersHangfireModule) )] public class DemoAppHangfireModule : AbpModule { @@ -24,4 +30,27 @@ public class DemoAppHangfireModule : AbpModule hangfireConfiguration.UseSqlServerStorage(configuration.GetConnectionString("Default")); }); } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.ServerOptions = new BackgroundJobServerOptions + { + Queues = new []{ "default", "my-default" } + }; + }); + + Configure(options => + { + options.TimeZone = TimeZoneInfo.Local; + options.Queue = "my-default"; + }); + } + + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + var backgroundWorkerManager = context.ServiceProvider.GetRequiredService(); + await backgroundWorkerManager.AddAsync(context.ServiceProvider.GetRequiredService()); + } } diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/TestWorker.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/TestWorker.cs new file mode 100644 index 0000000000..60d7f7d365 --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/TestWorker.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.Threading; + +namespace Volo.Abp.BackgroundJobs.DemoApp.HangFire; + +public class TestWorker : AsyncPeriodicBackgroundWorkerBase +{ + public TestWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory) + : base(timer, serviceScopeFactory) + { + CronExpression = Cron.Minutely(); + } + + protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) + { + Console.WriteLine($"[{DateTime.Now}] TestWorker executed."); + } +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj index 3d145c93ef..c59e3afac9 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj @@ -7,8 +7,10 @@ + + diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20201013055401_Initial.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20201013055401_Initial.cs deleted file mode 100644 index aa50d3f32a..0000000000 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20201013055401_Initial.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations; - -public partial class Initial : Migration -{ - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AbpBackgroundJobs", - columns: table => new { - Id = table.Column(nullable: false), - ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), - JobName = table.Column(maxLength: 128, nullable: false), - JobArgs = table.Column(maxLength: 1048576, nullable: false), - TryCount = table.Column(nullable: false, defaultValue: (short)0), - CreationTime = table.Column(nullable: false), - NextTryTime = table.Column(nullable: false), - LastTryTime = table.Column(nullable: true), - IsAbandoned = table.Column(nullable: false, defaultValue: false), - Priority = table.Column(nullable: false, defaultValue: (byte)15) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpBackgroundJobs", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_AbpBackgroundJobs_IsAbandoned_NextTryTime", - table: "AbpBackgroundJobs", - columns: new[] { "IsAbandoned", "NextTryTime" }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AbpBackgroundJobs"); - } -} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20201013055401_Initial.Designer.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20260119064307_Initial.Designer.cs similarity index 73% rename from modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20201013055401_Initial.Designer.cs rename to modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20260119064307_Initial.Designer.cs index 35129e8877..3225815926 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20201013055401_Initial.Designer.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20260119064307_Initial.Designer.cs @@ -8,20 +8,24 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Volo.Abp.BackgroundJobs.DemoApp.Db; using Volo.Abp.EntityFrameworkCore; +#nullable disable + namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations { [DbContext(typeof(DemoAppDbContext))] - [Migration("20201013055401_Initial")] + [Migration("20260119064307_Initial")] partial class Initial { + /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.8") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasAnnotation("ProductVersion", "10.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => { @@ -29,19 +33,25 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); + b.Property("ApplicationName") + .HasMaxLength(96) + .HasColumnType("nvarchar(96)"); + b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") + .IsRequired() + .HasMaxLength(40) .HasColumnType("nvarchar(40)") - .HasMaxLength(40); + .HasColumnName("ConcurrencyStamp"); b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); b.Property("IsAbandoned") .ValueGeneratedOnAdd() @@ -50,13 +60,13 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations b.Property("JobArgs") .IsRequired() - .HasColumnType("nvarchar(max)") - .HasMaxLength(1048576); + .HasMaxLength(1048576) + .HasColumnType("nvarchar(max)"); b.Property("JobName") .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); b.Property("LastTryTime") .HasColumnType("datetime2"); @@ -78,7 +88,7 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations b.HasIndex("IsAbandoned", "NextTryTime"); - b.ToTable("AbpBackgroundJobs"); + b.ToTable("AbpBackgroundJobs", (string)null); }); #pragma warning restore 612, 618 } diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20260119064307_Initial.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20260119064307_Initial.cs new file mode 100644 index 0000000000..ab0f0d7f37 --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20260119064307_Initial.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AbpBackgroundJobs", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ApplicationName = table.Column(type: "nvarchar(96)", maxLength: 96, nullable: true), + JobName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + JobArgs = table.Column(type: "nvarchar(max)", maxLength: 1048576, nullable: false), + TryCount = table.Column(type: "smallint", nullable: false, defaultValue: (short)0), + CreationTime = table.Column(type: "datetime2", nullable: false), + NextTryTime = table.Column(type: "datetime2", nullable: false), + LastTryTime = table.Column(type: "datetime2", nullable: true), + IsAbandoned = table.Column(type: "bit", nullable: false, defaultValue: false), + Priority = table.Column(type: "tinyint", nullable: false, defaultValue: (byte)15), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpBackgroundJobs", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpBackgroundJobs_IsAbandoned_NextTryTime", + table: "AbpBackgroundJobs", + columns: new[] { "IsAbandoned", "NextTryTime" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpBackgroundJobs"); + } + } +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs index 47ee56f4bb..ab91bc354c 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs @@ -7,6 +7,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Volo.Abp.BackgroundJobs.DemoApp.Db; using Volo.Abp.EntityFrameworkCore; +#nullable disable + namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations { [DbContext(typeof(DemoAppDbContext))] @@ -17,9 +19,10 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.8") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasAnnotation("ProductVersion", "10.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => { @@ -27,19 +30,25 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); + b.Property("ApplicationName") + .HasMaxLength(96) + .HasColumnType("nvarchar(96)"); + b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") + .IsRequired() + .HasMaxLength(40) .HasColumnType("nvarchar(40)") - .HasMaxLength(40); + .HasColumnName("ConcurrencyStamp"); b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); b.Property("IsAbandoned") .ValueGeneratedOnAdd() @@ -48,13 +57,13 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations b.Property("JobArgs") .IsRequired() - .HasColumnType("nvarchar(max)") - .HasMaxLength(1048576); + .HasMaxLength(1048576) + .HasColumnType("nvarchar(max)"); b.Property("JobName") .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); b.Property("LastTryTime") .HasColumnType("datetime2"); @@ -76,7 +85,7 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations b.HasIndex("IsAbandoned", "NextTryTime"); - b.ToTable("AbpBackgroundJobs"); + b.ToTable("AbpBackgroundJobs", (string)null); }); #pragma warning restore 612, 618 } From 47dcccf771bea5805023e33b8f00390b4d69d5df Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Mon, 19 Jan 2026 15:19:43 +0800 Subject: [PATCH 2/3] Update framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs index 8d2aa0cc22..2df335abd8 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpHangfirePeriodicBackgroundWorkerAdapterOptions.cs @@ -6,5 +6,5 @@ public class AbpHangfirePeriodicBackgroundWorkerAdapterOptions { public TimeZoneInfo TimeZone { get; set; } = TimeZoneInfo.Utc; - public string Queue { get; set; } = null!; + public string Queue { get; set; } = default!; } From 5c41632d84ec654b85589133fd1c30277562171b Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 20 Jan 2026 12:06:37 +0800 Subject: [PATCH 3/3] Handle Hangfire storage without JobQueueProperty feature. --- .../HangfireBackgroundWorkerManager.cs | 80 +++++++++++++------ 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs index 0822d18131..64a4a1be64 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Hangfire; using Hangfire.Common; +using Hangfire.Storage; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -33,6 +34,7 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet public override async Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default) { + var logger = ServiceProvider.GetRequiredService>(); var abpHangfireOptions = ServiceProvider.GetRequiredService>().Value; var defaultQueuePrefix = abpHangfireOptions.DefaultQueuePrefix; var defaultQueue = abpHangfireOptions.DefaultQueue; @@ -43,15 +45,31 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet { var unProxyWorker = ProxyHelper.UnProxy(hangfireBackgroundWorker); - RecurringJob.AddOrUpdate( - hangfireBackgroundWorker.RecurringJobId, - hangfireBackgroundWorker.Queue.IsNullOrWhiteSpace() ? defaultQueue : defaultQueuePrefix + hangfireBackgroundWorker.Queue, - () => ((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(cancellationToken), - hangfireBackgroundWorker.CronExpression, - new RecurringJobOptions - { - TimeZone = hangfireBackgroundWorker.TimeZone - }); + var queueName = hangfireBackgroundWorker.Queue.IsNullOrWhiteSpace() ? defaultQueue : defaultQueuePrefix + hangfireBackgroundWorker.Queue; + if (!JobStorage.Current.HasFeature(JobStorageFeatures.JobQueueProperty)) + { + logger.LogError($"Current storage doesn't support specifying queues({queueName}) directly for a specific job. Please use the QueueAttribute instead."); + RecurringJob.AddOrUpdate( + hangfireBackgroundWorker.RecurringJobId, + () => ((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(cancellationToken), + hangfireBackgroundWorker.CronExpression, + new RecurringJobOptions + { + TimeZone = hangfireBackgroundWorker.TimeZone + }); + } + else + { + RecurringJob.AddOrUpdate( + hangfireBackgroundWorker.RecurringJobId, + queueName, + () => ((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(cancellationToken), + hangfireBackgroundWorker.CronExpression, + new RecurringJobOptions + { + TimeZone = hangfireBackgroundWorker.TimeZone + }); + } break; } @@ -63,18 +81,17 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet switch (worker) { case AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase: - period = asyncPeriodicBackgroundWorkerBase.Period; - cronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression; + period = asyncPeriodicBackgroundWorkerBase.Period; + cronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression; break; case PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase: - period = periodicBackgroundWorkerBase.Period; - cronExpression = periodicBackgroundWorkerBase.CronExpression; + period = periodicBackgroundWorkerBase.Period; + cronExpression = periodicBackgroundWorkerBase.CronExpression; break; } if (period == null && cronExpression.IsNullOrWhiteSpace()) { - var logger = ServiceProvider.GetRequiredService>(); logger.LogError( $"Cannot add periodic background worker {worker.GetType().FullName} to Hangfire scheduler, because both Period and CronExpression are not set. " + "You can either set Period or CronExpression property of the worker." @@ -86,15 +103,32 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet Expression> methodCall = () => workerAdapter.DoWorkAsync(cancellationToken); var recurringJobId = !workerAdapter.RecurringJobId.IsNullOrWhiteSpace() ? workerAdapter.RecurringJobId : GetRecurringJobId(worker, methodCall); - RecurringJob.AddOrUpdate( - recurringJobId, - workerAdapter.Queue.IsNullOrWhiteSpace() ? defaultQueue : defaultQueuePrefix + workerAdapter.Queue, - methodCall, - cronExpression ?? GetCron(period!.Value), - new RecurringJobOptions - { - TimeZone = workerAdapter.TimeZone - }); + var queueName = workerAdapter.Queue.IsNullOrWhiteSpace() ? defaultQueue : defaultQueuePrefix + workerAdapter.Queue; + if (!JobStorage.Current.HasFeature(JobStorageFeatures.JobQueueProperty)) + { + logger.LogError($"Current storage doesn't support specifying queues({queueName}) directly for a specific job. Please use the QueueAttribute instead."); + RecurringJob.AddOrUpdate( + recurringJobId, + methodCall, + cronExpression ?? GetCron(period!.Value), + new RecurringJobOptions + { + TimeZone = workerAdapter.TimeZone + }); + } + else + { + RecurringJob.AddOrUpdate( + recurringJobId, + queueName, + methodCall, + cronExpression ?? GetCron(period!.Value), + new RecurringJobOptions + { + TimeZone = workerAdapter.TimeZone + }); + } + break; } default: