/// <summary> /// Implementation for building the overall T-SQL query. /// </summary> private void BuildSql( ) { _result = new BulkSqlQuery { Request = _request }; // Walk the request object graph to get individual nodes var allRequestNodes = Delegates.WalkGraph(_request, rq => rq.Relationships.Select(r => r.RequestedMembers)); // Build info for each node int nextTag = 0; foreach (EntityMemberRequest node in allRequestNodes) { var nodeInfo = new RequestNodeInfo { Request = node, Tag = nextTag++ }; _requestNodeMap.Add(node, nodeInfo); _result.RequestNodes.Add(nodeInfo.Tag, nodeInfo); } ProcessRelationships( ); ProcessFields( ); }
/// <summary> /// Execute the query held in the BulkSqlQuery object for the specified entity, and capture the results in a fairly raw format. /// </summary> public static BulkRequestResult RunQuery(BulkSqlQuery query, EntityRef entity) { if (entity == null) { throw new ArgumentNullException("entity"); } return(RunQuery(query, entity.Id.ToEnumerable())); }
/// <summary> /// Generates the query. /// </summary> /// <param name="request"></param> /// <returns></returns> private BulkRequestResult CreateResult(EntityRequest request) { // Get/create a query object BulkSqlQuery query = BulkSqlQueryCache.GetBulkSqlQuery(request); // Run query on SQL server BulkRequestResult unsecuredResult = BulkRequestSqlRunner.RunQuery(query, request.EntityIDsCanonical); return(unsecuredResult); }
/// <summary> /// Build, or retrieve from cache, a prepared SQL query. /// </summary> /// <param name="request">The query to load.</param> /// <returns>An object containing the sql query and other lookup data.</returns> public static BulkSqlQuery GetBulkSqlQuery(EntityRequest request) { if (request == null) { throw new ArgumentNullException("request"); } if (request.Request == null && request.RequestString == null) { throw new ArgumentException("Either Request or RequestString must be specified", "request"); } // Check the cache string key = GetCacheKey(request); BulkSqlQuery query = _requestCache.GetOrAdd(key, k => CreateQuery(request)); return(query); }
/// <summary> /// Read field values from the database result /// </summary> private static void ReadFieldValues(IDataReader reader, BulkSqlQuery query, BulkRequestResult result) { // select d.EntityId, d.FieldId, d.Data, [d.Namespace] while (reader.Read()) { // Caution: We're in a database-read context, so don't touch the entity model or things will crash. // Load row long entityId = reader.GetInt64(0); long fieldId = reader.GetInt64(1); object data = reader.GetValue(2); // Get alias namespace if (data != null && reader.FieldCount == 4) { string aliasNamespace = reader.GetString(3); data = aliasNamespace + ":" + data; } // Get/convert the type info for the field FieldInfo fieldInfo; if (!query.FieldTypes.TryGetValue(fieldId, out fieldInfo)) { throw new InvalidOperationException("Assert false: encountered a field type in the result that was not part of the request."); } if (fieldInfo.IsWriteOnly) { data = null; } // Prepare field value var typedValue = new TypedValue(DateTimeKind.Utc); typedValue.Type = fieldInfo.DatabaseType; typedValue.Value = data; var fieldValue = new FieldValue(data, typedValue); // Add to dictionary var key = new FieldKey(entityId, fieldId); result.FieldValues [key] = fieldValue; } }
/// <summary> /// Execute the query held in the BulkSqlQuery object for the specified entities, and capture the results in a fairly raw format. /// </summary> /// <param name="query">The query object.</param> /// <param name="entities">IDs of root-level entities to load. Must not contain duplicates.</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException"> /// query /// or /// entities /// </exception> /// <exception cref="System.ArgumentException">No entities were loaded;entities</exception> public static BulkRequestResult RunQuery(BulkSqlQuery query, IEnumerable <long> entities) { if (query == null) { throw new ArgumentNullException("query"); } if (entities == null) { throw new ArgumentNullException("entities"); } var entitiesList = entities.ToList( ); if (entitiesList.Count <= 0) { throw new ArgumentException("No entities were loaded", "entities"); } var result = new BulkRequestResult(); result.BulkSqlQuery = query; ///// // HACK:TODO: 'LastLogin' is handled differently to all other fields on an entity. See UserAccountValidator ///// long lastLogonId = WellKnownAliases.CurrentTenant.LastLogon; if (query.FieldTypes.ContainsKey(lastLogonId)) { using (CacheContext cacheContext = new CacheContext( )) { cacheContext.Entities.Add(lastLogonId); } } using (DatabaseContext ctx = DatabaseContext.GetContext()) using (IDbCommand command = ctx.CreateCommand()) { // If single entity, then pass via parameter (to allow SQL to cache execution plan) if (entitiesList.Count == 1) { ctx.AddParameter(command, "@entityId", DbType.Int64, entitiesList[0]); } else { command.AddIdListParameter("@entityIds", entitiesList); } ctx.AddParameter(command, "@tenantId", DbType.Int64, RequestContext.TenantId); foreach (KeyValuePair <string, DataTable> tvp in query.TableValuedParameters) { command.AddTableValuedParameter(tvp.Key, tvp.Value); } command.CommandText = "dbo.spExecBulkRequest"; command.CommandType = CommandType.StoredProcedure; using (IDataReader reader = command.ExecuteReader()) { if (reader != null) { ReadRelationships(reader, query, result); while (reader.NextResult()) { ReadFieldValues(reader, query, result); } } } } return(result); }
/// <summary> /// Read relationships, and top level entities, from the database result. /// </summary> private static void ReadRelationships(IDataReader reader, BulkSqlQuery query, BulkRequestResult result) { // select distinct EntityId, RelSrcId, RelTypeId from #process while (reader.Read()) { // Caution: We're in a database-read context, so don't touch the entity model or things will crash. long toId = reader.GetInt64(0); int nodeTag = reader.GetInt32(1); // tag of the request node that returned this entity long fromId = reader.GetInt64(2); // zero for root-level entities long typeIdWithNeg = reader.GetInt64(3); // relationship type-id, with reverse being indicated with negative values // Root result entity if (fromId == 0) { result.RootEntities.Add(toId); } else { // Add to dictionary var key = new RelationshipKey(fromId, typeIdWithNeg); var value = toId; List <long> list; if (!result.Relationships.TryGetValue(key, out list)) { list = new List <long>(); result.Relationships[key] = list; } list.Add(value); } // Implicit relationship security bool implicitlySecured = false; if (fromId != 0) { var relInfo = query.Relationships[typeIdWithNeg]; implicitlySecured = relInfo.ImpliesSecurity; } // Store entity EntityValue ev; if (!result.AllEntities.TryGetValue(toId, out ev)) { ev = new EntityValue { ImplicitlySecured = implicitlySecured }; result.AllEntities[toId] = ev; } else { ev.ImplicitlySecured = ev.ImplicitlySecured && implicitlySecured; } // Store the request node that specified members to load for this entity RequestNodeInfo requestNode = query.RequestNodes [nodeTag]; ev.Nodes.Add(requestNode); } #if DEBUG if (result.RootEntitiesList.Count != 0) { throw new InvalidOperationException("Assert false .. expected RootEntityList to be empty."); } #endif result.RootEntitiesList.AddRange(result.RootEntities); RemoveDuplicateRelationshipEntries(result); }