Entity Framework Core has yet to implement many-to-many relationships, as tracked in GitHub issue #1368; however, when I follow the navigation examples in that issue or similar answers here at Stack Overflow, my enumeration fails to yield results.
I have a many-to-many relationship between Photos and Tags.
After implementing the join table, examples show I should be able to:
var tags = photo.PhotoTags.Select(p => p.Tag);
While that yields no results, I am able to to load via:
var tags = _context.Photos
.Where(p => p.Id == 1)
.SelectMany(p => p.PhotoTags)
.Select(j => j.Tag)
.ToList();
Relevant code:
public class Photo
{
public int Id { get; set; }
public virtual ICollection<PhotoTag> PhotoTags { get; set; }
}
public class Tag
{
public int Id { get; set; }
public virtual ICollection<PhotoTag> PhotoTags { get; set; }
}
public class PhotoTag
{
public int PhotoId { get; set; }
public Photo Photo { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<PhotoTag>().HasKey(x => new { x.PhotoId, x.TagId });
}
What am I missing from other examples?
In fact this is not a specific for many-to-many
relationship, but in general to the lack of lazy loading support in EF Core. So in order to have Tag
property populated, it has to be eager (or explicitly) loaded. All this is (sort of) explained in the Loading Related Data section of the EF Core documentation. If you take a look at Including multiple levels section, you'll see the following explanation
You can drill down thru relationships to include multiple levels of related data using the
ThenInclude
method. The following example loads all blogs, their related posts, and the author of each post.
and example for loading the Post.Author
which is pretty much the same as yours:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
}
So to make this working
var tags = photo.PhotoTags.Select(p => p.Tag);
the photo
variable should have been be retrieved using something like this:
var photo = _context.Photos
.Include(e => e.PhotoTags)
.ThenInclude(e => e.Tag)
.FirstOrDefault(e => e.Id == 1);