Skip to content

Many-to-many (skip) navigation properties #19003

@ajcvickers

Description

@ajcvickers

This is one of the building blocks for many-to-many. However, it is also more broadly useful than just many-to-many.

Problem

Consider a model typical for a many-to-many relationship:

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }
    public string Content { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }

    public DateTime LinkCreated { get; set; }
}

Now consider how to load a Post and include all associated Tags. This requires using two navigation properties--one to go from Post to PostTag, and a second to go from PostTag to Post. For example:

var postsAndTags 
    = context.Posts
        .Include(e => e.PostTags)
        .ThenInclude(e => e.Tag)
        .ToList();

Likewise, querying across Posts and Tags also requires use of PostTags:

var post
    = context.Posts
        .Where(e => e.PostTags.Select(e => e.Tag.Content).Contains("MyTag")).ToList();

Proposal

The goal of forwarded navigation properties is to allow navigation properties to be defined which forward to some of the direct navigation properties in the model.

For example, forwarded navigation properties could be added to Post and Tag in the model above:

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public List<Tag> Tags { get; set; } // Skips over PostTag to Tag
    
    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }
    public string Content { get; set; }

    public List<Post> Posts { get; set; } // Skips over PostTag to Post
    
    public List<PostTag> PostTags { get; set; }
}

These forwarded navigation properties can then used to write simpler queries. For example, the queries from above can now be:

var postsAndTags 
    = context.Posts
        .Include(e => e.Tags)
        .ToList();
var post
    = context.Posts
        .Where(e => e.Tags.Select(e => e.Content).Contains("MyTag")).ToList();

Notes

Things to consider (non-exhaustive):

  • Model building API will be needed for the general case
    • Conventions/sugar may be use later for many-to-many pattern
    • Configuration by attribute could be considered, but might be too complex
  • It should be possible to define the skip-level navigation that skip over relationships that do not themselves expose navigation properties
    • This could involve shadow navigation properties if needed
    • However, also note that it is expected that "skipped" entity types (like for the join table in the example) can still be used explicitly as needed. This allows additional properties to be used, such as the LinkCreated property in the example above.

Tasks:

  • Model support
  • Fluent API
  • Change tracking support
  • Query support
  • Tracking query tests

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions