/// <summary>
        /// Check whether the specified <paramref name="permission"/> exists between the <paramref name="subjectId"/>
        /// and the <paramref name="entities"/> using just a relationship. Used for create permission.
        /// </summary>
        /// <param name="subjectId">
        /// The ID of the subject (user or role).
        /// </param>
        /// <param name="permission">
        /// The permission (operation). This cannot be null.
        /// </param>
        /// <param name="entityType">
        /// The type of the checked entities.
        /// </param>
        /// <param name="entities">
        /// The checked entities. This cannot be null or contain null.
        /// </param>
        /// <param name="result">
        /// The map of entity IDs to whether the relationship exists.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// No argument can be null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="entities"/> cannot contain null.
        /// </exception>
        internal void CheckAccessControlByRelationship(long subjectId, EntityRef permission,
                                                       long entityType, IList <EntityRef> entities, IDictionary <long, bool> result)
        {
            if (permission == null)
            {
                throw new ArgumentNullException("permission");
            }
            if (entities == null)
            {
                throw new ArgumentNullException("entities");
            }
            if (entities.Contains(null))
            {
                throw new ArgumentException(@"Entities cannot contain null", "entities");
            }
            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

            Subject            subject;
            IEnumerable <long> allowedEntityTypes;

            bool containType;

            using (new SecurityBypassContext())
            {
                subject            = Entity.Get <Subject>(new EntityRef(subjectId));
                allowedEntityTypes = AllowedEntityTypes(permission, subject);

                containType = allowedEntityTypes.Contains(entityType);
            }

            if (containType)
            {
                foreach (EntityRef entity in entities)
                {
                    result[entity.Id] = true;
                }

                using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName))
                {
                    messageContext.Append(() => string.Format(
                                              "'{0}' access to entities '{1}' allowed",
                                              Permissions.GetPermissionByAlias(permission),
                                              string.Join(", ", entities.Select(x => x.ToString()))));
                }
            }
        }
        /// <summary>
        /// Check whether the queries for the specified <paramref name="permission"/> allow the <paramref name="subjectId"/>
        /// access to <paramref name="entities"/> using the related security queries. Used for read, modify and delete
        /// permissions.
        /// </summary>
        /// <param name="subjectId">
        /// The ID of the subject (user or role).
        /// </param>
        /// <param name="permission">
        /// The permission (operation). This cannot be null.
        /// </param>
        /// <param name="entityType">
        /// The type ID of the entities to check. This cannot be null.
        /// </param>
        /// <param name="entities">
        /// The entities to check. This cannot be null or contain null.
        /// </param>
        /// <param name="allEntities">
        /// All entities.
        /// </param>
        /// <param name="queryResultsCache">
        /// The query results cache.
        /// </param>
        /// <param name="result">
        /// The map of entity IDs to whether the relationship exists.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// No argument can be null.
        /// </exception>
        internal void CheckAccessControlByQuery(long subjectId, EntityRef permission,
                                                long entityType, IList <EntityRef> entities, ISet <long> allEntities,
                                                IDictionary <long, ISet <long> > queryResultsCache, IDictionary <long, bool> result)
        {
            using (Profiler.MeasureAndSuppress("CheckAccessControlByQuery"))
            {
                if (permission == null)
                {
                    throw new ArgumentNullException("permission");
                }
                if (result == null)
                {
                    throw new ArgumentNullException("result");
                }
                if (entities == null)
                {
                    throw new ArgumentNullException("entities");
                }
                if (allEntities == null)
                {
                    throw new ArgumentNullException("allEntities");
                }
                if (queryResultsCache == null)
                {
                    throw new ArgumentNullException("queryResultsCache");
                }
                if (entities.Contains(null))
                {
                    throw new ArgumentException("Cannot check access for null entities", "entities");
                }

                IEnumerable <AccessRuleQuery> queries;

                // Allow access to temporary IDs
                if (AllowAccessToTemporaryIds(result))
                {
                    return;
                }

                using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName))
                {
                    queries = QueryRepository.GetQueries(subjectId, permission, new[] { entityType });
                    QueryResult queryResult;

                    // Check if any queries grant access to all instances of the type
                    StructuredQuery shortCircuitQuery = CheckIfAnyQueryProvideAccessToAllInstancesOfType(entityType, queries, entities, result);
                    if (shortCircuitQuery != null)
                    {
                        messageContext.Append(
                            () => string.Format(
                                "{0} allowed '{1}' access to entities '{2}' because it allows access to all instances of the type.",
                                AccessControlDiagnosticsHelper.GetAccessRuleName(shortCircuitQuery),
                                Permissions.GetPermissionByAlias(permission),
                                string.Join(", ", entities.Select(x => x.ToString()))));
                        return;
                    }

                    long securityOwnerRelId = WellKnownAliases.CurrentTenant.SecurityOwner;

                    foreach (AccessRuleQuery accessRuleQuery in queries)
                    {
                        StructuredQuery structuredQuery = accessRuleQuery.Query;
                        var             allowedEntities = new HashSet <long>();
                        ISet <long>     queryResultSet;

                        if (!queryResultsCache.TryGetValue(accessRuleQuery.ReportId, out queryResultSet))
                        {
                            var querySettings = new QuerySettings
                            {
                                SecureQuery = false,
                                Hint        = "security - " + Name
                            };

                            bool filtered = false;

                            if (allEntities.Count <= MaximumNumberOfFilteredEntities)
                            {
                                filtered = true;
                                querySettings.SupportRootIdFilter = true;
                                querySettings.RootIdFilterList    = allEntities;
                            }

                            queryResult = null;
                            try
                            {
                                using (MessageContext msg = new MessageContext("Reports", MessageContextBehavior.New))
                                {
                                    queryResult = Factory.QueryRunner.ExecuteQuery(structuredQuery, querySettings);
                                }
                            }
                            catch (Exception ex)
                            {
                                AccessControlDiagnosticsHelper.WriteInvalidSecurityReportMessage(structuredQuery, messageContext, ex);
                            }

                            queryResultSet = new HashSet <long>();

                            if (queryResult != null && QueryInspector.IsQueryUndamaged(structuredQuery))
                            {
                                foreach (DataRow dataRow in queryResult.DataTable.Rows)
                                {
                                    var id = dataRow.Field <long>(0);
                                    if (filtered || allEntities.Contains(id))
                                    {
                                        queryResultSet.Add(id);
                                    }
                                }
                            }
                            else
                            {
                                if (queryResult != null)
                                {
                                    AccessControlDiagnosticsHelper.WriteInvalidSecurityReportMessage(structuredQuery, messageContext);
                                }
                            }

                            queryResultsCache[accessRuleQuery.ReportId] = queryResultSet;
                        }

                        foreach (EntityRef entityRef in entities)
                        {
                            if (queryResultSet.Contains(entityRef.Id) &&
                                result.ContainsKey(entityRef.Id))
                            {
                                allowedEntities.Add(entityRef.Id);
                                result[entityRef.Id] = true;
                            }
                        }

                        // ReSharper disable AccessToForEachVariableInClosure
                        // ReSharper disable SpecifyACultureInStringConversionExplicitly
                        if (allowedEntities.Count > 0)
                        {
                            messageContext.Append(
                                () => string.Format(
                                    "{0} allowed '{1}' access to entities '{2}' out of '{3}'",
                                    AccessControlDiagnosticsHelper.GetAccessRuleName(structuredQuery),
                                    Permissions.GetPermissionByAlias(permission),
                                    string.Join(", ",
                                                allowedEntities.Select(x => x.ToString())),
                                    string.Join(", ", entities.Select(x => x.ToString()))));
                        }
                        else
                        {
                            messageContext.Append(
                                () => string.Format(
                                    "{0} returned no results for '{1}' access to entities '{2}'",
                                    AccessControlDiagnosticsHelper.GetAccessRuleName(structuredQuery),
                                    Permissions.GetPermissionByAlias(permission),
                                    string.Join(", ", entities.Select(x => x.ToString()))));
                        }
                        // ReSharper restore AccessToForEachVariableInClosure
                        // ReSharper restore SpecifyACultureInStringConversionExplicitly

                        // Set the cache invalidation information
                        using (CacheContext cacheContext = CacheContext.GetContext())
                        {
                            // ******************* TEMPORARY WORKAROUND ***********************
                            // Until we properly implement filtering the invalidating relationships and fields by type
                            // we will ignore invalidating on the security owner relationship

                            cacheContext.RelationshipTypes.Add(
                                StructuredQueryHelper.GetReferencedRelationships(structuredQuery).Where(er => er.Id != securityOwnerRelId).Select(er => er.Id));
                            cacheContext.FieldTypes.Add(
                                StructuredQueryHelper.GetReferencedFields(structuredQuery, true, true).Select(er => er.Id));
                        }
                    }
                }
            }
        }