Am I missing something here, I was certain EF lazy loading was disabled in Zero. I have a simple call to a table in my application layer:
var listTest = _ngFormSubmissionRepository.GetAll().ToList();
My FormSubmission table has millions of rows and a few parent tables, enabled in code as follows:
public class NgFormSubmission : FullAuditedEntity<long>, IMustHaveTenant
{
public int TenantId { get; set; }
public long? OrganizationUnitId { get; set; }
[Required]
public long UserId { get; set; }
[ForeignKey("NgFormId")]
public virtual NgForm NgForm { get; set; }
public virtual int? NgFormId { get; set; }
[ForeignKey("NgEntityId")]
public virtual NgEntity NgEntity { get; set; }
public System.Guid? NgEntityId { get; set; }
[ForeignKey("NgMobileDeviceId")]
public virtual NgMobileDevice NgMobileDevice { get; set; }
public int? NgMobileDeviceId { get; set; }
[ForeignKey("NgActionId")]
public virtual NgAction NgAction { get; set; }
public long? NgActionId { get; set; }
... code removed for brevity
I make a single call to get a list of FormSubmissions:
var listTest = _ngFormSubmissionRepository.GetAll().ToList();
and the SQL created as a result includes calls to parent entities (NgForm, NgEntity, NgMobileDevice and so on) which is not so bad but also included are calls to children of those entities.
SELECT [t].[ClientCreatedDatetime], [t].[CreationTime], COALESCE([t].[CreatorUserId], CAST(0 AS bigint)), [t].[DeleterUserId], [t].[DeletionTime], [t].[Id], [t].[IsDeleted], [t].[IsFlagged], [t].[LastModificationTime], [t].[LastModifierUserId], CASE
WHEN [t0].[Id] IS NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END, [t0].[CreationTime], COALESCE([t0].[CreatorUserId], CAST(0 AS bigint)), [t0].[DeleterUserId], [t0].[DeletionTime], [t0].[DisplayName], [t0].[ExtensionData], [t0].[Id], [t0].[IsDeleted], [t0].[LastModificationTime], [t0].[LastModifierUserId], CASE
WHEN [t1].[Id] IS NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END, [t1].[CreationTime], COALESCE([t1].[CreatorUserId], CAST(0 AS bigint)), [t1].[DeleterUserId], [t1].[DeletionTime], [t1].[Description], [t1].[DisplayName], [t1].[ExtensionData], [t1].[FormReportTemplate], [t1].[Id], [t1].[IsDeleted], [t1].[IsEditable], [t1].[LastModificationTime], [t1].[LastModifierUserId], [t1].[SerializedJson], [t1].[TenantId], [t0].[NgFormId], [t0].[OrganizationUnitId], [t0].[ParentId], [t0].[TenantId], [t].[NgActionId], CASE
WHEN [t2].[Id] IS NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END, [t2].[CreationTime], COALESCE([t2].[CreatorUserId], CAST(0 AS bigint)), [t2].[DeleterUserId], [t2].[DeletionTime], [t2].[DisplayName], [t2].[ExtensionData], [t2].[Id], [t2].[IsDeleted], [t2].[LastModificationTime], [t2].[LastModifierUserId], [t2].[TenantId], [t].[NgEntityId], CASE
WHEN [t3].[Id] IS NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END, [t3].[CreationTime], COALESCE([t3].[CreatorUserId], CAST(0 AS bigint)), [t3].[DeleterUserId], [t3].[DeletionTime], [t3].[Description], [t3].[DisplayName], [t3].[ExtensionData], [t3].[FormReportTemplate], [t3].[Id], [t3].[IsDeleted], [t3].[IsEditable], [t3].[LastModificationTime], [t3].[LastModifierUserId], [t3].[SerializedJson], [t3].[TenantId], [t].[NgFormId], CASE
WHEN [t4].[Id] IS NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END, [t4].[AppVersion], [t4].[AuthorisedTime], [t4].[Cordova], [t4].[CreationTime], COALESCE([t4].[CreatorUserId], CAST(0 AS bigint)), [t4].[DeleterUserId], [t4].[DeletionTime], [t4].[ExtensionData], [t4].[Id], [t4].[IMEI], [t4].[IsAuthorised], [t4].[IsDeleted], [t4].[IsOnline], [t4].[IsVirtual], [t4].[LastModificationTime], [t4].[LastModifierUserId], [t4].[Manufacturer], [t4].[Model], [t5].[Id], [t5].[LoginDatetime], [t5].[LogoutDatetime], [t5].[NgMobileDeviceId], [t5].[TenantId], [t5].[UserId], [t6].[CreationTime], [t6].[CreatorUserId], [t6].[DeleterUserId], [t6].[DeletionTime], [t6].[Directive], [t6].[ExtensionData], [t6].[Id], [t6].[IsDeleted], [t6].[NgMobileDeviceId], [t6].[Priority], [t6].[TenantId], [t4].[Platform], [t4].[Serial], [t4].[TenantId], [t4].[UUID], [t4].[Version], [t].[NgMobileDeviceId], [t].[OrganizationUnitId], [t].[ReportString], [t].[SerializedJson], [t].[TenantId], [t].[UserId]
FROM (
SELECT [n].[Id], [n].[ClientCreatedDatetime], [n].[CreationTime], [n].[CreatorUserId], [n].[DeleterUserId], [n].[DeletionTime], [n].[IsDeleted], [n].[IsFlagged], [n].[LastModificationTime], [n].[LastModifierUserId], [n].[NgActionId], [n].[NgEntityId], [n].[NgFormId], [n].[NgMobileDeviceId], [n].[OrganizationUnitId], [n].[ReportString], [n].[SerializedJson], [n].[TenantId], [n].[UserId]
FROM [NgFormSubmission] AS [n]
WHERE ((((@__ef_filter__p_0 = CAST(1 AS bit)) OR ([n].[IsDeleted] = CAST(0 AS bit))) AND ((@__ef_filter__p_1 = CAST(1 AS bit)) OR ([n].[TenantId] = @__ef_filter__CurrentTenantId_2))) AND ([n].[IsDeleted] = @__isDeleted_0)) AND ([n].[NgEntityId] = @__p_1)
ORDER BY [n].[ClientCreatedDatetime] DESC
OFFSET @__p_2 ROWS FETCH NEXT @__p_3 ROWS ONLY
) AS [t]
LEFT JOIN (
SELECT [n0].[Id], [n0].[CreationTime], [n0].[CreatorUserId], [n0].[DeleterUserId], [n0].[DeletionTime], [n0].[DisplayName], [n0].[ExtensionData], [n0].[IsDeleted], [n0].[LastModificationTime], [n0].[LastModifierUserId], [n0].[NgFormId], [n0].[OrganizationUnitId], [n0].[ParentId], [n0].[TenantId]
FROM [NgAction] AS [n0]
WHERE ((@__ef_filter__p_3 = CAST(1 AS bit)) OR ([n0].[IsDeleted] = CAST(0 AS bit))) AND ((@__ef_filter__p_4 = CAST(1 AS bit)) OR ([n0].[TenantId] = @__ef_filter__CurrentTenantId_5))
) AS [t0] ON [t].[NgActionId] = [t0].[Id]
LEFT JOIN (
SELECT [n1].[Id], [n1].[CreationTime], [n1].[CreatorUserId], [n1].[DeleterUserId], [n1].[DeletionTime], [n1].[Description], [n1].[DisplayName], [n1].[ExtensionData], [n1].[FormReportTemplate], [n1].[IsDeleted], [n1].[IsEditable], [n1].[LastModificationTime], [n1].[LastModifierUserId], [n1].[SerializedJson], [n1].[TenantId]
FROM [NgForm] AS [n1]
WHERE ((@__ef_filter__p_6 = CAST(1 AS bit)) OR ([n1].[IsDeleted] = CAST(0 AS bit))) AND ((@__ef_filter__p_7 = CAST(1 AS bit)) OR ([n1].[TenantId] = @__ef_filter__CurrentTenantId_8))
) AS [t1] ON [t0].[NgFormId] = [t1].[Id]
LEFT JOIN (
SELECT [n2].[Id], [n2].[CreationTime], [n2].[CreatorUserId], [n2].[DeleterUserId], [n2].[DeletionTime], [n2].[DisplayName], [n2].[ExtensionData], [n2].[IsDeleted], [n2].[LastModificationTime], [n2].[LastModifierUserId], [n2].[TenantId]
FROM [NgEntity] AS [n2]
WHERE ((@__ef_filter__p_9 = CAST(1 AS bit)) OR ([n2].[IsDeleted] = CAST(0 AS bit))) AND ((@__ef_filter__p_10 = CAST(1 AS bit)) OR ([n2].[TenantId] = @__ef_filter__CurrentTenantId_11))
) AS [t2] ON [t].[NgEntityId] = [t2].[Id]
LEFT JOIN (
SELECT [n3].[Id], [n3].[CreationTime], [n3].[CreatorUserId], [n3].[DeleterUserId], [n3].[DeletionTime], [n3].[Description], [n3].[DisplayName], [n3].[ExtensionData], [n3].[FormReportTemplate], [n3].[IsDeleted], [n3].[IsEditable], [n3].[LastModificationTime], [n3].[LastModifierUserId], [n3].[SerializedJson], [n3].[TenantId]
FROM [NgForm] AS [n3]
WHERE ((@__ef_filter__p_6 = CAST(1 AS bit)) OR ([n3].[IsDeleted] = CAST(0 AS bit))) AND ((@__ef_filter__p_7 = CAST(1 AS bit)) OR ([n3].[TenantId] = @__ef_filter__CurrentTenantId_8))
) AS [t3] ON [t].[NgFormId] = [t3].[Id]
LEFT JOIN (
SELECT [n4].[Id], [n4].[AppVersion], [n4].[AuthorisedTime], [n4].[Cordova], [n4].[CreationTime], [n4].[CreatorUserId], [n4].[DeleterUserId], [n4].[DeletionTime], [n4].[ExtensionData], [n4].[IMEI], [n4].[IsAuthorised], [n4].[IsDeleted], [n4].[IsOnline], [n4].[IsVirtual], [n4].[LastModificationTime], [n4].[LastModifierUserId], [n4].[Manufacturer], [n4].[Model], [n4].[Platform], [n4].[Serial], [n4].[TenantId], [n4].[UUID], [n4].[Version]
FROM [NgMobileDevice] AS [n4]
WHERE ((@__ef_filter__p_12 = CAST(1 AS bit)) OR ([n4].[IsDeleted] = CAST(0 AS bit))) AND ((@__ef_filter__p_13 = CAST(1 AS bit)) OR ([n4].[TenantId] = @__ef_filter__CurrentTenantId_14))
) AS [t4] ON [t].[NgMobileDeviceId] = [t4].[Id]
NOTE THESE LEFT JOINS.....
LEFT JOIN (
SELECT [n5].[Id], [n5].[LoginDatetime], [n5].[LogoutDatetime], [n5].[NgMobileDeviceId], [n5].[TenantId], [n5].[UserId]
FROM [NgMobileDeviceAndUserLoginAudit] AS [n5]
WHERE (@__ef_filter__p_15 = CAST(1 AS bit)) OR ([n5].[TenantId] = @__ef_filter__CurrentTenantId_16)
) AS [t5] ON [t4].[Id] = [t5].[NgMobileDeviceId]
LEFT JOIN (
SELECT [n6].[CreationTime], [n6].[CreatorUserId], [n6].[DeleterUserId], [n6].[DeletionTime], [n6].[Directive], [n6].[ExtensionData], [n6].[Id], [n6].[IsDeleted], [n6].[NgMobileDeviceId], [n6].[Priority], [n6].[TenantId]
FROM [NgMobileDeviceOfflineDirective] AS [n6]
WHERE ((@__ef_filter__p_17 = CAST(1 AS bit)) OR ([n6].[IsDeleted] = CAST(0 AS bit))) AND ((@__ef_filter__p_18 = CAST(1 AS bit)) OR ([n6].[TenantId] = @__ef_filter__CurrentTenantId_19))
) AS [t6] ON [t4].[Id] = [t6].[NgMobileDeviceId]**
ORDER BY [t].[ClientCreatedDatetime] DESC, [t].[Id], [t0].[Id], [t1].[Id], [t2].[Id], [t3].[Id], [t4].[Id], [t5].[Id]
My NgMobileDevice entity has audit tables for geolocation tracking and user sign-in/sign-out, each of which has several million rows making the query very ugly indeed. I thought Zero worked the lines of:
var listTest = _ngFormSubmissionRepository.GetAll()
.Include(m => m.NgMobileDevice)
.ToList();
when you wanted to include parents and children? Am I missing something and is there any way I can switch off EF lazy loading? More for my understanding of the application, maybe I've beign doing things incorrectly all these years?????
Cheers, Bob
I am using Formly in my application and to support error validation messages in bootstrap the recommendation is to implement the following in my module class:
imports: [
... code removed for brevity
FormlyBootstrapModule,
FormlyModule.forRoot({
validationMessages: [
{ name: 'required', message: requiredValidationMessage },
{ name: 'minlength', message: lengthValidationMessage },
{ name: 'maxlength', message: lengthValidationMessage },
{ name: 'min', message: minValidationMessage },
{ name: 'max', message: maxValidationMessage },
],
}),
We then have the following (example) function above the @NgModule declaration:
export function requiredValidationMessage(err, field) {
return `Input ${field.templateOptions.label} is required`;
}
See Formly - built in validations for suggested implementation.
What I would like to achieve is to use pre-existing validation messages from the language xml file but I can find no way to access the language service in my module. Ideally something like this....
export function requiredValidationMessage(err, field) {
return this.l('RequiredField', field.templateOptions.label);
}
Is there any way to access the language service from within a module class or is there a different way to implement this? Thanks in advance....
The demo-ui-date-time.component has a datepicker with this code:
<div class="col-lg-10 col-md-9 col-sm-8">
<input
id="SampleDatePicker"
type="text"
#SampleDatePicker
name="SampleDatePicker"
class="form-control"
bsDatepicker
datePickerLuxonModifier
[(date)]="sampleDate"
[bsConfig]="{ adaptivePosition: true }"
/>
</div>
and displays the datetime format in the input perfectly:
My date-time.component has a datepicker with this code:
<input class="form-control m-input"
#AuthorisedTime
formControlName="authorisedTime"
class="form-control"
[ngClass]="{ 'is-invalid': f.cordova.invalid }"
type="datetime"
bsDatepicker
datePickerLuxonModifier
[(date)]="ngMobileDevice.authorisedTime"
id="authorisedTime"
name="authorisedTime" />
and displays the datetime format in the input in US format:
I have checked each module and component for any trace of date time settings but cannot find out why my component displays in a different format to that of the demo component. Any help gratefully received.
Cheers, Bob
I thought it was a planned issue to generate Reactive Forms in Angular using Power Tools? Angular: Moving to Reactive (and Dynamic) Forms?
Any update would be nice, we really do not like the template forms.
Cheers
I have a cuople of question install
Can anyone tell where the InstallAppService is invoked and why? I have searched the code and found the module and component in angular and invoked the route using localhost:4200/app/admin/install and the page flickers on screen before rerouting. What I was trying to do was to set development smtp settings during start up for a fresh project. I can see the following settings in the .NET Core project, src\Nuage.Application\Install\InstallAppService.cs:
private async Task SetSmtpSettings(EmailSettingsEditDto input)
{
await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.DefaultFromAddress, input.DefaultFromAddress);
await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.DefaultFromDisplayName, input.DefaultFromDisplayName);
await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.Host, input.SmtpHost);
await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.Port, input.SmtpPort.ToString(CultureInfo.InvariantCulture));
await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.UserName, input.SmtpUserName);
await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.Password, SimpleStringCipher.Instance.Encrypt(input.SmtpPassword));
await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.Domain, input.SmtpDomain);
await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.EnableSsl, input.SmtpEnableSsl.ToString().ToLowerInvariant());
await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.UseDefaultCredentials, input.SmtpUseDefaultCredentials.ToString().ToLowerInvariant());
}
And thought that would be where I put my initialisation code. Am I missing something? Any pointers most welcome.
It seems to me that DemoMode is difficult to find and incomplete. At line 33 of src\Nuage.Core\MultiTenancy\Demo\ TenantDemoDataBuilder.cs we have:
return string.Equals(_appConfiguration["App:DemoMode"], "true", StringComparison.OrdinalIgnoreCase);
Should we not also add the following to appsettings and document same?
I have a couple of question questions on the implementation of 11.0.0. This is a fresh build, not an upgrade.
I think this is minor point but no doubt could come back to bite someone later on and maybe needs to be looked at now. There seems to be some problems with the migration scripts for Editions, it's not a show stopper but may need pointing out.
The system throws errors in the JwtTokenHandler for all unauthorised requests before initial sign in. It causes a break point in Visual Studio, throws an exception in the log and is extremely annoying.
Once the user is signed in the exception is no longer thrown, even after the user has signed out. That's probably why it's been missed. If I'm right this will mean that every time a user navigates to the root of the site without having first signed in an exception will be thrown. If they navigate to any of the hyperlinks in the initial sign in page ("Forgot password", "New tenant" and "Email activation") further exceptions are thrown.
This can be reproduced by removing cache from the browser. Zero works perfectly but several exceptions are logged and these are very difficult to filter out in Application Insights in Azure. For those of us who analyse log exceptions it's very difficult to see what's valid and what's not.
In the Angular project, I follow the instructions in the documentation and a cli object is added to my angular.json file:
What is this analytic code and who does it belong to? I have no problem with it, I just don't know what it is.
I just don't know what it is, could someone explain?
Hope you can help!
Cheers, Bob
The download page for a new project has no option for .NET Core 6.0 Consequently the project will not compile in Visual Studio 2022 (Enterprise)..
I have a queryable which I return to a Kendo DataSourceResult object for paging and querying for a grid:
private readonly IRepository<User, long> _userRepository
...
public IQueryable<NcFormSubmissionForKendoGridDto> GetFormSubmissionsForNcActionAudit()
{
var query = from formSubmission in _ncFormSubmissionRepository.GetAll()
join ncAction in _ncActionRepository.GetAll() on formSubmission.NcActionId equals ncAction.Id
join user in _userRepository.GetAll() on formSubmission.CreatorUserId equals user.Id
join ncEntity in _ncEntityRepository.GetAll() on formSubmission.NcEntityId equals ncEntity.Id
join organizationUnit in _organizationUnitRepository.GetAll() on ncEntity.OrganizationUnitId equals organizationUnit.Id
select new NcFormSubmissionForKendoGridDto
{
Id = formSubmission.Id,
CreationTime = formSubmission.CreationTime,
ClientCreatedDatetime = formSubmission.ClientCreatedDatetime,
ReportString = formSubmission.ReportString,
SerializedJson = formSubmission.SerializedJson,
CreatedByFullName = user.UserName,
NcActionDisplayName = ncAction.DisplayName,
NcEntityDisplayName = ncEntity.DisplayName,
OrganizationUnitDisplayName = organizationUnit.DisplayName,
};
return query;
}
Ideally I would like the full name of the user:
CreatedByFullName = user.FullName
However this throws the following error message:
Translation of member 'FullName' on entity type 'User' failed. This commonly occurs when the specified member is unmapped. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Is this a bug or is there anything which can be done about it? I could iterate the data after converting to a DataSourceResult but thought there may be a cleaner way.
Cheers, Bob
I am currently in the process of upgrading to the latest version but have a couple of jobs before it is released.
Currently I have use this code to format some of my datetime strings:
format: '{0:dd/MM/yyyy}'
Given that culture is changed when a language is changed is there any way to find the the date format for the tenant or the current session in Angular?
For example, when using Kendo grid I have to cast all dates back to javascript dates so I first grab the tenant timezone before doing so:
let tenantTimezone = abp.timing.timeZoneInfo.iana.timeZoneId;
Then I can carry out the following:
if (d.deletionTime) { d.deletionTime = new Date(moment(d.deletionTime).tz(tenantTimezone).format()); }
It's a pain but that's the way it is with Kendo...
Is there any way I can grab the current date time format:
let currentdateTimeFormat = ?????
so I can format my dates accordingly:
format: currentdateTimeFormat
I hope that makes sense, I wanted to ask this question now as work on upgrading is on-going and this useful information to know. Thanks in advance, Bob
How do I set date and time formats? I always understood it was decided by the language selection which also (I believe) sets the current culture. As a test, on a fresh install of Zero go to Admin -> Users and select any of the languages, the date format in the Creation Time column never changes. Am I missing something or is this a hangover of the Luxon implementation? Let me know if this is a bug and I will open an issue in github.
Also, on the language drop-down there are two implementations of English, which is at it should be. However, one uses the English flag (as it should) and one uses the British Flag - should this be a USA flag?
Cheers, Bob