Hello ,
So , in that case abp's default async periodic workers like (Token CleanUp Background worker) need that global configuration right ?
If we want to create background jobs or workers (recurring things) which should be working with Hangfire server, we need to configure this global configuration ?
AbpHangfireOptions is just for Hangfire server itself , but it is not including jobs or workers working with this server ?
Thank you.
Hello , Thanks, It looks like the JobStorage error is gone. But why do we need a global configuration, I had already given the storage configuration with AbpHangfireOptions ?
Hello , I've tried from scratch with brand new abp project and I'm getting still same exception. I specially wanted to try in a template project, because there is nothing special , I've implemented configuration and I'm still getting JobStorage error which I shouldn't take. I think there is a problem about Abp Hangfire BackgroundWorker module or it could be related with Abp Background Job Module. Because I think there are some pre initialized background jobs and handlers' running behind by abp and this async jobs can't work with abp hangfire worker ?
I'm sharing my steps : 1- Firstly I created a new tiered application template project from abp suit with 5.2.0-rc.2 version 2- I installed <PackageReference Include="Volo.Abp.HangFire" Version="5.2.0-rc.2" /> and <PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="5.2.0-rc.2" /> references into IdentityServer project. 3- Then I made below configuration inside IdentityServerModule 4- After I ran IdentityServer solution I got below exception :
2022-03-28 13:13:25.938 +03:00 [FTL] deneme52.IdentityServer terminated unexpectedly!
Volo.Abp.AbpInitializationException: An error occurred during the initialize Volo.Abp.Modularity.OnApplicationInitializationModuleLifecycleContributor phase of the module Volo.Abp.IdentityServer.AbpIdentityServerDomainModule, Volo.Abp.IdentityServer.Domain, Version=5.2.0.0, Culture=neutral, PublicKeyToken=null: JobStorage.Current property value has not been initialized. You must set it before using Hangfire Client or Server API.. See the inner exception for details.
---> System.InvalidOperationException: JobStorage.Current property value has not been initialized. You must set it before using Hangfire Client or Server API.
at Hangfire.JobStorage.get_Current()
at Hangfire.RecurringJobManager..ctor()
at Hangfire.RecurringJob.<>c.<.cctor>b__20_0()
at System.Lazy1.CreateValue() at System.Lazy
1.get_Value()
at Hangfire.RecurringJob.AddOrUpdate(Expression`1 methodCall, String cronExpression, TimeZoneInfo timeZone, String queue)
at Volo.Abp.BackgroundWorkers.Hangfire.HangfireBackgroundWorkerManager.AddAsync(IBackgroundWorker worker)
at Volo.Abp.IdentityServer.AbpIdentityServerDomainModule.OnApplicationInitializationAsync(ApplicationInitializationContext context)
at Volo.Abp.Modularity.OnApplicationInitializationModuleLifecycleContributor.InitializeAsync(ApplicationInitializationContext context, IAbpModule module)
at Volo.Abp.Modularity.ModuleManager.InitializeModulesAsync(ApplicationInitializationContext context)
--- End of inner exception stack trace ---
at Volo.Abp.Modularity.ModuleManager.InitializeModulesAsync(ApplicationInitializationContext context)
at Volo.Abp.AbpApplicationBase.InitializeModulesAsync()
at Volo.Abp.AbpApplicationWithExternalServiceProvider.InitializeAsync(IServiceProvider serviceProvider)
at Microsoft.AspNetCore.Builder.AbpApplicationBuilderExtensions.InitializeApplicationAsync(IApplicationBuilder app)
at deneme52.Program.Main(String[] args) in C:\Users\z0046r5w\Downloads\deneme52\src\deneme52.IdentityServer\Program.cs:line 40
Thank you.
Hello all, Actually We're having same issue still with below question after upgrading to Volo.Abp.BackgroundWorkers.Hangfire" Version="5.2.0-rc.2" , but I can't reply because the question was locked.
https://support.abp.io/QA/Questions/2578/AbpBackgroundWorkersHangfireModule-exception-without-using-hangfire-configuration
I'm sharing the exception below , too. Thank you.
Hello , (sorry for my late response, I was dealing with another issue. ) Thanks for your suggestions and I've checked your solution and I applied all unit of work depended options. But it just didn't work. After that I saw your ISoftDelete implementation in the framework code in AbpContext :
and I suspect of using of ISoftDelete interface (because like I mentioned before, I couldn't see the change of the entity state as deleted in our case) , so I just removed ISoftDelete interface from our child entities and all the above code that I've mentioned, worked successfully, All child entities removed without removing parent like we expect.
I don't know the main issue, but I think maybe there could be an entity state changing problem about ISoftDelete implementation in such specific cases like ours.
Thank you.
Hello , Is there any progress about this issue ? It is an important problem for us. Thank you.
Of course , could be , Here are our configurations :
**OdmsDbContextModelCreatingExtensions inside: **
/* Configure all entities here. */
builder.Entity<Model>(b =>
{
b.ToTable(OdmsDbProperties.DbTablePrefix + "Models", OdmsDbProperties.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.SchemaName).HasMaxLength(ModelConsts.MaxSchemaNameLength).HasColumnName(nameof(Model.SchemaName)).IsRequired();
b.Property(x => x.ServerName).HasMaxLength(ModelConsts.MaxServerNameLength).HasColumnName(nameof(Model.ServerName)).IsRequired();
b.Property(x => x.DatabaseType).HasMaxLength(ModelConsts.MaxDatabaseTypeLength).HasColumnName(nameof(Model.DatabaseType));
b.Property(x => x.Password).HasMaxLength(ModelConsts.MaxEncryptedPasswordLength).HasColumnName(nameof(Model.Password));
b.Property(x => x.Version).HasMaxLength(ModelConsts.MaxVersionLength).HasColumnName(nameof(Model.Version));
// Relations
b.HasMany<Export>(m => m.Exports).WithOne(e => e.Model).HasForeignKey(e => e.ModelId).IsRequired();
b.HasMany<Import>(m => m.Imports).WithOne(i => i.Model).HasForeignKey(i => i.ModelId).IsRequired();
b.HasMany<Source>(m => m.Sources).WithOne(s => s.Model).HasForeignKey(s => s.ModelId).IsRequired();
// Index
b.HasIndex(x => new { x.SchemaName });
b.Navigation(x => x.Exports).HasField("_exports");
b.Metadata.FindNavigation("Exports").SetPropertyAccessMode(PropertyAccessMode.Field);
});
builder.Entity<Export>(b =>
{
b.ToTable(OdmsDbProperties.DbTablePrefix + "Exports", OdmsDbProperties.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.ModelId).HasColumnName(nameof(Export.ModelId)).IsRequired();
b.Property(x => x.OperationId).HasColumnName(nameof(Export.OperationId)).IsRequired();
b.Property(x => x.ExportType).HasMaxLength(ExportConsts.MaxExportTypeLength).HasColumnName(nameof(Export.ExportType)).IsRequired();
b.Property(x => x.Result).HasMaxLength(ExportConsts.MaxResultLength).HasColumnName(nameof(Export.Result)).IsRequired();
// Value object
b.OwnsOne(x => x.ExportFile, p =>
{
p.Property(x => x.StorageId).HasColumnName(ExportConsts.ExportFileIdColumnName);
p.Property(x => x.Name).HasColumnName(ExportConsts.ExportFileNameColumnName);
p.Ignore(x => x.NameOnly);
p.Ignore(x => x.FullName);
p.Ignore(x => x.ModelType);
// Index
p.HasIndex(x => x.Name);
}).Navigation(x => x.ExportFile).IsRequired();
});
**Domain Manager layer inside : **
public virtual async Task HardDeleteExportAsync(string schemaName, string serverName, Guid fileId)
{
Check.NotNullOrWhiteSpace(schemaName, nameof(schemaName), ModelConsts.MaxSchemaNameLength);
Check.NotNullOrWhiteSpace(serverName, nameof(serverName), ModelConsts.MaxServerNameLength);
// Get model from database with conditional exports
var model = await ModelRepository.FindWithExportDetailAsync(
schemaName,
serverName,
x => x.IsDeleted == true && x.ExportFile.StorageId == fileId,
includeDetails: true // includeDetails: Set true to include all children of this aggregate
);
if (model == null)
{
throw new ModelDoesNotExistException(
schemaName: schemaName,
serverName: serverName
);
}
//NOTE => below code not working if we have a cascade delete relation but we want to delete only children , not with parent. we need to do it from repository layer
model.RemoveAllExports(); //model.HardDeleteExport(fileId);
await ModelRepository.UpdateAsync(model,true);
}
FindWithExportDetailAsync inside which is inside repository layer :
public virtual async Task<Model> FindWithExportDetailAsync(string schemaName, string serverName, Expression<Func<Export, bool>> expression, bool includeDetails = true, CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync())
.IncludeExportDetail(expression, includeDetails) // Include only exports
.Where(x => x.SchemaName == schemaName && x.ServerName == serverName)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); // Returns null if not found
}
IncludeExportDetail method inside :
public static IQueryable<Model> IncludeExportDetail(this IQueryable<Model> queryable, Expression<Func<Export, bool>> predicate, bool include = true)
{
if (!include)
{
return queryable;
}
return queryable
.Include(
x => x.Exports.AsQueryable()
.Where(predicate));
}```
Model aggregate root and removeExport method
public class Model : AuditedAggregateRoot<Guid>, IMultiTenant // Using Guid type as the Id key
{
public Guid? TenantId { get; protected set; }
[NotNull]
public virtual string SchemaName { get; protected set; } // Value object can be created for primitive types. There is no such requirement in the web API. Inputs are validated in the HTTP layer.
[NotNull]
public virtual string ServerName { get; protected set; } // Value object can be created for primitive types. There is no such requirement in the web API. Inputs are validated in the HTTP layer.
public virtual DatabaseType DatabaseType { get; protected set; }
public virtual string Password { get; protected set; }
[NotNull]
public virtual string Version { get; protected set; }
// Don't expose mutable collections in an aggregate
public virtual IReadOnlyCollection<Export> Exports
{
get
{
return _exports?.ToList(); // Paged operation may return without sub collection. If null then do not turn into list
}
}
private readonly ICollection<Export> _exports;
public virtual void RemoveAllExports()
{
// NOTE => Clear ,or new, or removeall not working when dealing with ef core because of it only clear the list , and parent doesn't know about the relational children deletion.
_exports.Clear();
}
}
Yes, you should already update the aggregate root normally after children entities changes. So I've tried and that's the problem which is not working even if updating aggregate root. I don't think it's because of private field because we are using backing fields actually , and in a normal ef core project it's working as expected for example :
Entity:
And the operation below is working :
Because of this simple ef core project is working without problem , we think that if this issue about abp efcore implementation? I can show our configuration anytime , adding and saving changes working perfectly but in deletion , removing step with that backing fields , could it be an issue ?
Don't understand the last message , you'll be checking the issue right ?