internal override ResolvedAttributeSetBuilder ConstructResolvedAttributes()

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;
        }