I get this error after upgrade microservice template from v7.3.0 to v9.1.0. Old version still works fine.
[web_80132ba5-d]: [10:43:56 ERR] ---------- RemoteServiceErrorInfo ----------
[web_80132ba5-d]: {
[web_80132ba5-d]: "code": null,
[web_80132ba5-d]: "message": "An error occurred during the ABP remote HTTP request. (The operation didn't complete within the allowed timeout of '00:00:30'.) See the inner exception for details.",
[web_80132ba5-d]: "details": null,
[web_80132ba5-d]: "data": null,
[web_80132ba5-d]: "validationErrors": null
[web_80132ba5-d]: }
[web_80132ba5-d]:
[web_80132ba5-d]: [10:43:56 ERR] An error occurred during the ABP remote HTTP request. (The operation didn't complete within the allowed timeout of '00:00:30'.) See the inner exception for details.
[web_80132ba5-d]: Volo.Abp.Http.Client.AbpRemoteCallException: An error occurred during the ABP remote HTTP request. (The operation didn't complete within the allowed timeout of '00:00:30'.) See the inner exception for details.
[web_80132ba5-d]: ---> Polly.Timeout.TimeoutRejectedException: The operation didn't complete within the allowed timeout of '00:00:30'.
[web_80132ba5-d]: ---> System.Threading.Tasks.TaskCanceledException: The operation was canceled.
[web_80132ba5-d]: ---> System.IO.IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request..
[web_80132ba5-d]: ---> System.Net.Sockets.SocketException (995): The I/O operation has been aborted because of either a thread exit or an application request.
[web_80132ba5-d]: --- End of inner exception stack trace ---
I tried to increase the timeout like this, but the error still occur.
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpHttpClientBuilderOptions>(options =>
{
options.ProxyClientActions.Add((remoteServiceName, clientBuilder, client) =>
{
client.Timeout = TimeSpan.FromMinutes(60);
});
options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) =>
{
clientBuilder.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromMinutes(60)));
clientBuilder.AddResilienceHandler("MHIBS", builder =>
{
builder.AddTimeout(TimeSpan.FromMinutes(60));
});
clientBuilder.AddStandardResilienceHandler(opts =>
{
opts.TotalRequestTimeout.Timeout = TimeSpan.FromMinutes(60);
opts.AttemptTimeout.Timeout = TimeSpan.FromMinutes(60);
opts.CircuitBreaker.SamplingDuration = TimeSpan.FromMinutes(120);
});
});
});
}
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.4.0" />
Please help
I've created a report module and need to use this module in multiple microservices. In this module, I have some pages with following controllers
namespace MZH.MHIBS.Report.Controllers
{
[RemoteService(Name = ReportRemoteServiceConsts.RemoteServiceName)]
[Area(ReportRemoteServiceConsts.ModuleName)]
[Route("api/report/report-item")]
public class ReportItemController : ReportController, IReportItemAppService
{
protected IReportItemAppService ReportItemAppService { get; }
public ReportItemController(IReportItemAppService reportItemAppService)
{
ReportItemAppService = reportItemAppService;
}
[HttpPost]
[Route("batch-update")]
public Task BatchUpdateAsync(List<DXBatchUpdate> changes)
{
return ReportItemAppService.BatchUpdateAsync(changes);
}
[HttpGet]
public Task<LoadResult> GetListAsync(DataSourceLoadOptionsCustomized options)
{
return ReportItemAppService.GetListAsync(options);
}
}
}
Each microservice use different database. When using this module, each microservice will have the same tables. But I don't know how to separate the route of controllers for each microservice. Please help.
Hi,
OK, so the secret here is the transaction must be newly created. I tested with requiresNew: false and isolationLevel: System.Data.IsolationLevel.Serializable it still inserts duplicated records.
But I still don't understand why I can't use UnitOfWork attribute like this [UnitOfWork(true, System.Data.IsolationLevel.Serializable)]. It does not work. Can you explain it?
Hi, I solved this issue. Isolation level must be Serializable not Read Committed, because Report Period is newly inserted. Read Committed can not lock it.
Hi, Sorry, I can't share my code. Please guide me which code I need to check. Any doubt that you need me to clarify?
Hi,
I updated AppService like this:
[HttpPost]
//[UnitOfWork(true, System.Data.IsolationLevel.ReadCommitted)]
public async Task<Guid> CollectDataAsync<T>(string reportCode, string formData, string? permission = "") where T : CollectDataBaseDto
{
if (string.IsNullOrEmpty(permission))
{
throw new UserFriendlyException(_localizer["common_msg_DoNotPermission"]);
}
if (!await AuthorizationService.IsGrantedAsync(permission))
{
throw new UserFriendlyException(string.Format(_localizer["common_msg_DoNotPermission_Item"], permission));
}
if (CurrentUser == null)
{
throw new UserFriendlyException(new ArgumentNullException(nameof(CurrentUser)).Message);
}
if (CurrentTenant == null)
{
throw new UserFriendlyException(new ArgumentNullException(nameof(CurrentTenant)).Message);
}
T? paraObject = default(T);
if (!string.IsNullOrEmpty(formData))
{
paraObject = JsonConvert.DeserializeObject<T>(formData);
}
Dictionary<string, object> keyValueParams = new Dictionary<string, object>();
if (paraObject == null)
{
return Guid.Empty;
}
paraObject.ReportCode = reportCode;
if (CurrentUser != null && CurrentUser.Id.HasValue)
{
paraObject.CurrentUserId = CurrentUser.Id.Value;
string department = await GetCurrentDepartmentAsync();
if (!string.IsNullOrEmpty(department))
{
paraObject.Department = department;
}
}
if (CurrentTenant != null && CurrentTenant.Id.HasValue)
{
paraObject.TenantId = CurrentTenant.Id.Value;
}
Logger.LogInformation("Para object: {0}", paraObject.ObjectToString());
var properties = paraObject.GetType().GetProperties();
foreach (var prop in properties)
{
keyValueParams.Add(prop.Name, prop.GetValue(paraObject) ?? string.Empty);
}
Guid reportPeriodId;
try
{
using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true, isolationLevel: System.Data.IsolationLevel.ReadCommitted))
{
using (_dataFilter.Disable<ISoftDelete>())
{
reportPeriodId = await _reportDataRepository.CollectDataAsync(reportCode, paraObject.ReportDate, paraObject.Department, keyValueParams);
}
await uow.CompleteAsync();
}
return reportPeriodId;
}
catch (Exception ex)
{
throw new UserFriendlyException(ex.Message);
}
}
Hi,
If so, I understand that, I can't run my app with Abp Studio, right? So how can I run my entire app? use Tye?
[HttpPost]
[UnitOfWork(true, System.Data.IsolationLevel.ReadCommitted)]
public async Task<Guid> CollectDataAsync<T>(string reportCode, string formData, string? permission = "") where T : CollectDataBaseDto
{
if (string.IsNullOrEmpty(permission))
{
throw new UserFriendlyException(_localizer["common_msg_DoNotPermission"]);
}
if (!await AuthorizationService.IsGrantedAsync(permission))
{
throw new UserFriendlyException(string.Format(_localizer["common_msg_DoNotPermission_Item"], permission));
}
if (CurrentUser == null)
{
throw new UserFriendlyException(new ArgumentNullException(nameof(CurrentUser)).Message);
}
if (CurrentTenant == null)
{
throw new UserFriendlyException(new ArgumentNullException(nameof(CurrentTenant)).Message);
}
T? paraObject = default(T);
if (!string.IsNullOrEmpty(formData))
{
paraObject = JsonConvert.DeserializeObject<T>(formData);
}
Dictionary<string, object> keyValueParams = new Dictionary<string, object>();
if (paraObject == null)
{
return Guid.Empty;
}
paraObject.ReportCode = reportCode;
if (CurrentUser != null && CurrentUser.Id.HasValue)
{
paraObject.CurrentUserId = CurrentUser.Id.Value;
string department = await GetCurrentDepartmentAsync();
if (!string.IsNullOrEmpty(department))
{
paraObject.Department = department;
}
}
if (CurrentTenant != null && CurrentTenant.Id.HasValue)
{
paraObject.TenantId = CurrentTenant.Id.Value;
}
Logger.LogInformation("Para object: {0}", paraObject.ObjectToString());
var properties = paraObject.GetType().GetProperties();
foreach (var prop in properties)
{
keyValueParams.Add(prop.Name, prop.GetValue(paraObject) ?? string.Empty);
}
using (_dataFilter.Disable<ISoftDelete>())
{
return await _reportDataRepository.CollectDataAsync(reportCode, paraObject.ReportDate, paraObject.Department, keyValueParams);
}
}
CollectDataAsync method in ReportDataRepository
public async Task<Guid> CollectDataAsync(string reportCode, DateTime reportDate, string department, Dictionary<string, object> keyValueParams)
{
if (string.IsNullOrEmpty(department))
return Guid.Empty;
var dbContext = await GetDbContextAsync();
await SetCommandTimeout(dbContext);
ReportType? reportType = await dbContext.ReportTypes.FirstOrDefaultAsync(a => a.ReportCode == reportCode && !a.IsDeleted);
if (reportType == null)
return Guid.Empty;
ReportStructure? reportStructure = await dbContext.ReportStructures.FirstOrDefaultAsync(a => a.ReportTypeId == reportType.Id && a.IsActive && !a.IsDeleted);
if (reportStructure == null)
return Guid.Empty;
ReportPeriod? reportPeriod = await dbContext.ReportPeriods.FirstOrDefaultAsync(a => a.ReportTypeId == reportType.Id && a.ReportDate.Date == reportDate.Date && !a.IsDeleted);
if (reportPeriod == null)
{
//create new report period for this report type
reportPeriod = new ReportPeriod(Guid.NewGuid());
reportPeriod.ReportTypeId = reportType.Id;
reportPeriod.ReportStructureId = reportStructure.Id;
reportPeriod.ReportDate = reportDate;
await dbContext.ReportPeriods.AddAsync(reportPeriod);
await dbContext.SaveChangesAsync();
}
if (await dbContext.ReportData.AnyAsync(a => a.ReportPeriodId == reportPeriod.Id && a.Department.ToLower() == department.ToLower()))
return reportPeriod.Id;
//Find all datasources and execute scripts
var datasources = dbContext.ReportDataSources.Where(a => a.ReportStructureId == reportStructure.Id && a.Department.Contains(department) && !a.IsDeleted
).OrderBy(a => a.RunningOrder);
foreach (var ds in datasources)
{
await ExecuteSQLCommand(ds, keyValueParams);
}
return reportPeriod.Id;
}
I check ReportPeriod table with these conditions: ReportPeriod? reportPeriod = await dbContext.ReportPeriods.FirstOrDefaultAsync(a => a.ReportTypeId == reportType.Id && a.ReportDate.Date == reportDate.Date && !a.IsDeleted); and expect that it can not have > 1 record with the same ReportType, ReportDate and not deleted.
But when I quickly click Collect Data button on UI 4 times. It still creates 4 records in database.
Where am I wrong?
Hi,
In version 7.3, I already had some services in DDD, so can I use those services in version 9.0 in parallel? It means that, if I create new service I will follow the architect of version 9.0 but old services are still in old architect of version 7.3?