/// <summary> /// Perform the query /// </summary> protected virtual IEnumerable <Object> DoQueryInternal(DataContext context, Expression <Func <TModel, bool> > primaryQuery, Guid queryId, int offset, int?count, out int totalResults, ModelSort <TModel>[] orderBy, bool includeCount = true, bool overrideFuzzyTotalSetting = false) { #if DEBUG Stopwatch sw = new Stopwatch(); sw.Start(); #endif OrmResultSet <TQueryReturn> retVal = null; Expression <Func <TModel, bool> >[] queries = new Expression <Func <TModel, bool> >[] { primaryQuery }; // Are we intersecting? if (context.Data.TryGetValue("UNION", out object others) && others is Expression <Func <TModel, bool> >[]) { context.Data.Remove("UNION"); queries = queries.Concat((Expression <Func <TModel, bool> >[])others).ToArray(); } try { // Fetch queries foreach (var q in queries) { var query = q; SqlStatement domainQuery = null; // Query has been registered? if (queryId != Guid.Empty && this.m_queryPersistence?.IsRegistered(queryId) == true) { return(this.GetStoredQueryResults(queryId, offset, count, out totalResults)); } // Is obsoletion time already specified? If so and the entity is an iversioned entity we don't want obsolete data coming back var queryString = query.ToString(); if (!queryString.Contains("ObsoletionTime") && typeof(IVersionedEntity).IsAssignableFrom(typeof(TModel)) && !queryString.Contains("VersionKey")) { var obsoletionReference = Expression.MakeBinary(ExpressionType.Equal, Expression.MakeMemberAccess(query.Parameters[0], typeof(TModel).GetProperty(nameof(BaseEntityData.ObsoletionTime))), Expression.Constant(null)); //var obsoletionReference = Expression.MakeUnary(ExpressionType.Not, Expression.MakeMemberAccess(Expression.MakeMemberAccess(query.Parameters[0], typeof(TModel).GetProperty(nameof(BaseEntityData.ObsoletionTime))), typeof(Nullable<DateTimeOffset>).GetProperty("HasValue")), typeof(bool)); query = Expression.Lambda <Func <TModel, bool> >(Expression.MakeBinary(ExpressionType.AndAlso, obsoletionReference, query.Body), query.Parameters); } else if (!queryString.Contains("ObsoleteVersionSequenceId") && typeof(IVersionedAssociation).IsAssignableFrom(typeof(TModel))) { var obsoletionReference = Expression.MakeBinary(ExpressionType.Equal, Expression.MakeMemberAccess(query.Parameters[0], typeof(TModel).GetProperty(nameof(IVersionedAssociation.ObsoleteVersionSequenceId))), Expression.Constant(null)); //var obsoletionReference = Expression.MakeUnary(ExpressionType.Not, Expression.MakeMemberAccess(Expression.MakeMemberAccess(query.Parameters[0], typeof(TModel).GetProperty(nameof(IVersionedAssociation.ObsoleteVersionSequenceId))), typeof(Nullable<decimal>).GetProperty("HasValue")), typeof(bool)); query = Expression.Lambda <Func <TModel, bool> >(Expression.MakeBinary(ExpressionType.AndAlso, obsoletionReference, query.Body), query.Parameters); } // Domain query Type[] selectTypes = { typeof(TQueryReturn) }; if (selectTypes[0].IsConstructedGenericType) { selectTypes = selectTypes[0].GenericTypeArguments; } domainQuery = context.CreateSqlStatement <TDomain>().SelectFrom(selectTypes); var expression = m_mapper.MapModelExpression <TModel, TDomain, bool>(query, false); if (expression != null) { Type lastJoined = typeof(TDomain); if (typeof(CompositeResult).IsAssignableFrom(typeof(TQueryReturn))) { foreach (var p in typeof(TQueryReturn).GenericTypeArguments.Select(o => this.m_persistenceService.GetMapper().MapModelType(o))) { if (p != typeof(TDomain)) { // Find the FK to join domainQuery.InnerJoin(lastJoined, p); lastJoined = p; } } } domainQuery.Where <TDomain>(expression); } else { m_tracer.TraceEvent(EventLevel.Verbose, "Will use slow query construction due to complex mapped fields"); domainQuery = this.m_persistenceService.GetQueryBuilder().CreateQuery(query, orderBy); } if (retVal == null) { retVal = this.DomainQueryInternal <TQueryReturn>(context, domainQuery); } else { retVal = retVal.Union(this.DomainQueryInternal <TQueryReturn>(context, domainQuery)); } } this.AppendOrderBy(retVal.Statement, orderBy); // Count = 0 means we're not actually fetching anything so just hit the db if (count != 0) { // Stateful query identifier = We need to add query results if (queryId != Guid.Empty && ApplicationContext.Current.GetService <IQueryPersistenceService>() != null) { // Create on a separate thread the query results var keys = retVal.Keys <Guid>().ToArray(); totalResults = keys.Length; this.m_queryPersistence?.RegisterQuerySet(queryId, keys, queries, totalResults); } else if (count.HasValue && includeCount && !m_configuration.UseFuzzyTotals) // Get an exact total { totalResults = retVal.Count(); } else { totalResults = 0; } // Fuzzy totals - This will only fetch COUNT + 1 as the total results if (count.HasValue) { if ((overrideFuzzyTotalSetting || m_configuration.UseFuzzyTotals) && totalResults == 0) { var fuzzResults = retVal.Skip(offset).Take(count.Value + 1).OfType <Object>().ToList(); totalResults = fuzzResults.Count(); return(fuzzResults.Take(count.Value)); } else { return(retVal.Skip(offset).Take(count.Value).OfType <Object>()); } } else { return(retVal.Skip(offset).OfType <Object>()); } } else { totalResults = retVal.Count(); return(new List <Object>()); } } catch (Exception ex) { this.m_tracer.TraceError("Error performing underlying query: {0}", ex); if (retVal != null) { this.m_tracer.TraceEvent(EventLevel.Error, context.GetQueryLiteral(retVal.ToSqlStatement())); } context.Dispose(); // No longer important throw; } #if DEBUG finally { sw.Stop(); } #endif }
/// <summary> /// Query internal for versioned data elements /// </summary> protected override IEnumerable <Object> DoQueryInternal(DataContext context, Expression <Func <TModel, bool> > primaryQuery, Guid queryId, int offset, int?count, out int totalResults, ModelSort <TModel>[] orderBy, bool countResults = true, bool overrideFuzzyTotalSetting = false) { #if DEBUG Stopwatch sw = new Stopwatch(); sw.Start(); #endif // Queries to be performed OrmResultSet <CompositeResult <TDomain, TDomainKey> > retVal = null; Expression <Func <TModel, bool> >[] queries = new Expression <Func <TModel, bool> >[] { primaryQuery }; // Are we intersecting? if (context.Data.TryGetValue("UNION", out object others) && others is Expression <Func <TModel, bool> >[]) { context.Data.Remove("UNION"); queries = queries.Concat((Expression <Func <TModel, bool> >[])others).ToArray(); } try { // Execute queries foreach (var q in queries) { var query = q; // Is obsoletion time already specified? (this is important for versioned objects if we want to get the most current version of the object) if (!query.ToString().Contains("ObsoletionTime") && !query.ToString().Contains("VersionKey")) { var obsoletionReference = Expression.MakeBinary(ExpressionType.Equal, Expression.MakeMemberAccess(query.Parameters[0], typeof(TModel).GetProperty(nameof(BaseEntityData.ObsoletionTime))), Expression.Constant(null)); query = Expression.Lambda <Func <TModel, bool> >(Expression.MakeBinary(ExpressionType.AndAlso, obsoletionReference, query.Body), query.Parameters); } // Query has been registered? if (queryId != Guid.Empty && this.m_queryPersistence?.IsRegistered(queryId) == true) { return(this.GetStoredQueryResults(queryId, offset, count, out totalResults)); } SqlStatement domainQuery = null; var expr = m_mapper.MapModelExpression <TModel, TDomain, bool>(query, false); if (expr != null) { domainQuery = context.CreateSqlStatement <TDomain>().SelectFrom(typeof(TDomain), typeof(TDomainKey)) .InnerJoin <TDomain, TDomainKey>(o => o.Key, o => o.Key) .Where <TDomain>(expr).Build(); } else { domainQuery = this.m_persistenceService.GetQueryBuilder().CreateQuery(query).Build(); } // Create or extend queries if (retVal == null) { retVal = this.DomainQueryInternal <CompositeResult <TDomain, TDomainKey> >(context, domainQuery); } else { retVal = retVal.Union(this.DomainQueryInternal <CompositeResult <TDomain, TDomainKey> >(context, domainQuery)); } } // HACK: More than one query which indicates union was used, we need to wrap in a select statement to be adherent to SQL standard on Firebird and PSQL if (queries.Count() > 1) { var query = this.AppendOrderBy(context.CreateSqlStatement("SELECT * FROM (").Append(retVal.ToSqlStatement()).Append(") AS domain_query "), orderBy); retVal = this.DomainQueryInternal <CompositeResult <TDomain, TDomainKey> >(context, query); } else { this.AppendOrderBy(retVal.Statement, orderBy); } // Only perform count if (count == 0) { totalResults = retVal.Count(); return(new List <CompositeResult <TDomain, TDomainKey> >()); } else { if (queryId != Guid.Empty && ApplicationContext.Current.GetService <IQueryPersistenceService>() != null) { var keys = retVal.Keys <Guid>(false).ToArray(); totalResults = keys.Count(); this.m_queryPersistence?.RegisterQuerySet(queryId, keys, queries, totalResults); } else if (count.HasValue && countResults && !m_configuration.UseFuzzyTotals) { totalResults = retVal.Count(); } else { totalResults = 0; } // Fuzzy totals - This will only fetch COUNT + 1 as the total results if (count.HasValue) { if ((overrideFuzzyTotalSetting || m_configuration.UseFuzzyTotals) && totalResults == 0) { var fuzzResults = retVal.Skip(offset).Take(count.Value + 1).OfType <Object>().ToList(); totalResults = fuzzResults.Count(); return(fuzzResults.Take(count.Value)); } else // We already counted as part of the queryId so no need to take + 1 { return(retVal.Skip(offset).Take(count.Value).OfType <Object>()); } } else { return(retVal.Skip(offset).OfType <Object>()); } } } catch (Exception ex) { if (retVal != null) { this.m_tracer.TraceEvent(EventLevel.Error, context.GetQueryLiteral(retVal.ToSqlStatement())); } context.Dispose(); // No longer important throw new DataPersistenceException("Error executing query", ex); } #if DEBUG finally { sw.Stop(); } #endif }