Bug description
An aggregate over a collection navigation generates SQL that SQL Server rejects when the aggregated expression combines a value from a FirstOrDefault() subquery with a column from the outer query:
Msg 8124: Multiple columns are specified in an aggregated expression containing an outer reference. If an expression being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression.
This is a regression: the query executes on 11.0.0-preview.6.26359.118 and fails on 11.0.0-rc.1.26410.101.
Each blog sums a value over its posts. The summed value multiplies a column read from a single-result subquery (m.Views) by a column on the outer entity (b.Rating):
var query = from b in ctx.Blogs
let projected = from p in b.Posts
let m = p.Metrics.Select(x => new { x.Views, x.Likes }).FirstOrDefault()
select new { Views = m.Views * b.Rating, Likes = m.Likes * b.Rating }
select new
{
b.Id,
TotalViews = projected.Sum(x => x.Views),
TotalLikes = projected.Sum(x => x.Likes)
};
On 11.0.0-rc.1.26410.101 this produces:
SELECT [b].[Id], (
SELECT ISNULL(SUM([m1].[Views] * [b].[Rating]), 0)
FROM [Posts] AS [p]
LEFT JOIN (
SELECT [m0].[Views], [m0].[PostId]
FROM (
SELECT [m].[Views], [m].[PostId], ROW_NUMBER() OVER(PARTITION BY [m].[PostId] ORDER BY [m].[Id]) AS [row]
FROM [Metrics] AS [m]
) AS [m0]
WHERE [m0].[row] <= 1
) AS [m1] ON [p].[Id] = [m1].[PostId]
WHERE [b].[Id] = [p].[BlogId]) AS [TotalViews], (
SELECT ISNULL(SUM([m4].[Likes] * [b].[Rating]), 0)
FROM [Posts] AS [p0]
LEFT JOIN (
SELECT [m3].[Likes], [m3].[PostId]
FROM (
SELECT [m2].[Likes], [m2].[PostId], ROW_NUMBER() OVER(PARTITION BY [m2].[PostId] ORDER BY [m2].[Id]) AS [row]
FROM [Metrics] AS [m2]
) AS [m3]
WHERE [m3].[row] <= 1
) AS [m4] ON [p0].[Id] = [m4].[PostId]
WHERE [b].[Id] = [p0].[BlogId]) AS [TotalLikes]
FROM [Blogs] AS [b]
SUM([m1].[Views] * [b].[Rating]) sits inside a sub-query correlated on [b].[Id] = [p].[BlogId], so [b].[Rating] is an outer reference and [m1].[Views] is a second column in the same aggregated expression. SQL Server rejects that, and the query fails at execution.
On 11.0.0-preview.6.26359.118 the same LINQ produces valid SQL. The multiplication is evaluated in an OUTER APPLY, so each SUM receives exactly one column:
SELECT [b].[Id], (
SELECT ISNULL(SUM([s].[value]), 0)
FROM [Posts] AS [p]
OUTER APPLY (
SELECT (
SELECT TOP(1) [m].[Views]
FROM [Metrics] AS [m]
WHERE [p].[Id] = [m].[PostId]) * [b].[Rating] AS [value]
) AS [s]
WHERE [b].[Id] = [p].[BlogId]) AS [TotalViews], (
SELECT ISNULL(SUM([s0].[value]), 0)
FROM [Posts] AS [p0]
OUTER APPLY (
SELECT (
SELECT TOP(1) [m0].[Likes]
FROM [Metrics] AS [m0]
WHERE [p0].[Id] = [m0].[PostId]) * [b].[Rating] AS [value]
) AS [s0]
WHERE [b].[Id] = [p0].[BlogId]) AS [TotalLikes]
FROM [Blogs] AS [b]
The number of members read from the single-result subquery decides which form is generated. Projecting one member instead of two keeps the OUTER APPLY form on 11.0.0-rc.1.26410.101 and the query executes:
var query = from b in ctx.Blogs
let projected = from p in b.Posts
let m = p.Metrics.Select(x => new { x.Views }).FirstOrDefault()
select new { Views = m.Views * b.Rating }
select new
{
b.Id,
TotalViews = projected.Sum(x => x.Views)
};
SELECT [b].[Id], (
SELECT ISNULL(SUM([s].[value]), 0)
FROM [Posts] AS [p]
OUTER APPLY (
SELECT (
SELECT TOP(1) [m].[Views]
FROM [Metrics] AS [m]
WHERE [p].[Id] = [m].[PostId]) * [b].[Rating] AS [value]
) AS [s]
WHERE [b].[Id] = [p].[BlogId]) AS [TotalViews]
FROM [Blogs] AS [b]
Your code
using Microsoft.EntityFrameworkCore;
public class Blog
{
public int Id { get; set; }
public int Rating { get; set; }
public List<Post> Posts { get; set; } = null!;
}
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; } = null!;
public List<Metric> Metrics { get; set; } = null!;
}
public class Metric
{
public int Id { get; set; }
public int PostId { get; set; }
public Post Post { get; set; } = null!;
public int Views { get; set; }
public int Likes { get; set; }
}
public class AppContext : DbContext
{
public DbSet<Blog> Blogs => Set<Blog>();
public DbSet<Post> Posts => Set<Post>();
public DbSet<Metric> Metrics => Set<Metric>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlServer(
@"Server=(localdb)\MSSQLLocalDB;Database=Repro;Trusted_Connection=True;TrustServerCertificate=True");
}
public static class Program
{
public static void Main()
{
using var ctx = new AppContext();
var query = from b in ctx.Blogs
let projected = from p in b.Posts
// Projecting only x.Views here keeps the working translation.
let m = p.Metrics.Select(x => new { x.Views, x.Likes }).FirstOrDefault()
select new { Views = m.Views * b.Rating, Likes = m.Likes * b.Rating }
select new
{
b.Id,
TotalViews = projected.Sum(x => x.Views),
TotalLikes = projected.Sum(x => x.Likes)
};
Console.WriteLine(query.ToQueryString());
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
// Throws on 11.0.0-rc.1.26410.101, succeeds on 11.0.0-preview.6.26359.118.
Console.WriteLine($"rows: {query.ToList().Count}");
}
}
Stack traces
Microsoft.Data.SqlClient.SqlException (0x80131904): Multiple columns are specified in an aggregated expression containing an outer reference. If an expression being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression.
at Microsoft.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.InitializeReader(Enumerator enumerator)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.<>c.<MoveNext>b__21_0(DbContext _, Enumerator enumerator)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Program.Main()
Error Number:8124,State:1,Class:16
Verbose output
EF Core version
11.0.0-rc.1.26410.101
Database provider
Microsoft.EntityFrameworkCore.SqlServer
Target framework
.NET 11
Operating system
Windows 11
IDE
No response
Bug description
An aggregate over a collection navigation generates SQL that SQL Server rejects when the aggregated expression combines a value from a
FirstOrDefault()subquery with a column from the outer query:This is a regression: the query executes on
11.0.0-preview.6.26359.118and fails on11.0.0-rc.1.26410.101.Each blog sums a value over its posts. The summed value multiplies a column read from a single-result subquery (
m.Views) by a column on the outer entity (b.Rating):On
11.0.0-rc.1.26410.101this produces:SUM([m1].[Views] * [b].[Rating])sits inside a sub-query correlated on[b].[Id] = [p].[BlogId], so[b].[Rating]is an outer reference and[m1].[Views]is a second column in the same aggregated expression. SQL Server rejects that, and the query fails at execution.On
11.0.0-preview.6.26359.118the same LINQ produces valid SQL. The multiplication is evaluated in anOUTER APPLY, so eachSUMreceives exactly one column:The number of members read from the single-result subquery decides which form is generated. Projecting one member instead of two keeps the
OUTER APPLYform on11.0.0-rc.1.26410.101and the query executes:Your code
Stack traces
Verbose output
EF Core version
11.0.0-rc.1.26410.101
Database provider
Microsoft.EntityFrameworkCore.SqlServer
Target framework
.NET 11
Operating system
Windows 11
IDE
No response