in objectModel/CSharp/Microsoft.CommonDataModel.ObjectModel/Cdm/CdmEntityAttributeDefinition.cs [245:471]
internal override ResolvedAttributeSetBuilder ConstructResolvedAttributes(ResolveOptions resOpt, CdmAttributeContext under = null)
{
// find and cache the complete set of attributes
// attributes definitions originate from and then get modified by subsequent re-defintions from (in this order):
// the entity used as an attribute, traits applied to that entity,
// the purpose of the attribute, any traits applied to the attribute.
ResolvedAttributeSetBuilder rasb = new ResolvedAttributeSetBuilder();
CdmAttributeContext underAtt = under;
AttributeContextParameters acpEnt = null;
if (!resOpt.InCircularReference)
{
if (this.Entity?.IsProjection == true)
{
// A Projection
// if the max depth is exceeded it should not try to execute the projection
if (!resOpt.DepthInfo.MaxDepthExceeded)
{
CdmProjection projDef = this.Entity.FetchObjectDefinition<CdmProjection>(resOpt);
ProjectionDirective projDirective = new ProjectionDirective(resOpt, this, ownerRef: this.Entity);
ProjectionContext projCtx = projDef.ConstructProjectionContext(projDirective, under);
rasb.ResolvedAttributeSet = projDef.ExtractResolvedAttributes(projCtx, under);
// from the traits of purpose and applied here
rasb.ResolvedAttributeSet.ApplyTraits(this.FetchResolvedTraits(resOpt));
}
}
else
{
// Resolution guidance
resOpt.UsedResolutionGuidance = true;
AttributeResolutionContext arc = this.FetchAttResContext(resOpt);
RelationshipInfo relInfo = arc.GetRelationshipInfo();
if (underAtt != null)
{
// make a context for this attreibute that holds the attributes that come up from the entity
acpEnt = new AttributeContextParameters
{
under = underAtt,
type = CdmAttributeContextType.Entity,
Name = this.Entity.FetchObjectDefinitionName(),
Regarding = this.Entity,
IncludeTraits = true
};
}
if (relInfo.IsByRef)
{
// make the entity context that a real recursion would have give us
if (under != null)
under = rasb.ResolvedAttributeSet.CreateAttributeContext(resOpt, acpEnt);
// if selecting from one of many attributes, then make a context for each one
if (under != null && relInfo.SelectsOne)
{
// the right way to do this is to get a resolved entity from the embedded entity and then
// look through the attribute context hierarchy for non-nested entityReferenceAsAttribute nodes
// that seems like a disaster waiting to happen given endless looping, etc.
// for now, just insist that only the top level entity attributes declared in the ref entity will work
CdmEntityDefinition entPickFrom = this.Entity.FetchObjectDefinition<CdmEntityDefinition>(resOpt);
CdmCollection<CdmAttributeItem> attsPick = entPickFrom?.Attributes;
if (entPickFrom != null && attsPick != null)
{
for (int i = 0; i < attsPick.Count; i++)
{
if (attsPick[i].ObjectType == CdmObjectType.EntityAttributeDef)
{
// a table within a table. as expected with a selectsOne attribute
// since this is by ref, we won't get the atts from the table, but we do need the traits that hold the key
// these are the same contexts that would get created if we recursed
// first this attribute
AttributeContextParameters acpEntAtt = new AttributeContextParameters
{
under = under,
type = CdmAttributeContextType.AttributeDefinition,
Name = attsPick[i].FetchObjectDefinitionName(),
Regarding = attsPick[i],
IncludeTraits = true
};
CdmAttributeContext pickUnder = rasb.ResolvedAttributeSet.CreateAttributeContext(resOpt, acpEntAtt);
CdmEntityReference pickEnt = (attsPick[i] as CdmEntityAttributeDefinition).Entity;
CdmAttributeContextType pickEntType = (pickEnt.FetchObjectDefinition<CdmObjectDefinition>(resOpt).ObjectType == CdmObjectType.ProjectionDef) ?
CdmAttributeContextType.Projection :
CdmAttributeContextType.Entity;
AttributeContextParameters acpEntAttEnt = new AttributeContextParameters
{
under = pickUnder,
type = pickEntType,
Name = pickEnt.FetchObjectDefinitionName(),
Regarding = pickEnt,
IncludeTraits = true
};
rasb.ResolvedAttributeSet.CreateAttributeContext(resOpt, acpEntAttEnt);
}
}
}
}
// if we got here because of the max depth, need to impose the directives to make the trait work as expected
if (resOpt.DepthInfo.MaxDepthExceeded)
{
if (arc.ResOpt.Directives == null)
arc.ResOpt.Directives = new AttributeResolutionDirectiveSet();
arc.ResOpt.Directives.Add("referenceOnly");
}
}
else
{
ResolveOptions resLink = resOpt.Copy();
resLink.SymbolRefSet = resOpt.SymbolRefSet;
rasb.MergeAttributes(this.Entity.FetchResolvedAttributes(resLink, acpEnt));
// need to pass up maxDepthExceeded if it was hit
if (resLink.DepthInfo.MaxDepthExceeded)
{
resOpt.DepthInfo = resLink.DepthInfo.Copy();
}
}
// from the traits of purpose and applied here, see if new attributes get generated
rasb.ResolvedAttributeSet.AttributeContext = underAtt;
rasb.ApplyTraits(arc);
rasb.GenerateApplierAttributes(arc, true); // true = apply the prepared traits to new atts
// this may have added symbols to the dependencies, so merge them
resOpt.SymbolRefSet.Merge(arc.ResOpt.SymbolRefSet);
// use the traits for linked entity identifiers to record the actual foreign key links
if (rasb.ResolvedAttributeSet?.Set != null && relInfo.IsByRef)
{
foreach (var att in rasb.ResolvedAttributeSet.Set)
{
if (att.ResolvedTraits != null)
{
var reqdTrait = att.ResolvedTraits.Find(resOpt, "is.linkedEntity.identifier");
if (reqdTrait == null)
{
continue;
}
if (reqdTrait.ParameterValues == null || reqdTrait.ParameterValues.Length == 0)
{
Logger.Warning(this.Ctx as ResolveContext, Tag, nameof(ConstructResolvedAttributes), this.AtCorpusPath, CdmLogCode.WarnLinkEntIdentArgsNotSupported, att.DisplayName, this.Entity.NamedReference);
continue;
}
var entReferences = new List<string>();
var attReferences = new List<string>();
Action<CdmEntityReference, string> addEntityReference = (CdmEntityReference entRef, string nameSpace) =>
{
var entDef = entRef.FetchObjectDefinition<CdmEntityDefinition>(resOpt);
if (entDef != null)
{
var otherResTraits = entRef.FetchResolvedTraits(resOpt);
ResolvedTrait identifyingTrait;
if (otherResTraits != null && (identifyingTrait = otherResTraits.Find(resOpt, "is.identifiedBy")) != null)
{
var attRef = identifyingTrait.ParameterValues.FetchParameterValueByName("attribute").Value;
string attNamePath = ((CdmObjectReferenceBase)attRef).NamedReference;
string attName = attNamePath.Split('/').Last();
string absoluteEntPath = Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(entDef.AtCorpusPath, entDef.InDocument);
entReferences.Add(absoluteEntPath);
attReferences.Add(attName);
}
}
};
if (relInfo.SelectsOne)
{
var entPickFrom = this.Entity.FetchObjectDefinition<CdmEntityDefinition>(resOpt);
var attsPick = entPickFrom?.Attributes.Cast<CdmObject>().ToList();
if (entPickFrom != null && attsPick != null)
{
for (int i = 0; i < attsPick.Count; i++)
{
if (attsPick[i].ObjectType == CdmObjectType.EntityAttributeDef)
{
var entAtt = attsPick[i] as CdmEntityAttributeDefinition;
addEntityReference(entAtt.Entity, this.InDocument.Namespace);
}
}
}
}
else
{
addEntityReference(this.Entity, this.InDocument.Namespace);
}
var constantEntity = this.Ctx.Corpus.MakeObject<CdmConstantEntityDefinition>(CdmObjectType.ConstantEntityDef);
constantEntity.EntityShape = this.Ctx.Corpus.MakeRef<CdmEntityReference>(CdmObjectType.EntityRef, "entityGroupSet", true);
constantEntity.ConstantValues = entReferences.Select((entRef, idx) => new List<string> { entRef, attReferences[idx] }).ToList();
var traitParam = this.Ctx.Corpus.MakeRef<CdmEntityReference>(CdmObjectType.EntityRef, constantEntity, false);
reqdTrait.ParameterValues.SetParameterValue(resOpt, "entityReferences", traitParam);
}
}
}
// a 'structured' directive wants to keep all entity attributes together in a group
if (arc.ResOpt.Directives?.Has("structured") == true)
{
// make one resolved attribute with a name from this entityAttribute that contains the set
// of atts we just put together.
ResolvedAttribute raSub = new ResolvedAttribute(arc.TraitsToApply.ResOpt, rasb.ResolvedAttributeSet, this.Name, underAtt);
if (relInfo.IsArray)
{
// put a resolved trait on this att group, hope I never need to do this again and then need to make a function for this
CdmTraitReference tr = this.Ctx.Corpus.MakeObject<CdmTraitReference>(CdmObjectType.TraitRef, "is.linkedEntity.array", true);
var t = tr.FetchObjectDefinition<CdmTraitDefinition>(resOpt);
ResolvedTrait rt = new ResolvedTrait(t, null, new List<dynamic>(), new List<bool>());
raSub.ResolvedTraits = raSub.ResolvedTraits.Merge(rt, true);
}
int depth = rasb.ResolvedAttributeSet.DepthTraveled;
rasb = new ResolvedAttributeSetBuilder();
rasb.ResolvedAttributeSet.AttributeContext = raSub.AttCtx; // this got set to null with the new builder
rasb.OwnOne(raSub);
rasb.ResolvedAttributeSet.DepthTraveled = depth;
}
}
}
// how ever they got here, mark every attribute from this entity attribute as now being 'owned' by this entityAtt
rasb.ResolvedAttributeSet.SetAttributeOwnership(this.Name);
rasb.ResolvedAttributeSet.DepthTraveled += 1;
return rasb;
}