Skip to content

Improved paging links on secondary endpoint using inverse relationship #1010

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

Closed
bart-degreed opened this issue Jun 10, 2021 · 1 comment · Fixed by #1100
Closed

Improved paging links on secondary endpoint using inverse relationship #1010

bart-degreed opened this issue Jun 10, 2021 · 1 comment · Fixed by #1100

Comments

@bart-degreed
Copy link
Contributor

bart-degreed commented Jun 10, 2021

Paging links are currently implemented in two ways:

  1. If options.IncludeTotalResourceCount is enabled, we fetch COUNT(*) before retrieving the actual data. This enables the link renderer to know the total number of pages, so it can render the Next/Last links reliably.
  2. If this option is not enabled, the link renderer cannot know the total number of pages, so it switches to best-effort mode. This means it never renders Last and renders Next only when the current page is full. Note this may result in a Next link to an empty page.

For secondary endpoints, we currently have no way to fetch COUNT(*), so the link renderer always uses best-effort mode. The reason is that our query composition layer does not support retrieving nested values that do not map into a resource object.

Example for primary endpoint /articles?filter...:

var query =
    from article in DbContext.Articles
    where (filter)
    select article

var count = query.Count();

Example for secondary endpoint /blogs/1/articles?filter...:

var query =
    from blog in DbContext.Blogs
    where blog.Id == 1
    select new Blog(
        Id = blog.Id,
        Articles = blog.Articles.Where(filter) // where to put the count?
    );

var count = ?

There is no way to determine the total number of articles in the second example: query.Count() would always return 1. query.Single().Articles.Count() would fetch all articles first, then determine count in-memory (which is a no-go for large tables).

The proposed solution (to determine the total count on secondary endpoints) is to turn the query upside-down. By using the inverse of the relationship. So instead of using the relationship Blog.Articles, we use its inverse, which is Article.Blog.

Example for secondary endpoint /blogs/1/articles?filter...:

var query =
    from article in DbContext.Articles
    where article.Blog.Id == 1
    where (filter)
    select article;

var count = query.Count();

Notes:

  • If the inverse relationship is unavailable, there's nothing we can do to improve the current behavior. By default, we use the underlying EF Core model to find the inverse.
  • If another data source is used, developers need to implement a custom IInverseNavigationResolver.
  • This proposal solely affects logic to determine the total resource count. The actual fetching of data remains as-is.
  • This proposal only applies to to-many relationships, so we'll need to tackle many-to-many relationships too.
  • This requires a resource service to use a repository for a different resource type. For example: ResourceService<Blog> delegates to ResourceRepository<Article>. We can use our existing IResourceRepositoryAccessor for that.
  • IResourceDefinition.OnApplyFilter must be called on the secondary resource type, possibly on the primary resource type too.
@bart-degreed
Copy link
Contributor Author

This issue was created from TODO in code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

1 participant