I am posting this topic as a discussion because I am doubtful there is a answer to this issue that can be provided in the forum, and I don’t want to post a topic that will not have an answer. This drives Chris crazy. :-)
I customized the Projects screen so that I can “filter” the results that show up in the Attributes tab.
My customer has over 2000 project attributes. Yes, you read that correctly, over 2000.
I created a cross reference table that links project attributes to the Project template. When a template is selected, I overrode the Answers view to discard the attributes that are not linked to the template.
In my override, I get the current PMProject from the cache. I then store the template ID from that record into a variable to be used later in the override to do the filtering.
When the screen is first loaded or refreshed, there is only one record in the PMProject cache.
If you make a change to an attribute value and save the screen, another project gets added to the cache.
In my case, I am working on PR00000018. In the RowSelected handler (for testing), I verify that there is only one record in the PMProject cache.
After making a change to an attribute and saving the screen, PR00000019 gets added to the cache.
This screws up the Answers override because there are now TWO records in the PMProject cache. I had a workaround to grab the template ID of the first record in the cache and ignore any others. I am not comfortable that I will always get the correct template id.
So, in RowSelected for PMProject, I loop through the PMProject cache and remove any “extra” items from the cache that are not the same as the current project.
My code below shows how I am doing this in the RowSelected handler.
In the code below, NOTE 1 shows where I get the project record from the cache.
NOTE 2 shows where I create a list of attributes that should be included in the answer result set.
NOTE 3 shows where I discard non-matching attributes.
I don’t understand how PR00000019 is being added to the cache. It makes no sense.
This is the code for this extension. You can see I am not “mucking around” with the project in any handlers other than the RowSelected and that is just to remove the “extra” project from the cache.
So HOW THE HECK is PR00000019 getting added to the cache?
using Greenspark;
using PX.Api;
using PX.Common;
using PX.Data;
using PX.Data.BQL;
using PX.Data.BQL.Fluent;
using PX.Objects.CN.Common.Extensions;
using PX.Objects.CR;
using PX.Objects.CS;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace PX.Objects.PM
{
public class ProjectEntry_Extension : PXGraphExtension<PX.Objects.PM.ProjectEntry>
{
public static bool IsActive() => true;
[PXViewName(Messages.ProjectAnswers)]
public TemplateAttributeList<PMProject> Answers;
public class TemplateAttributeList<TEntity> : CRAttributeList<TEntity>
{
#region SelectDelegate - Redefined
protected new virtual IEnumerable SelectDelegate()
{
object current = _Graph.Caches[typeof(TEntity)].Current;
return MySelectInternal(_Graph, current);
}
#endregion
#region Copied from CRAttributeList to make this work
private readonly EntityHelper _helper;
private const string CbApiValueFieldName = "Value$value";
private const string CbApiAttributeIDFieldName = "AttributeID$value";
public TemplateAttributeList(PXGraph graph) : base(graph)
{
_Graph = graph;
_helper = new EntityHelper(graph);
View = (graph.IsExport ? GetExportOptimizedView() : new PXView(graph, isReadOnly: false, new Select3<CSAnswers, OrderBy<Asc<CSAnswers.order>>>(), new PXSelectDelegate(SelectDelegate)));
PXDependToCacheAttribute.AddDependencies(View, new Type[1] { typeof(TEntity) });
_Graph.EnsureCachePersistence(typeof(CSAnswers));
PXDBAttributeAttribute.Activate(_Graph.Caches[typeof(TEntity)]);
_Graph.FieldUpdating.AddHandler<CSAnswers.value>(FieldUpdatingHandler);
_Graph.FieldSelecting.AddHandler<CSAnswers.value>(FieldSelectingHandler);
_Graph.FieldSelecting.AddHandler<CSAnswers.isRequired>(IsRequiredSelectingHandler);
_Graph.FieldSelecting.AddHandler<CSAnswers.attributeCategory>(AttributeCategorySelectingHandler);
_Graph.FieldSelecting.AddHandler<CSAnswers.attributeID>(AttrFieldSelectingHandler);
_Graph.RowPersisting.AddHandler<CSAnswers>(RowPersistingHandler);
_Graph.RowPersisting.AddHandler<TEntity>(ReferenceRowPersistingHandler);
_Graph.RowUpdating.AddHandler<TEntity>(ReferenceRowUpdatingHandler);
_Graph.RowDeleted.AddHandler<TEntity>(ReferenceRowDeletedHandler);
_Graph.RowInserted.AddHandler<TEntity>(RowInsertedHandler);
_Graph.Caches<CSAnswers>().Fields.Add("Value$value");
_Graph.Caches<CSAnswers>().Fields.Add("AttributeID$value");
_Graph.FieldSelecting.AddHandler(typeof(CSAnswers), "Value$value", CbApiValueFieldSelectingHandler);
_Graph.FieldSelecting.AddHandler(typeof(CSAnswers), "AttributeID$value", CbApiAttributeIdFieldSelectingHandler);
}
private PXView GetExportOptimizedView()
{
object row = _Graph.Caches[typeof(TEntity)].CreateInstance();
Type classIdField = GetClassIdField(row);
Type nestedType = typeof(TEntity).GetNestedType("noteID");
BqlCommand bqlCommand = BqlTemplate.OfCommand<Select2<CSAnswers, InnerJoin<CSAttribute, On<CSAnswers.attributeID, Equal<CSAttribute.attributeID>>, InnerJoin<CSAttributeGroup, On<CSAnswers.attributeID, Equal<CSAttributeGroup.attributeID>>>>, Where<CSAttributeGroup.isActive, Equal<True>, And<CSAttributeGroup.entityType, Equal<TypeNameConst>, And<CSAttributeGroup.entityClassID, Equal<Current<BqlPlaceholder.A>>, And<CSAnswers.refNoteID, Equal<Current<BqlPlaceholder.B>>>>>>>>.Replace<BqlPlaceholder.A>(classIdField).Replace<BqlPlaceholder.B>(nestedType).ToCommand();
return new PXView(_Graph, isReadOnly: true, bqlCommand);
}
public IEnumerable<CSAnswers> MySelectInternal(PXGraph graph, object row)
{
if (row == null)
{
yield break;
}
Guid? noteId = GetNoteId(row);
if (!noteId.HasValue)
{
yield break;
}
//NOTE 1
//JRS CODE
//Get the template ID from the project. The templateID is used below to filter the result set
//Loop through the cache to get the current project
//SHOULD ONLY BE 1
PXCache projectCache = graph.Caches[typeof(PMProject)];
int? templateID = null;
int test = 0;
foreach (PMProject proj in projectCache.Cached)
{
test++;
if (proj.TemplateID != null && templateID == null)
{
templateID = proj.TemplateID;
}
}
if (test > 1)
{
string joe = "joe";
}
PXCache answerCache = graph.Caches[typeof(CSAnswers)];
PXCache entityCache = graph.Caches[row.GetType()];
PXEntryStatus status = entityCache.GetStatus(row);
List<CSAnswers> answerList = ((status != PXEntryStatus.Inserted && status != PXEntryStatus.InsertedDeleted) ? PXSelectBase<CSAnswers, PXSelect<CSAnswers, Where<CSAnswers.refNoteID, Equal<Required<CSAnswers.refNoteID>>, And<CSAnswers.attributeID, NotEqual<PX.Objects.IN.Matrix.Attributes.MatrixAttributeSelectorAttribute.dummyAttributeName>>>>.Config>.Select(graph, noteId).FirstTableItems.ToList() : (from CSAnswers x in answerCache.Inserted
where x.RefNoteID == noteId && !"~MX~DUMMY~".Equals(x.AttributeID, StringComparison.OrdinalIgnoreCase)
select x).ToList());
string classId = GetClassId(row);
CRAttribute.ClassAttributeList classAttributeList = new CRAttribute.ClassAttributeList();
if (classId != null)
{
classAttributeList = CRAttribute.EntityAttributes(GetEntityTypeFromAttribute(row), classId);
(from _ in classAttributeList
where _.NotInClass
select _.ID).ToList().ForEach(delegate (string _)
{
classAttributeList.Remove(_);
});
}
if (graph.IsImport && PXView.SortColumns.Any() && PXView.Searches.Any())
{
int columnIndex = Array.FindIndex(PXView.SortColumns, (string x) => x.Equals(typeof(CSAnswers.attributeID).Name, StringComparison.OrdinalIgnoreCase));
if (columnIndex >= 0 && columnIndex < PXView.Searches.Length)
{
object searchValue = PXView.Searches[columnIndex];
if (searchValue != null)
{
CRAttribute.Attribute attributeDefinition = CRAttribute.Attributes[searchValue.ToString()] ?? CRAttribute.AttributesByDescr[searchValue.ToString()];
if (attributeDefinition == null)
{
string message = GetSelectInternalExceptionMessage();
throw new PXSetPropertyException<CSAnswers.attributeID>(message, searchValue.ToString());
}
if (classAttributeList[attributeDefinition.ID] == null)
{
classAttributeList.Add(new CRAttribute.AttributeExt(attributeDefinition, null, required: false, isActive: true)
{
NotInClass = true
});
}
}
}
}
if (answerList.Count == 0 && classAttributeList.Count == 0)
{
yield break;
}
List<string> attributeIdListClass = classAttributeList.Select((CRAttribute.AttributeExt x) => x.ID).Except(answerList.Select((CSAnswers x) => x.AttributeID)).ToList();
List<string> attributeIdListIntersection = classAttributeList.Select((CRAttribute.AttributeExt x) => x.ID).Intersect(answerList.Select((CSAnswers x) => x.AttributeID)).Distinct()
.ToList();
bool cacheIsDirty = answerCache.IsDirty;
List<CSAnswers> output = new List<CSAnswers>();
foreach (string attributeId in attributeIdListClass)
{
CRAttribute.AttributeExt classAttributeDefinition = classAttributeList[attributeId];
if ((PXSiteMap.IsPortal && classAttributeDefinition.IsInternal && string.IsNullOrEmpty(classAttributeDefinition.DefaultValue)) || !classAttributeDefinition.IsActive)
{
continue;
}
CSAnswers answer2 = (CSAnswers)answerCache.CreateInstance();
answer2.AttributeID = classAttributeDefinition.ID;
answer2.RefNoteID = noteId;
answer2.Value = GetDefaultAnswerValue(classAttributeDefinition);
if (classAttributeDefinition.ControlType == 4)
{
if (bool.TryParse(answer2.Value, out var value))
{
answer2.Value = Convert.ToInt32(value).ToString(CultureInfo.InvariantCulture);
}
else if (answer2.Value == null)
{
answer2.Value = 0.ToString();
}
}
answer2.IsRequired = classAttributeDefinition.Required;
answer2.NotInClass = classAttributeDefinition.NotInClass;
Dictionary<string, object> keys = new Dictionary<string, object>();
string[] array = answerCache.Keys.ToArray();
foreach (string key in array)
{
keys[key] = answerCache.GetValue(answer2, key);
}
answerCache.Locate(keys);
answer2 = (CSAnswers)(answerCache.Locate(answer2) ?? answerCache.Insert(answer2));
if (!PXSiteMap.IsPortal || !classAttributeDefinition.IsInternal)
{
output.Add(answer2);
}
}
foreach (CSAnswers answer4 in answerList.Where((CSAnswers x) => attributeIdListIntersection.Contains(x.AttributeID)).ToList())
{
CRAttribute.AttributeExt classAttributeDefinition2 = classAttributeList[answer4.AttributeID];
if ((!PXSiteMap.IsPortal || !classAttributeDefinition2.IsInternal) && classAttributeDefinition2.IsActive)
{
if (answer4.Value == null && classAttributeDefinition2.ControlType == 4)
{
answer4.Value = bool.FalseString;
}
if (!answer4.IsRequired.HasValue || classAttributeDefinition2.Required != answer4.IsRequired)
{
answer4.IsRequired = classAttributeDefinition2.Required;
bool fieldValue = View.Cache.GetValueExt<CSAnswers.isRequired>(answer4) is PXFieldState fieldState && ((bool?)fieldState.Value).GetValueOrDefault();
answer4.IsRequired = classAttributeDefinition2.Required || fieldValue;
}
output.Add(answer4);
}
}
answerCache.IsDirty = cacheIsDirty;
output = (from x in output
orderby classAttributeList.Contains(x.AttributeID) ? classAttributeList.IndexOf(x.AttributeID) : x.Order.GetValueOrDefault(), x.AttributeID
select x).ToList();
short attributeOrder = 0;
//NOTE 2
//JRS Code
//Use a select to get the list of Attribute ID's to return
List<string> myList = new List<string>();
foreach (ICSProjTemplateAttrLinks x in SelectFrom<ICSProjTemplateAttrLinks>.Where<ICSProjTemplateAttrLinks.contractID.IsEqual<@P.AsInt>>
.View.Select(_Graph, templateID))
{
myList.Add(x.AttributeID);
}
foreach (CSAnswers answer in output)
{
//NOTE 3
//JRS CODE - if the attributeID in the output is not in my list, ignore it
string matchingvalues = myList.Find(x => x.Contains(answer.AttributeID));
if (matchingvalues == null)
{
continue;
}
answer.Order = attributeOrder++;
yield return answer;
}
}
internal virtual void CbApiValueFieldSelectingHandler(PXCache sender, PXFieldSelectingEventArgs e)
{
if (e.Row is CSAnswers answer)
{
// set in FieldUpdating
if (sender.GetAttributes<CSAnswers.value>(answer).OfType<PXUIFieldAttribute>().FirstOrDefault() is IPXInterfaceField uiField)
{
if (uiField.ErrorText != null)
{
e.ReturnState = PXFieldState.CreateInstance(uiField.ErrorValue, typeof(String),
errorLevel: PXErrorLevel.Error,
error: uiField.ErrorText);
}
}
e.ReturnValue = answer.Value;
}
}
internal virtual void CbApiAttributeIdFieldSelectingHandler(PXCache sender, PXFieldSelectingEventArgs e)
{
if (e.Row is CSAnswers answer)
e.ReturnValue = answer.AttributeID;
}
#endregion
}
#region Event Handlers
protected void PMProject_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
var row = (PMProject)e.Row;
if (row == null) return;
int c = 0;
PXCache projectCache = Base.Caches[typeof(PMProject)];
foreach (PMProject proj in projectCache.Cached)
{
c++;
if (proj.ContractID != row.ContractID)
{
projectCache.Remove(proj);
}
}
if (c > 1)
{
string joe = "joe";
}
c = 0;
foreach (PMProject proj in projectCache.Cached)
{
c++;
}
if (c > 1)
{
string joe2 = "joe";
}
}
#endregion
}
}