Union with .SelectMany is not translated
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
I´ve been getting the following exception for a simple union:
```
Unable to translate a collection subquery in a projection since either parent or the subquery doesn't project necessary information required to uniquely identify it and correctly generate results on the client side.
This can happen when trying to correlate on keyless entity type. This can also happen for some cases of projection before 'Distinct' or some shapes of grouping key in case of 'GroupBy'.
These should either contain all key properties of the entity that the operation is applied on, or only contain simple property access expressions.
```
```C#
var blogsWithTags = db.Blogs.Select(b => new
{
b.BlogId,
Tags = b.Tags.Select(m => m.Tag.Name)
.Union(b.Posts.SelectMany(p => p.Tags).Select(m => m.Tag.Name))
}).ToList();
```
In EF6 this would generate something like this:
```
SELECT
[Project1].[C1] AS [C1],
[Project1].[BlogId] AS [BlogId],
[Project5].[C2] AS [C2],
[Project5].[C1] AS [C3]
FROM (SELECT
[Extent1].[BlogId] AS [BlogId],
1 AS [C1]
FROM [dbo].[Blogs] AS [Extent1] ) AS [Project1]
OUTER APPLY (SELECT
[Distinct1].[C1] AS [C1],
1 AS [C2]
FROM ( SELECT DISTINCT
[UnionAll1].[Name] AS [C1]
FROM (SELECT
[Extent3].[Name] AS [Name]
FROM [dbo].[BlogTagMappings] AS [Extent2]
INNER JOIN [dbo].[Tags] AS [Extent3] ON [Extent2].[TagId] = [Extent3].[Id]
WHERE [Project1].[BlogId] = [Extent2].[BlogId]
UNION ALL
SELECT
[Extent6].[Name] AS [Name]
FROM [dbo].[Posts] AS [Extent4]
INNER JOIN [dbo].[PostTagMappings] AS [Extent5] ON [Extent4].[PostId] = [Extent5].[PostId]
INNER JOIN [dbo].[Tags] AS [Extent6] ON [Extent5].[TagId] = [Extent6].[Id]
WHERE [Project1].[BlogId] = [Extent4].[BlogId]) AS [UnionAll1]
) AS [Distinct1] ) AS [Project5]
ORDER BY [Project1].[BlogId] ASC, [Project5].[C2] ASC
```
How is the query above supposed to be done with EF Core?
Below is a minimal example but in my use cases I have many more of these mappings and unions.
```C#
public class BloggingContext : DbContext
{
public DbSet Blogs { get; set; }
public DbSet Posts { get; set; }
public DbSet Tags { get; set; }
public DbSet BlogTagMappings { get; set; }
public DbSet PostTagMappings { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlServer(@"data source=(local);Integrated Security=SSPI;initial catalog=Blogging_Core;MultipleActiveResultSets=True;Encrypt=False");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity().HasKey(m => new { m.BlogId, m.TagId });
modelBuilder.Entity().HasKey(m => new { m.PostId, m.TagId });
}
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List Posts { get; } = new();
public List Tags { get; } = new();
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
public List Tags { get; } = new();
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
}
public class BlogTagMapping
{
public int BlogId { get; set; }
public int TagId { get; set; }
public Blog Blog { get; set; }
public Tag Tag { get; set; }
}
public class PostTagMapping
{
public int PostId { get; set; }
public int TagId { get; set; }
public Post Post { get; set; }
public Tag Tag { get; set; }
}
class Program
{
static void Main(string[] args)
{
using var db = new BloggingContext();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var blog = new Blog { Url = "http://blogs.msdn.com/adonet" };
db.Blogs.Add(blog);
var post1 = new Post { Title = "Post1", Content = "No content", Blog = blog };
var post2 = new Post { Title = "Post2", Content = "No content", Blog = blog };
db.Posts.Add(post1);
db.Posts.Add(post2);
var tag1 = new Tag { Name = "Tag1" };
var tag2 = new Tag { Name = "Tag2" };
db.Tags.Add(tag1);
db.Tags.Add(tag2);
db.BlogTagMappings.Add(new BlogTagMapping { Blog = blog, Tag = tag1 });
db.PostTagMappings.Add(new PostTagMapping { Post = post1, Tag = tag1 });
db.PostTagMappings.Add(new PostTagMapping { Post = post2, Tag = tag2 });
db.SaveChanges();
var blogsWithTags = db.Blogs.Select(b => new
{
b.BlogId,
Tags = b.Tags.Select(m => m.Tag.Name)
.Union(b.Posts.SelectMany(p => p.Tags).Select(m => m.Tag.Name))
}).ToList();
}
}
```
EF Core version: 7.0.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
Operating system: Windows 10
IDE: Visual Studio 2022 17.4.1
Contributor guide
Assessment
This issue has not been assessed yet.