When I create a query on a DbSet with too many Concat or Where clauses, I get a stack overflow error.
Essentially I have the problem where I have a list of thousands of AND clauses all connected with OR clauses. It would look a little something like:
(A AND B) OR (C AND D) OR ...
The clauses are created from a list so the number of AND clauses that are concatenated by the OR clauses is dynamic and could be from 0 to thousands.
I tried creating selects for each AND clause and using Concat to combine multiple selects together using Entity Framework, but I get a stack overflow exception.
I feel like there should be a better way to write the code, but I'm not sure so I've included the error and some example code in a hope someone knows how this should be done without reverting back to writing inline SQL (Goes against the entity framework paradigm)
The exact error is as follows:
Stack overflow.
Repeat 798 times:
--------------------------------
at Microsoft.EntityFrameworkCore.Query.Internal.ExpressionTreeFuncletizer.Visit[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.ObjectModel.ReadOnlyCollection`1<System.__Canon>, System.Func`2<System.__Canon,System.__Canon>, StateType ByRef, State[] ByRef, Boolean)
at Microsoft.EntityFrameworkCore.Query.Internal.ExpressionTreeFuncletizer.Visit(System.Collections.ObjectModel.ReadOnlyCollection`1<System.Linq.Expressions.Expression>, StateType ByRef, State[] ByRef, Boolean)
at Microsoft.EntityFrameworkCore.Query.Internal.ExpressionTreeFuncletizer.VisitMethodCall(System.Linq.Expressions.MethodCallExpression)
at Microsoft.EntityFrameworkCore.Query.Internal.ExpressionTreeFuncletizer.Visit(System.Linq.Expressions.Expression)
--------------------------------
at Microsoft.EntityFrameworkCore.Query.Internal.ExpressionTreeFuncletizer.Visit[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.ObjectModel.ReadOnlyCollection`1<System.__Canon>, System.Func`2<System.__Canon,System.__Canon>, StateType ByRef, State[] ByRef, Boolean)
at Microsoft.EntityFrameworkCore.Query.Internal.ExpressionTreeFuncletizer.Visit(System.Collections.ObjectModel.ReadOnlyCollection`1<System.Linq.Expressions.Expression>, StateType ByRef, State[] ByRef, Boolean)
Libraries:
EFCore.BulkExtensions.PostgreSqlVersion="8.1.2"Microsoft.EntityFrameworkCore.DesignVersion="9.0.0"Npgsql.EntityFrameworkCore.PostgreSQLVersion="9.0.2"Npgsql.EntityFrameworkCore.PostgreSQL.DesignVersion="1.1.0"
Here's an example how to reproduce the issue:
private class SimpleDbContext : DbContext
{
// Stores the values 0, 1, 2, ..., 100000
public virtual DbSet<SequencePoint> SequencePoints { get; set; }
}
private class SequencePoint
{
public int SequenceNumber { get; set; }
}
private void ConcatErrorTest()
{
SimpleDbContext simpleDbContext = new();
List<Tuple<int, int>> selectRanges = new(); // 0, 10, 11, 20, 21, 21, etc...
for (int i = 0; i < 7500; i++)
{
int startRange = i * 10;
int endRange = startRange + (i % 5);
selectRanges.Add(new Tuple<int, int>(startRange, endRange));
}
IQueryable<SequencePoint> queryable = null;
foreach (Tuple<int,int> selectRange in selectRanges)
{
IQueryable<SequencePoint> whereQueryable = simpleDbContext.SequencePoints.AsQueryable().Where(point =>
(point.SequenceNumber >= selectRange.Item1) &&
(point.SequenceNumber <= selectRange.Item2)
);
queryable = queryable == null ? whereQueryable : queryable.Concat(whereQueryable);
}
// Throws Stack overflow.
List<int> result = queryable.Select(sequenceNumber => sequenceNumber.SequenceNumber).ToList();
_logger.LogInformation("result = {result}", result);
}
selectRanges? The obvious solution is to find the threshold where stackoverflow occurs, and then rather than having a singlequeryableyou run them in batches and append toresulte.g. batchselectRangesinto batches of 100, and then run multiple queries, each with 200 parameters in them and then run them one at a time,AddRangeing intoresult. This will likely also allow you to avoid stackoverflow.com/questions/1009706/… .simpleDbContext.SequencePoints.AsQueryable().Where(point => selectRanges.Any( sr => point.SequenceNumber >= sr.Item1 && point.SequenceNumber <= sr.Item2))or something along these line should remove the need for the for loop and the Concat. That still leaves open the option that the dbprovider will choke whenselectRangesis big