/// <summary> /// Store an entity into the JSON result structure. /// </summary> /// <param name="result">The result.</param> /// <param name="entityData">The entity data.</param> private void AppendEntityToResult(JsonQueryResult result, EntityData entityData) { long entityKey = entityData.Id.Id; // Avoid circular loops within a query. if (_entitiesVisited.Contains(entityKey)) { return; } _entitiesVisited.Add(entityKey); Dictionary <string, object> entityJson; if (!result.Entities.TryGetValue(entityKey, out entityJson)) { entityJson = new Dictionary <string, object>( ); result.Entities.Add(entityKey, entityJson); } AppendFieldsToEntity(result, entityData, entityJson); AppendRelationshipsToEntity(result, entityData, entityJson); if (entityJson.Count <= 0) { // Note: always add, then remove if not needed. Otherwise it's not present in the dictionary when we recurse, // which can lead to infinite loops. result.Entities.Remove(entityKey); } }
public EntityPackageContext() { EntitiesVisited = new HashSet <long>(); Svc = new EntityInfoService(); ; Result = new JsonQueryResult(); }
#pragma warning restore 618 /// <summary> /// Initializes a new instance of the <see cref="EntityPackage" /> class. /// </summary> public EntityPackage( ) { _entitiesVisited = new HashSet <long>( ); #pragma warning disable 618 _svc = new EntityInfoService( ); #pragma warning restore 618 _result = new JsonQueryResult( ); }
/// <summary> /// Store relationships into the JSON entity structure. /// </summary> /// <param name="result">The result.</param> /// <param name="entityData">The entity being read.</param> /// <param name="entityJson">JSON for holding member data for the individual entity.</param> private void AppendRelationshipsToEntity(JsonQueryResult result, EntityData entityData, Dictionary <string, object> entityJson) { if (entityData != null && entityData.Relationships != null) { // Visit relationships foreach (RelationshipData rel in entityData.Relationships) { // Register the relationship member metadata // Note: we need to first see if there is a container for the relationship in either direction RegisterRelationshipMember(result, rel); // Get relationship data. // Holds a mixture of either long or JsonRelationshipInstance var relResult = new List <object>( ); if (rel != null && rel.Instances != null) { foreach (RelationshipInstanceData relInst in rel.Instances) { // If we have a relationship instance, send a complex object // Otherwise just send the ID of the entity we're relating to. if (relInst.RelationshipInstanceEntity != null) { var relInstResult = new JsonRelationshipInstance( ); if (relInst.Entity != null) { relInstResult.EntityId = relInst.Entity.Id.Id; } relInstResult.RelationshipInstance = relInst.RelationshipInstanceEntity.Id.Id; relResult.Add(relInstResult); // Recursively visit the relationship instance entity AppendEntityToResult(result, relInst.RelationshipInstanceEntity); } else if (relInst.Entity != null) { long relatedId = relInst.Entity.Id.Id; relResult.Add(relatedId); } // Recursively visit the related entity AppendEntityToResult(result, relInst.Entity); } } // Store the data in the entity StoreRelationshipData(entityJson, rel, relResult); } } }
/// <summary> /// Store fields into the JSON entity structure. /// </summary> /// <param name="result">The result.</param> /// <param name="entityData">The entity being read.</param> /// <param name="entityJson">JSON for holding member data for the individual entity.</param> private void AppendFieldsToEntity(JsonQueryResult result, EntityData entityData, Dictionary <string, object> entityJson) { Dictionary <long, JsonMember> members = result.Members; // Visit fields foreach (FieldData field in entityData.Fields) { long fieldId = field.FieldId.Id; // Register the field member JsonMember member; if (members.TryGetValue(fieldId, out member)) { if (field.FieldId.Alias != null && member.Alias == null) { member.Alias = GetAliasString(field.FieldId); } } else { member = new JsonMember { Alias = GetAliasString(field.FieldId), DataType = DataTypeHelper.FromDatabaseType(field.Value.Type).ToString( ) }; members.Add(fieldId, member); } object value = field.Value.Value; // Ensure empty strings are represented as nulls - this is by design! if (value is string && (( string )value).Length == 0) { value = null; } // Store the data in the entity entityJson[fieldId.ToString(CultureInfo.InvariantCulture)] = value; } }
/// <summary> /// Registers a 'member' entry for the relationship. /// </summary> /// <param name="result">The result.</param> /// <param name="rel">The relative.</param> private void RegisterRelationshipMember(JsonQueryResult result, RelationshipData rel) { Dictionary <long, JsonMember> members = result.Members; long relId = rel.RelationshipTypeId.Id; // Determine if there is an existing member registered for this relationship. // And create one if necessary. JsonMember member; if (!members.TryGetValue(relId, out member)) { member = new JsonMember( ); members.Add(relId, member); } // Then we ensure there is information for the member in the particular direction that we're interested in. JsonRelationshipMember relMember = rel.IsReverseActual ? member.Reverse : member.Forward; if (relMember == null) { relMember = new JsonRelationshipMember { Alias = GetAliasString(rel.RelationshipTypeId), IsLookup = rel.IsLookup }; if (rel.IsReverseActual) { member.Reverse = relMember; } else { member.Forward = relMember; } } else if (relMember.Alias == null && rel.RelationshipTypeId.Alias != null) { relMember.Alias = GetAliasString(rel.RelationshipTypeId); } }
/// <summary> /// Add a request for a set of entities and queries. /// </summary> /// <param name="batch">The batch.</param> /// <param name="hintText">A hint about what this query is doing. Use for logging/diagnostics only.</param> /// <exception cref="System.ArgumentException"> /// </exception> /// <exception cref="System.Exception">Internal error: batch query mismatch.</exception> public void AddEntityRequestBatch(JsonQueryBatchRequest batch, string hintText = null) { JsonQueryResult result = _result; #pragma warning disable 618 EntityInfoService svc = _svc; #pragma warning restore 618 var memberRequests = new EntityMemberRequest[batch.Queries.Length]; foreach (JsonQuerySingleRequest request in batch.Requests) { string requestHintText = request.Hint ?? hintText; _entitiesVisited.Clear( ); var singleResult = new JsonSingleQueryResult { Hint = request.Hint, Ids = new List <long>( ) }; try { EntityMemberRequest memberRequest; // Do not require access to the individual query components using (PerformanceCounters.Measure(EntityInfoServicePerformanceCounters.CategoryName, EntityInfoServicePerformanceCounters.RequestCountersPrefix)) using (new SecurityBypassContext( )) { // Get/parse the request query. (Either parse, or load from previous cache as part of this request). if (request.QueryIndex < 0 || request.QueryIndex >= batch.Queries.Length) { EventLog.Application.WriteError("Cannot locate query string."); singleResult.Code = HttpStatusCode.BadRequest; continue; } memberRequest = memberRequests[request.QueryIndex]; if (memberRequest == null) { string queryString = batch.Queries[request.QueryIndex]; //// Parse the request try { memberRequest = Factory.RequestParser.ParseRequestQuery(queryString); } catch (Exception ex) { EventLog.Application.WriteError(ex.ToString( )); singleResult.Code = HttpStatusCode.BadRequest; continue; } memberRequests[request.QueryIndex] = memberRequest; } } // Get the entityRefs IEnumerable <EntityRef> ids = Enumerable.Empty <EntityRef>( ); if (request.Aliases != null && request.Aliases.Length > 0) { ids = ids.Union(request.Aliases.Select(ConvertId)); } if (request.Ids != null && request.Ids.Length > 0) { ids = ids.Union(request.Ids.Select(ConvertId)); } IList <EntityRef> entityRefs = ids.ToList( ); if (entityRefs.All(er => er == null)) { singleResult.Code = HttpStatusCode.NotFound; continue; } // Run the request IEnumerable <EntityData> entitiesData; try { if (BulkRequestHelper.IsValidForBulkRequest(memberRequest)) { var entityRequest = new EntityRequest { QueryType = request.QueryType, RequestString = memberRequest.RequestString, Hint = requestHintText, Entities = entityRefs, Filter = request.Filter, Isolated = request.Isolated }; // Run the request entitiesData = BulkRequestRunner.GetEntities(entityRequest); singleResult.Cached = entityRequest.ResultFromCache; } else { EventLog.Application.WriteWarning( "Request cannot be handled by BulkRequest. Hint: {0}\n{1}", requestHintText, memberRequest.RequestString); switch (request.QueryType) { case QueryType.Instances: entitiesData = svc.GetEntitiesByType(entityRefs.Single( ), true, memberRequest); break; case QueryType.ExactInstances: entitiesData = svc.GetEntitiesByType(entityRefs.Single( ), false, memberRequest); break; case QueryType.Basic: case QueryType.BasicWithDemand: entitiesData = svc.GetEntitiesData(entityRefs, memberRequest); break; default: throw new ArgumentException(string.Format("Unknown query type {0}", request.QueryType)); } } } catch (ArgumentException ex) { // sorry world! if (ex.Message.Contains("does not represent a known entity")) { singleResult.Code = HttpStatusCode.BadRequest; continue; } throw; } // Skip results where access is denied foreach (EntityData entityData in entitiesData.Where(ed => ed != null)) { AppendEntityToResult(result, entityData); singleResult.Ids.Add(entityData.Id.Id); } // Set the result to NotFound for Basic and BasicWithDemand only if (request.QueryType == QueryType.Basic || request.QueryType == QueryType.BasicWithDemand) { singleResult.Code = singleResult.Ids.Count > 0 ? HttpStatusCode.OK : HttpStatusCode.NotFound; } else { singleResult.Code = HttpStatusCode.OK; } } catch (PlatformSecurityException ex) { EventLog.Application.WriteWarning(ex.ToString( )); singleResult.Ids.Clear( ); singleResult.Code = HttpStatusCode.Forbidden; } catch (Exception ex) { EventLog.Application.WriteError(ex.ToString( )); singleResult.Code = HttpStatusCode.InternalServerError; } finally { // Place in the finally block so we are certain that it gets run exactly once per iteration result.Results.Add(singleResult); } } if (result.Results.Count != batch.Requests.Length) { // We are indexing by ID, so we must always ensure that request position matches result position. throw new Exception("Internal error: batch query mismatch."); } }