Activity Index. User Account country. Internals regions.
This commit is contained in:
2021-05-25 22:00:08 +01:00
parent 778ee3aa03
commit 63473c725b
173 changed files with 8149 additions and 41639 deletions
@@ -0,0 +1,177 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Dbas.Data;
using Saxonite.Base.App;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public abstract class SaxActivityConsole_Web : SaxActivityConsole
{
//-------------------------------------------------------------------
// Data members.
private readonly SaxInternals internals;
private readonly String alias_ActivityIndex = "ActIdx";
protected class IndexStatsData
{
public int EntriesInserted { get; set; }
public int EntriesUpdated { get; set; }
public int EntriesDeleted { get; set; }
}
//-------------------------------------------------------------------
// Protected construction.
protected SaxActivityConsole_Web(SaxSessionContext sessionContext) :
base(sessionContext)
{
internals = new SaxInternals(sessionContext);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
internals.Dispose();
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Query Activity Index Entries for User Context.
//-------------------------------------------------------------------
public override IndexResults QueryIndex(IndexContext context)
{
String fn = GetType().Name + ".QueryIndex";
IndexResults results = new IndexResults();
if (!context.IsValid()) return results;
// Columns to query.
ColumnDfn colItemType = internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemType);
ColumnDfn colItemId = internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemId);
// Query the index.
ISacsDatabase db = SaxGlobals.App.GetCoreDatabase(SessionContext);
DbQuery query = new DbQuery(db.DbmsType);
query.Tables.Add(SaxDbDfn_Core.GetTableName(SaxDbDfn_Core.TableIdx.ActivityIndex, false), alias_ActivityIndex);
query.Columns.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemType, alias_ActivityIndex), colItemType.DataName);
query.Columns.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemId, alias_ActivityIndex), colItemId.DataName);
query.Columns.Add("Max(" + SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_EffectiveOn) + ")", alias_ActivityIndex);
query.GroupBy.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemType, alias_ActivityIndex));
query.GroupBy.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemId, alias_ActivityIndex));
// Filter.
SaxActivityIndex.Params prmActivityIndex = new SaxActivityIndex.Params();
prmActivityIndex.SportIdsIncluded.AddRange(context.SportIds);
prmActivityIndex.RegionIdsIncluded.AddRange(context.RegionIds);
prmActivityIndex.EffectiveFrom = context.EffectiveFrom;
prmActivityIndex.EffectiveTo = context.EffectiveTo;
prmActivityIndex.Alias_ActivityIndex = alias_ActivityIndex;
query.Filters.AddRange(internals.ActivityMgr.GetActivityIndexQueryFilter(prmActivityIndex));
using (DbResultSet dbResultSet = DbResultSet_DataReader.ExecuteQuery(db, query.BuildQuery(context.Paging)))
{
while (dbResultSet != null && dbResultSet.NextRecord())
{
NamedValue.Map values = dbResultSet.GetRecordValues();
Sax.DataItemType itemType = (Sax.DataItemType)values.GetLong(colItemType.DataName);
ItemId itemId = new ItemId((short)itemType, new SysId(values.GetLong(colItemId.DataName)));
SaLogger.Info(fn + " Activity found - " + itemType.ToString() + ":" + SysId.ToString(itemId.Id));
}
}
return results;
}
//-------------------------------------------------------------------
// Store Activity Index.
//-------------------------------------------------------------------
protected override bool StoreActivityIndexData(IndexData indexData, DbUpdateInfo dbUpdateInfo)
{
String fn = GetType().Name + ".StoreActivityIndexData";
if (!ItemId.IsValid(indexData.ItemId)) return false;
if (!ValidActivityItemTypes.Contains((Sax.DataItemType)indexData.ItemId.Type))
{
SaLogger.Warn(fn + " Invalid item type specified for Activity Index: " + ((Sax.DataItemType)indexData.ItemId.Type).ToString());
return false;
}
IndexStatsData stats = new IndexStatsData();
// Index entries required for activity.
List<IndexEntryData> indexEntries = GetEntriesForIndex(indexData);
// Retrieve existing index entries for the item.
List<SaxActivityIndex> itemIndexEntries = internals.ActivityMgr.GetActivityIndexListForItem(indexData.ItemId);
foreach (SaxActivityIndex itemIndexEntry in itemIndexEntries)
{
// Is the index entry still required.
// Logic only compares the Ids (item, sport, region) and then updates the effective date if a match is found.
// Based on the fact that and change in the effective date will impact all entries in the index for the item
// and this approach minimises the number of updates required as Ids can't be changed in code (protected setters).
IndexEntryData indexEntry = indexEntries.Find(item => item.MatchesIds(itemIndexEntry));
if (indexEntry != null)
{
// Match found - if the effective date differs, update it.
if (!itemIndexEntry.EffectiveOn.Equals(indexEntry.EffectiveOn))
{
itemIndexEntry.EffectiveOn = indexEntry.EffectiveOn;
if (internals.ActivityMgr.UpdateActivityIndex(itemIndexEntry, dbUpdateInfo))
stats.EntriesUpdated++;
else SaLogger.Warn(fn + " Failed to update Activity Index - " + internals.ActivityMgr.ErrorStateMessage);
}
// Remove the entry from the 'required' list.
// Entries remaining in this list will be created.
indexEntries.Remove(indexEntry);
}
else
{
// Index entry no longer required.
if (internals.ActivityMgr.DeleteActivityIndex(itemIndexEntry, dbUpdateInfo))
stats.EntriesDeleted++;
else SaLogger.Warn(fn + " Failed to delete Activity Index - " + internals.ActivityMgr.ErrorStateMessage);
}
}
// The remaining required Index entries will be created.
foreach (IndexEntryData indexEntry in indexEntries)
{
SaxActivityIndex itemIndexEntry = internals.ActivityMgr.CreateActivityIndex(indexEntry.ItemId, indexEntry.SportId, indexEntry.RegionId, indexEntry.EffectiveOn);
if (internals.ActivityMgr.InsertActivityIndex(itemIndexEntry, dbUpdateInfo))
stats.EntriesInserted++;
else SaLogger.Warn(fn + " Failed to insert Activity Index - " + internals.ActivityMgr.ErrorStateMessage);
}
// Log statistics.
List<String> statsLog = new List<string>();
if (stats.EntriesInserted > 0) statsLog.Add("Inserted: " + stats.EntriesInserted);
if (stats.EntriesUpdated > 0) statsLog.Add("Updated: " + stats.EntriesUpdated);
if (stats.EntriesDeleted > 0) statsLog.Add("Deleted: " + stats.EntriesDeleted);
SaLogger.Info(fn + " Activity Index stored for " + ((Sax.DataItemType)indexData.ItemId.Type).ToString() + ":" + indexData.ItemId.Id.ToLong() + "." + statsLog.Concat("", "", ". "));
return true;
}
//-------------------------------------------------------------------
protected override bool ClearActivityIndex(ItemId itemId, DbUpdateInfo dbUpdateInfo)
{
String fn = GetType().Name + ".ClearActivityIndex";
String tableName = SaxDbDfn_Core.GetTableName(SaxDbDfn_Core.TableIdx.ActivityIndex);
String filter =
SaxDbDfn_Core.GetColumnName(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemType) + "=" + itemId.Type +
" And " + SaxDbDfn_Core.GetColumnName(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemId) + "=" + itemId.Id.ToLong();
ISacsDatabase db = SaxGlobals.App.GetCoreDatabase(SessionContext);
int recordsDeleted = db.ExecuteDelete(tableName, filter);
SaLogger.Info(fn + " Records deleted: " + recordsDeleted + ". Filter: " + filter, true);
return true;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -16,20 +16,24 @@ namespace Saxonite.Net.Core.Web.Base.Consoles
{
//-------------------------------------------------------------------
// Data members.
private readonly SaxInternals internals;
private class DataMembers
{
public SaxInternals internals;
}
private DataMembers data = new DataMembers();
//-------------------------------------------------------------------
// Protected construction.
protected SaxDataCacheConsole_Web(SaxSessionContext sessionContext) :
base(sessionContext)
{
internals = new SaxInternals(sessionContext);
data.internals = new SaxInternals(sessionContext, false);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
internals.Dispose();
data.internals.Dispose();
}
base.Dispose(disposing);
}
@@ -38,13 +42,30 @@ namespace Saxonite.Net.Core.Web.Base.Consoles
// Utilities.
protected abstract void ForceRefresh();
public override void PopulateCachedDataToInternals(SaxInternals internals)
{
if (regionsData.regionDataMap == null) ForceRefresh();
// Regions.
List<SaxRegion.Data> activeRegionDataList = new List<SaxRegion.Data>();
lock (regionsData)
activeRegionDataList.AddRange(regionsData.activeRegionDataList);
internals.PopulateRegionCache(activeRegionDataList);
// Sports.
List<SaxSport.Data> activeSportDataList = new List<SaxSport.Data>();
lock (sportsData)
activeSportDataList.AddRange(sportsData.activeSportDataList);
internals.PopulateSportCache(activeSportDataList);
}
//-------------------------------------------------------------------
// Regions.
public bool RegionsSupported { get; protected set; } = false;
private class RegionsData
{
public Dictionary<SysId, SaxRegion> regions = null;
public List<SaxRegion> activeRegions = null;
public Dictionary<SysId, SaxRegion.Data> regionDataMap = null;
public List<SaxRegion.Data> activeRegionDataList = null;
}
private readonly RegionsData regionsData = new RegionsData();
public override List<SaxRegion> GetActiveRegions()
@@ -52,62 +73,69 @@ namespace Saxonite.Net.Core.Web.Base.Consoles
String fn = GetType().Name + ".ActiveRegions";
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
List<SaxRegion> activeRegions = new List<SaxRegion>();
if (regionsData.regions == null) ForceRefresh();
lock (regionsData)
List<SaxRegion.Data> activeRegionDataList = new List<SaxRegion.Data>();
if (regionsData.regionDataMap == null) ForceRefresh();
lock (regionsData)
{
if (regionsData.regions == null) throw new NotSupportedException(fn + " Regions not populated.");
activeRegions = new List<SaxRegion>(regionsData.activeRegions);
if (regionsData.regionDataMap == null) throw new NotSupportedException(fn + " Regions not populated.");
activeRegionDataList.AddRange(regionsData.activeRegionDataList);
}
foreach (SaxRegion.Data regionData in activeRegionDataList)
activeRegions.Add(GetRegion(regionData));
return activeRegions;
}
private bool IsRegionActive(SaxRegion region)
{
if (region == null) return false;
if (!region.IsStatusActive) return false;
if (SysId.IsValid(region.RegionIdParent))
return IsRegionActive(regionsData.regions.GetValue(region.RegionIdParent, null));
return true;
}
protected void RefreshRegions()
{
String fn = GetType().Name + ".RefreshRegions";
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
SaLogger.Info(fn + " START.", false);
DateTime startedAt = DateTime.Now;
List<SaxRegion> oldRegions = null;
Dictionary<SysId, SaxRegion> newRegions = new Dictionary<SysId, SaxRegion>();
// Query Regions.
// Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists.
Dictionary<SysId, SaxRegion.Data> newRegionDataMap = new Dictionary<SysId, SaxRegion.Data>();
List<SaxRegion.Data> newActiveRegionDataList = new List<SaxRegion.Data>();
// Query Regions to replace cache.
SaxRegion.Params prmRegion = new SaxRegion.Params();
prmRegion.StatusCat = SaxRegion.StatusCat.NotDeleted;
foreach (SaxRegion region in internals.ReferenceMgr.GetRegionList(prmRegion))
newRegions[region.Id] = region;
foreach (SaxRegion region in data.internals.ReferenceMgr.GetRegionList(prmRegion))
{
// Only interested in retaining the Data objects.
newRegionDataMap[region.Id] = region.GetData();
region.Dispose();
}
// Populate replacement list of active regions.
foreach (SaxRegion.Data regionData in newRegionDataMap.GetValues())
{
if (IsRegionActive(newRegionDataMap, regionData))
newActiveRegionDataList.Add(regionData.Clone());
}
// Replace the list of Regions.
lock (regionsData)
{
oldRegions = regionsData.regions?.GetValues() ?? new List<SaxRegion>();
regionsData.regions = newRegions;
// Populate list of active regions.
regionsData.activeRegions = new List<SaxRegion>();
foreach (SaxRegion region in regionsData.regions.GetValues())
{
if (IsRegionActive(region))
{
// Clone region to ensure internal list isn't modified.
SaxRegion activeRegion = internals.ReferenceMgr.CreateRegion();
activeRegion.SetData(region.GetData());
regionsData.activeRegions.Add(activeRegion);
}
}
SaLogger.Info(fn + " END. Regions found: " + regionsData.regions.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds), true);
// Assign the refreshed regions and active regions.
// NB The old regions don't need to be disposed as they are SaxRegion.Data objects.
regionsData.regionDataMap = newRegionDataMap;
regionsData.activeRegionDataList = newActiveRegionDataList;
}
// Dispose old regions.
oldRegions.ClearAndDispose<SaxRegion>();
oldRegions = null;
SaLogger.Info(fn + " END. Regions found: " + regionsData.regionDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds), true);
}
private static bool IsRegionActive(Dictionary<SysId, SaxRegion.Data> regionDataMap, SaxRegion.Data regionData)
{
if (regionData == null) return false;
if (!regionData.IsStatusActive) return false;
if (SysId.IsValid(regionData.RegionIdParent))
return IsRegionActive(regionDataMap, regionDataMap.GetValue(regionData.RegionIdParent, null));
return true;
}
private SaxRegion GetRegion(SaxRegion.Data regionData)
{
if (regionData == null) return null;
SaxRegion region = data.internals.ReferenceMgr.CreateRegion();
region.SetData(regionData);
return region;
}
//-------------------------------------------------------------------
@@ -115,8 +143,8 @@ namespace Saxonite.Net.Core.Web.Base.Consoles
public bool SportsSupported { get; protected set; } = false;
private class SportsData
{
public Dictionary<SysId, SaxSport> sports = null;
public List<SaxSport> activeSports = null;
public Dictionary<SysId, SaxSport.Data> sportDataMap = null;
public List<SaxSport.Data> activeSportDataList = null;
}
private readonly SportsData sportsData = new SportsData();
public override List<SaxSport> GetActiveSports()
@@ -124,63 +152,67 @@ namespace Saxonite.Net.Core.Web.Base.Consoles
String fn = GetType().Name + ".ActiveSports";
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
List<SaxSport> activeSports = new List<SaxSport>();
if (sportsData.sports == null) ForceRefresh();
List<SaxSport.Data> activeSportDataList = new List<SaxSport.Data>();
if (sportsData.sportDataMap == null) ForceRefresh();
lock (sportsData)
{
foreach (SaxSport sport in sportsData.sports.GetValues())
{
if (IsSportActive(sport))
{
// Clone sport to ensure internal list isn't modified.
SaxSport activeSport = internals.ReferenceMgr.CreateSport();
activeSport.SetData(sport.GetData());
activeSports.Add(activeSport);
}
}
if (sportsData.sportDataMap == null) throw new NotSupportedException(fn + " Sports not populated.");
activeSportDataList.AddRange(sportsData.activeSportDataList);
}
foreach (SaxSport.Data sportData in activeSportDataList)
activeSports.Add(GetSport(sportData));
return activeSports;
}
private bool IsSportActive(SaxSport sport)
{
if (sport == null) return false;
if (!sport.IsStatusActive) return false;
return true;
}
protected void RefreshSports()
{
String fn = GetType().Name + ".RefreshSports";
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
SaLogger.Info(fn + " START.", false);
DateTime startedAt = DateTime.Now;
List<SaxSport> oldSports = null;
Dictionary<SysId, SaxSport> newSports = new Dictionary<SysId, SaxSport>();
// Query Sports.
// Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists.
Dictionary<SysId, SaxSport.Data> newSportDataMap = new Dictionary<SysId, SaxSport.Data>();
List<SaxSport.Data> newActiveSportDataList = new List<SaxSport.Data>();
// Query Sports to replace cache.
SaxSport.Params prmSport = new SaxSport.Params();
prmSport.StatusCat = SaxSport.StatusCat.NotDeleted;
foreach (SaxSport sport in internals.ReferenceMgr.GetSportList(prmSport))
newSports[sport.Id] = sport;
foreach (SaxSport sport in data.internals.ReferenceMgr.GetSportList(prmSport))
{
// Only interested in retaining the Data objects.
newSportDataMap[sport.Id] = sport.GetData();
sport.Dispose();
}
// Populate replacement list of active sports.
foreach (SaxSport.Data sportData in newSportDataMap.GetValues())
{
if (IsSportActive(newSportDataMap, sportData))
newActiveSportDataList.Add(sportData.Clone());
}
// Replace the list of Sports.
lock (sportsData)
{
oldSports = sportsData.sports?.GetValues() ?? new List<SaxSport>();
sportsData.sports = newSports;
// Populate list of active sports.
sportsData.activeSports = new List<SaxSport>();
foreach (SaxSport sport in sportsData.sports.GetValues())
{
if (IsSportActive(sport))
sportsData.activeSports.Add(sport);
}
SaLogger.Info(fn + " END. Sports found: " + sportsData.sports.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds), true);
// Assign the refreshed sports and active sports.
// NB The old sports don't need to be disposed as they are SaxSport.Data objects.
sportsData.sportDataMap = newSportDataMap;
sportsData.activeSportDataList = newActiveSportDataList;
}
// Dispose old sports.
oldSports.ClearAndDispose<SaxSport>();
oldSports = null;
SaLogger.Info(fn + " END. Sports found: " + sportsData.sportDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds), true);
}
private static bool IsSportActive(Dictionary<SysId, SaxSport.Data> sportDataMap, SaxSport.Data sportData)
{
if (sportData == null) return false;
if (!sportData.IsStatusActive) return false;
return true;
}
private SaxSport GetSport(SaxSport.Data sportData)
{
if (sportData == null) return null;
SaxSport sport = data.internals.ReferenceMgr.CreateSport();
sport.SetData(sportData);
return sport;
}
//-------------------------------------------------------------------
@@ -83,6 +83,7 @@ namespace Saxonite.Net.Core.Web.Base.Controllers
public SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(SessionContext); return internals; } }
// Consoles.
public SaxDataCacheConsole DataCacheConsole { get { return Internals.DataCacheConsole; } }
public SaxActivityConsole ActivityConsole { get { return Internals.ActivityConsole; } }
// Managers.
public SaxReferenceMgr ReferenceMgr { get { return Internals.ReferenceMgr; } }
public SaxAccountMgr AccountMgr { get { return Internals.AccountMgr; } }
@@ -95,6 +96,7 @@ namespace Saxonite.Net.Core.Web.Base.Controllers
public List<TypeCodeDesc> OrganisationCategories { get { return Internals.OrganisationCategories; } }
public List<TypeCodeDesc> OrganisationRoles { get { return Internals.OrganisationRoles; } }
public List<TypeCodeDesc> OpportunityTypes { get { return Internals.OpportunityTypes; } }
public List<TypeCodeDesc> DataItemTypes { get { return Internals.DataItemTypes; } }
// Email.
public SaEmailConfig EmailConfig { get { return App.EmailConfig; } }
public SaxEmailConsole EmailConsole { get { return Internals.EmailConsole; } }
+12 -10
View File
@@ -48,6 +48,7 @@ namespace Saxonite.Net.Core.Web.Base.Models
protected SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(SessionContext); return internals; } }
// Consoles.
protected SaxDataCacheConsole DataCacheConsole { get { return Internals.DataCacheConsole; } }
protected SaxActivityConsole ActivityConsole { get { return Internals.ActivityConsole; } }
// Managers.
protected SaxReferenceMgr ReferenceMgr { get { return Internals.ReferenceMgr; } }
protected SaxAccountMgr AccountMgr { get { return Internals.AccountMgr; } }
@@ -60,6 +61,7 @@ namespace Saxonite.Net.Core.Web.Base.Models
public List<TypeCodeDesc> OrganisationCategories { get { return Internals.OrganisationCategories; } }
public List<TypeCodeDesc> OrganisationRoles { get { return Internals.OrganisationRoles; } }
public List<TypeCodeDesc> OpportunityTypes { get { return Internals.OpportunityTypes; } }
public List<TypeCodeDesc> DataItemTypes { get { return Internals.DataItemTypes; } }
//-------------------------------------------------------------------
// Page configuration.
@@ -98,22 +100,22 @@ namespace Saxonite.Net.Core.Web.Base.Models
/// <para>In many cases the database columns allow plenty of extra space for the value, in case they need to be encrypted.</para>
/// <para>Hence, several cases where the value entered in the control isn't allowed the full database length.</para>
/// </summary>
public int GetControlMaxLength(SaxDbDfn.ColumnIdx column)
public int GetControlMaxLength(SaxDbDfn_Core.ColumnIdx column)
{
int maxLength = (int)SaxDbDfn.GetColTextLen(column);
int maxLength = (int)SaxDbDfn_Core.GetColTextLen(column);
switch (column)
{
case SaxDbDfn.ColumnIdx.UserAccount_EmailAddress:
case SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress:
return Math.Min(maxLength, 60);
case SaxDbDfn.ColumnIdx.UserAccount_FirstName:
case SaxDbDfn.ColumnIdx.UserAccount_LastName:
case SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName:
case SaxDbDfn_Core.ColumnIdx.UserAccount_LastName:
return Math.Min(maxLength, 40);
case SaxDbDfn.ColumnIdx.Organisation_Name:
case SaxDbDfn_Core.ColumnIdx.Organisation_Name:
return Math.Min(maxLength, 80);
case SaxDbDfn.ColumnIdx.UserAccount_PhoneMain:
case SaxDbDfn.ColumnIdx.UserAccount_PhoneMobile:
case SaxDbDfn.ColumnIdx.Organisation_PhoneMain:
case SaxDbDfn.ColumnIdx.Organisation_PhoneAlt:
case SaxDbDfn_Core.ColumnIdx.UserAccount_PhoneMain:
case SaxDbDfn_Core.ColumnIdx.UserAccount_PhoneMobile:
case SaxDbDfn_Core.ColumnIdx.Organisation_PhoneMain:
case SaxDbDfn_Core.ColumnIdx.Organisation_PhoneAlt:
return Math.Min(maxLength, 20);
//case SaxDbDfn.ColumnIdx.UserAccount_Postcode:
//case SaxDbDfn.ColumnIdx.ItemAddress_Postcode: