Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Union fails if one query has returned no results #19705

Closed
FormerMarine opened this issue Jan 25, 2020 · 30 comments · Fixed by #19835
Closed

Union fails if one query has returned no results #19705

FormerMarine opened this issue Jan 25, 2020 · 30 comments · Fixed by #19835
Assignees
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Milestone

Comments

@FormerMarine
Copy link

FormerMarine commented Jan 25, 2020

Union fails if one query in the union returns no result. An InvalidOperationException is thrown with the message "When performing a set operation, both operands must have the same include operations. Investigation revealed that if one of the queries in the union is empty (returned no results) the union operation fails. In code example below any or the queries (1, 2, or 3) may return no results.

        public async Task<DelimitedList> AvailableClubsAsync(string clubMnemonic = null)
        {
            using var context = new PGSSqlServerContext();

            var query1 = context.Clubs.ForGolferAsync().Where(cl => cl.ClubMnemonic == clubMnemonic)
                                                                                   .Select(cl => new { cl.ClubMnemonic, Sequence = 0 });

            var query2 = context.Clubs.ForGolferAsync().Where(cl => !cl.Courses.Any())
                                                                                   .Select(cl => new { cl.ClubMnemonic, Sequence = 1 });

            var numberOfTees = await context.Tees.ForGolferAsync().CountAsync().ConfigureAwait(false);

            var query3 = context.Courses.ForGolferAsync().Where(co => co.ClubMnemonic != clubMnemonic)
                                                                                       .GroupBy(co => new { co.ClubMnemonic, co.CourseName })
                                                                                       .Where(g => (g.Count() < numberOfTees && g.Key.CourseName == null) || (g.Key.CourseName != null))
                                                                                       .Select(g => new { g.Key.ClubMnemonic, Sequence = 1 });

            var query = query1.Union(query2).Union(query3)
                                          .OrderBy(q => q.Sequence).ThenBy(q => q.ClubMnemonic)
                                          .Select(q => q.ClubMnemonic);

            return new DelimitedList(list: await query.ToListAsync());
        }

Further technical details

EF Core version: 3.1.1
Database provider: (e.g. Microsoft.EntityFrameworkCore.SqlServer)
Target framework: (e.g. .NET Core 3.1)
Operating system: Windows 10
IDE: (e.g. Visual Studio 2019 164..3)

@ajcvickers
Copy link
Member

/cc @roji

@roji
Copy link
Member

roji commented Jan 28, 2020

Reproduced this, it doesn't seem related to whether any query returns empty results or not, and is somehow related to the second GroupBy/Select query. Simpler queries work with empty results.

class Program
{
    static void Main(string[] args)
    {
        using (var ctx = new BlogContext())
        {
            ctx.Database.EnsureDeleted();
            ctx.Database.EnsureCreated();

            ctx.Blogs.Add(new Blog { Name = "foo" });
            ctx.Blogs.Add(new Blog { Name = "bar" });
            ctx.SaveChanges();
        }

        using (var ctx = new BlogContext())
        {
            var results = ctx.Blogs
                .Where(b => b.Name == "foo").Select(b => new { b.Name, Sequence = 0 })
                .Union(ctx.Blogs
                    .GroupBy(b => new { b.Name, b.SomeInt })
                    .Select(g => new { g.Key.Name, Sequence = 1}))
                .ToList();
        }
    }
}

public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlServer(@"...");
}

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int SomeInt { get; set; }
}

@roji roji self-assigned this Jan 28, 2020
@FormerMarine
Copy link
Author

I tested it with query1 and query3 returning results and it worked fine. I then forced query1 to return no results and the union failed.

@smitpatel smitpatel assigned smitpatel and unassigned roji Jan 28, 2020
@smitpatel
Copy link
Member

The select is not applied to GroupBy before comparing shapes for set operations causing the shape to mismatch and throw the exception.

@ajcvickers ajcvickers added this to the 5.0.0 milestone Jan 28, 2020
smitpatel added a commit that referenced this issue Feb 7, 2020
If all components are NewExpression are default then just make it wholy default so that it can match to GroupBy-Aggregate shape

Resolves #19705
@smitpatel smitpatel added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Feb 7, 2020
smitpatel added a commit that referenced this issue Feb 7, 2020
If all components are NewExpression are default then just make it wholy default so that it can match to GroupBy-Aggregate shape

Resolves #19705
@ajcvickers ajcvickers modified the milestones: 5.0.0, 5.0.0-preview1 Mar 13, 2020
@jacqueskang
Copy link

Hi,

We encountered this issue when migrating to .NET Core 3.1.
Since we cannot yet migrate to 5.0. Is there any walkaround for .NET Core 3 please?

Thanks in advance.

@FormerMarine
Copy link
Author

FormerMarine commented Apr 7, 2020 via email

@Fosol
Copy link

Fosol commented Apr 22, 2020

Just ran into this issue with 3.1.
Is there a fix or a workaround?

It occurs if the union attempts to include another entity in the results that is not available in one of the selects.

Sudo-code below

select buildings
{
 b.Id,
 b.ConstructionType
}
union
select parcels
{
 p.Id,
 ConstructionType = (ConstructionType)null
}

@FormerMarine
Copy link
Author

FormerMarine commented Apr 22, 2020 via email

@jacqueskang
Copy link

The only 'fix' I found was to use EF6.4. EFCore is definitely not industrial strength - it needs a lot of work

On Wed, Apr 22, 2020 at 2:49 PM Jeremy Foster @.***> wrote: Just ran into this issue with 3.1. Is there a fix or a workaround? — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub <#19705 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALZAM75WU6V6CUDKVLIEFVDRN5J7DANCNFSM4KLQPOLA .

Well we ended up by refactoring our codes and union with in-memory lists.

@roji
Copy link
Member

roji commented Apr 23, 2020

Note that this has already been fixed in 5.0.0-preview1 - you can give that a try as well. It would be great to get confirmation that the bug is gone.

@FormerMarine
Copy link
Author

FormerMarine commented Apr 23, 2020 via email

@roji
Copy link
Member

roji commented Apr 24, 2020

Sorry to hear that @FormerMarine. It would be good to know exactly which problems you ran into, and hopefully you'll try again at some point.

@FormerMarine
Copy link
Author

FormerMarine commented Apr 24, 2020 via email

@davidyee
Copy link

Testing with the latest EF Core 5.0 Preview 4 we appear to have the same error on union where one of the queries returns no data.

Our query looks something like the below code. Specifics aside, you can see that we have the same Includes running in both y and z queries. The below code fails with the same error mentioned by the original poster when z query is returning 0 count.

var q = DbContext.TakeOffLineItemGroup
    .Include(e => e.TakeOffLineItems.Where(e => e.DeletionDate == null))
        .ThenInclude(e => e.CalculationLineItem)
            .ThenInclude(e => e.CalculationType)
    .Where(e => e.DeletionDate == null)
    .AsQueryable();

var y = q
    .Where(x => x.IsAllowLineItems && x.DeletionDate == null)
    .SelectMany(h => h.TakeOffLineItems.Where(x => x.DeletionDate == null)
        .Select(i => i.CalculationLineItem)
    )
    .AsQueryable();

var z = q
    .Where(x => x.IsAllowLineItems == false && x.DeletionDate == null)
    .Select(i => i.CalculationLineItem)
    .AsQueryable();
    
// y.ToList() returns > 1 count
// z.ToList() returns 0 count
    
query = y.Union(z);

var calculations = await query.ToListAsync(); // InvalidOperationException error

Performing the operation in-memory as suggested in this issue works:

/////////////////////////////////////////////////////////////////////////////
// Workaround
var yList = await y.ToListAsync();
var zList = await z.ToListAsync();

calculations = yList.Union(zList).ToList();
/////////////////////////////////////////////////////////////////////////////

@roji
Copy link
Member

roji commented May 21, 2020

@davidyee I can confirm that the code sample above, which was failing in 3.1, now works with 5.0.0-preview4. Can you please open a new issue with your problem, and include a fully runnable code sample? We need your model - not just the query - as well as a bit of data in the database which is needed to trigger the exception.

@FormerMarine
Copy link
Author

Same query fails with same error - see my original input

@ajcvickers
Copy link
Member

@FormerMarine If this is still failing in 5.0 RC1, then please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

@FormerMarine
Copy link
Author

My bad - using wrong version of EF Core - aplogies galore

@FormerMarine
Copy link
Author

Everything worked fine until I tried to compare the strings in the query that started all this using the 'ToLower()' function. eg cl.ClubMnemonic.ToLower() == clubMnemonic.ToLower(). The result was:

System.InvalidOperationException
HResult=0x80131509
Message=An exception was thrown while attempting to evaluate a LINQ query parameter expression. To show additional information call EnableSensitiveDataLogging() when overriding DbContext.OnConfiguring.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.GetValue(Expression expression, String& parameterName)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Evaluate(Expression expression, Boolean generateParameter)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression1 node) at System.Linq.Expressions.Expression1.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
at System.Linq.Expressions.UnaryExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.ExtractParameters(Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExtractParameters(Expression query, IParameterValues parameterValues, IDiagnosticsLogger1 logger, Boolean parameterize, Boolean generateContextAccessors) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetEnumerator()
at System.Collections.Generic.List1..ctor(IEnumerable1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at PGSDataAccessLayer.EntityFramework.Course.AvailableClubs(String clubMnemonic) in D:\Development 2020.4\Shared\Applications\EntityFrameworkCore\PGSDataAccessLayer.EntityFrameworkCore.SyncCodeBase\PGSDataAccessLayer.EntityFrameworkCore.SyncCodeBase\Sync\CourseSync.cs:line 69
at PGSDataAccessLayer.Test.Program.Main(String[] args) in D:\Development 2020.4\Net 5.0\Applications\Common\PGS Data Access Layer\PGSDataAccessLayer.Test\Program.cs:line 18

Inner Exception 1:
NullReferenceException: Object reference not set to an instance of an object.

@roji
Copy link
Member

roji commented Oct 9, 2020

@FormerMarine can you please post a full, runnable code sample which shows the failure on 5.0.0-rc1?

@FormerMarine
Copy link
Author

This code is a portion of a very large data access layer so producing a full runnable code sample is not possible. If look at the very first code sample above, this now works. However if any of the comparisons include a function (ToUpper(), ToLower(), ToString() ) the new error occurs. cl.ClubMnemonic.ToLower() == clubMnemonic.ToLower(). I would have thought it would have been relatively easy for you to use the code used to correct the initial problem to investigate this one.

@FormerMarine
Copy link
Author

I have done a bit of further investigation. Just as in the original submission the exception is only thrown if one of the queries in the union returns no result.

@roji
Copy link
Member

roji commented Oct 10, 2020

@FormerMarine we really need you to narrow down the issue into a minimal repro - there's unfortunately very little we can do with textual descriptions and fragments... It's hard to understand from the information given above how exactly to reproduce the problem.

@roji
Copy link
Member

roji commented Oct 10, 2020

To be clear, I'm not trying to say there isn't a bug here, or to avoid work by placing the burden on you. It's just that we get a very large amount of bugs reports, and in a very significant number of cases, when users work on producing an actual code sample it turns out there was some sort of unrelated issue in their code which is the culprit, and which they did not communicate to us (because it simply wasn't obvious).

@FormerMarine
Copy link
Author

Shay, I have over 50 years of software development experience and only submit bug reports when I have tested my software exhaustively. This problem is the same as the original except that now it only occurs if a function (in my case ToLower() is appended in the comparisons. I turned on SensitiveDataLogging and got slightly more information:

System.InvalidOperationException: 'An exception was thrown while attempting to evaluate the LINQ query parameter expression 'value(PGSDataAccessLayer.EntityFramework.Course+<>c__DisplayClass150_0).clubMnemonic.ToLower()'.'

Having worked in the industry for over 50 years, when bugs appeared in software I was responsible for I expected to customer to provide sufficient information to enable me to replicate the bug. I belive I have done that in this case.

@FormerMarine
Copy link
Author

FormerMarine commented Oct 12, 2020

Given your reluctance to investigate the problem I decided to do further invesigation. Here are the results.
The Where function Where(cl => cl.ClubMnemonic == clubMnemonic) works in both EF6 and EFCore if clubMnemonic is null.
The Where function Where(cl => cl.ClubMnemonic.ToLower() == clubMnemonic.ToLower()) works in EF6 when clubMnemonic is null but not in EF Core when clubMnemonic is null giving the output above. Is this a bug or a case of fails as designed?

@roji
Copy link
Member

roji commented Oct 12, 2020

@FormerMarine just to make it clear what we're missing, your original code sample above is missing the actual model (the classes for Club, Course, Tee), there's an unknown function (ForGolferAsync), and we don't know anything about how you're configuring your model with the Fluent API, because the context definition is missing. That means we have to guess at your code and hopefully get it right, rather than you simply providing a simple code sample.

In any case, I've taken a look, and from what I understand you're calling ToLower() on a parameter (clubMnemonic), which has a null value. If so, then the exception you're seeing is expected and can be reproduced easily as follows:

string clubMnemonic = null;
var results = context.Clubs
    .Where(cl => cl.ClubMnemonic.ToLower() == clubMnemonic.ToLower())
    .ToList();

Since clubMnemonic is a parameter, EF Core evaluates the ToLower function call on the client inside of sending it to the database; but since the variable contains null, you're seeing the exception; you would get exactly the same exception in a regular LINQ to Objects query (without EF).

To do this correctly, you need to check your variable for null:

string clubMnemonic = null;
var results = context.Clubs
    .Where(cl => cl.ClubMnemonic.ToLower() == (clubMnemonic == null ? null : clubMnemonic.ToLower()))
    .ToList();

Hope the above is clear, please post back if you need any further guidance.

@FormerMarine
Copy link
Author

Your first example above would work fine in EF6 if clubMnemonic were null. A lot of additional coding is required to achieve the same result if EF Core. This is the type of software engineering I would expect from Oracle not Microsoft.

The following is my implementation.

clubMnemonic = clubMnemonic?.ToLower(); //Note - clubMnemonic is a parameter
var results = context.Clubs
.Where(cl => cl.ClubMnemonic.ToLower() == clubMnemonic.)
.ToList();

@roji
Copy link
Member

roji commented Oct 13, 2020

There are many programming patterns which are supported in EF6 and unsupported in EF Core - we generally don't aim to provide full compatibility with EF6. AFAIK this has been the EF Core behavior since the beginning.

Your implementation definitely looks fine (and terser than what I proposed above).

@FormerMarine
Copy link
Author

Just a suggestion. Given that the ToLower() function is required if you plan to use Oracle (I know it's not suppored fully yet) it might be worth thinking about implementing this programming pattern.

@ajcvickers ajcvickers modified the milestones: 5.0.0-preview1, 5.0.0 Nov 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants