Skip to content

Commit

Permalink
Add async infrastructure for lazy-loading
Browse files Browse the repository at this point in the history
Part of #10509, #3797

Adds ILazyLoader.LoadAsync and allows binding to delegate for it.
  • Loading branch information
ajcvickers committed Feb 7, 2018
1 parent 3b9a5e9 commit 7a0ad41
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 9 deletions.
258 changes: 258 additions & 0 deletions src/EFCore.Specification.Tests/WithConstructorsTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
Expand Down Expand Up @@ -374,6 +376,37 @@ public virtual void Query_with_loader_injected_for_collections()
}
}

[Fact]
public virtual async Task Query_with_loader_injected_for_reference_async()
{
using (var context = CreateContext())
{
var post = await context.Set<LazyAsyncPost>().OrderBy(e => e.Id).FirstAsync();

var loaded = await post.LoadBlogAsync();

Assert.NotNull(loaded);
Assert.Same(loaded, post.LazyAsyncBlog);
Assert.Contains(post, post.LazyAsyncBlog.LazyAsyncPosts);
}
}

[Fact]
public virtual async Task Query_with_loader_injected_for_collections_async()
{
using (var context = CreateContext())
{
var blog = await context.Set<LazyAsyncBlog>().SingleAsync();

var loaded = await blog.LoadPostsAsync();

Assert.Same(loaded, blog.LazyAsyncPosts);
Assert.Equal(2, blog.LazyAsyncPosts.Count());
Assert.Same(blog, blog.LazyAsyncPosts.First().LazyAsyncBlog);
Assert.Same(blog, blog.LazyAsyncPosts.Skip(1).First().LazyAsyncBlog);
}
}

[Fact]
public virtual void Query_with_POCO_loader_injected_for_reference()
{
Expand All @@ -398,6 +431,37 @@ public virtual void Query_with_POCO_loader_injected_for_collections()
Assert.Same(blog, blog.LazyPocoPosts.Skip(1).First().LazyPocoBlog);
}
}

[Fact]
public virtual async Task Query_with_loader_delegate_injected_for_reference_async()
{
using (var context = CreateContext())
{
var post = await context.Set<LazyAsyncPocoPost>().OrderBy(e => e.Id).FirstAsync();

var loaded = await post.LoadBlogAsync();

Assert.NotNull(loaded);
Assert.Same(loaded, post.LazyAsyncPocoBlog);
Assert.Contains(post, post.LazyAsyncPocoBlog.LazyAsyncPocoPosts);
}
}

[Fact]
public virtual async Task Query_with_loader_delegate_injected_for_collections_async()
{
using (var context = CreateContext())
{
var blog = await context.Set<LazyAsyncPocoBlog>().SingleAsync();

var loaded = await blog.LoadPostsAsync();

Assert.Same(loaded, blog.LazyAsyncPocoPosts);
Assert.Equal(2, blog.LazyAsyncPocoPosts.Count());
Assert.Same(blog, blog.LazyAsyncPocoPosts.First().LazyAsyncPocoBlog);
Assert.Same(blog, blog.LazyAsyncPocoPosts.Skip(1).First().LazyAsyncPocoBlog);
}
}

[Fact]
public virtual void Query_with_loader_injected_into_property_for_reference()
Expand Down Expand Up @@ -633,6 +697,37 @@ public virtual void Query_with_loader_delgate_injected_into_property_for_collect
}
}

[Fact]
public virtual async Task Query_with_loader_delegate_injected_into_property_for_reference_async()
{
using (var context = CreateContext())
{
var post = await context.Set<LazyAsyncPsPost>().OrderBy(e => e.Id).FirstAsync();

var loaded = await post.LoadBlogAsync();

Assert.NotNull(loaded);
Assert.Same(loaded, post.LazyAsyncPsBlog);
Assert.Contains(post, post.LazyAsyncPsBlog.LazyAsyncPsPosts);
}
}

[Fact]
public virtual async Task Query_with_loader_delegate_injected_into_property_for_collections_async()
{
using (var context = CreateContext())
{
var blog = await context.Set<LazyAsyncPsBlog>().SingleAsync();

var loaded = await blog.LoadPostsAsync();

Assert.Same(loaded, blog.LazyAsyncPsPosts);
Assert.Equal(2, blog.LazyAsyncPsPosts.Count());
Assert.Same(blog, blog.LazyAsyncPsPosts.First().LazyAsyncPsBlog);
Assert.Same(blog, blog.LazyAsyncPsPosts.Skip(1).First().LazyAsyncPsBlog);
}
}

[Fact]
public virtual void Query_with_loader_injected_into_property_via_constructor_for_reference()
{
Expand Down Expand Up @@ -1076,6 +1171,41 @@ public LazyPsBlog LazyPsBlog
}
}

protected class LazyAsyncPsBlog
{
private readonly ICollection<LazyAsyncPsPost> _lazyAsyncPsPosts = new List<LazyAsyncPsPost>();

private Func<object, CancellationToken, string, Task> LazyLoader { get; set; }

public int Id { get; set; }

public void AddPost(LazyAsyncPsPost post) => _lazyAsyncPsPosts.Add(post);

public async Task<IEnumerable<LazyAsyncPsPost>> LoadPostsAsync(CancellationToken cancellationToken = default)
{
await LazyLoader(this, cancellationToken, nameof(LazyAsyncPsPosts));

return LazyAsyncPsPosts;
}

public IEnumerable<LazyAsyncPsPost> LazyAsyncPsPosts => _lazyAsyncPsPosts;
}

protected class LazyAsyncPsPost
{
private Func<object, CancellationToken, string, Task> LazyLoader { get; set; }

public int Id { get; set; }
public async Task<LazyAsyncPsBlog> LoadBlogAsync(CancellationToken cancellationToken = default)
{
await LazyLoader(this, cancellationToken, nameof(LazyAsyncPsBlog));

return LazyAsyncPsBlog;
}

public LazyAsyncPsBlog LazyAsyncPsBlog { get; set; }
}

protected class LazyPcBlog
{
private ICollection<LazyPcPost> _lazyPcPosts = new List<LazyPcPost>();
Expand Down Expand Up @@ -1266,6 +1396,112 @@ public LazyPocoBlog LazyPocoBlog
}
}

protected class LazyAsyncPocoBlog
{
private readonly Func<object, CancellationToken, string, Task> _loader;
private readonly ICollection<LazyAsyncPocoPost> _lazyAsyncPocoPosts = new List<LazyAsyncPocoPost>();

public LazyAsyncPocoBlog()
{
}

private LazyAsyncPocoBlog(Func<object, CancellationToken, string, Task> lazyLoader)
{
_loader = lazyLoader;
}

public int Id { get; set; }

public void AddPost(LazyAsyncPocoPost post) => _lazyAsyncPocoPosts.Add(post);

public async Task<IEnumerable<LazyAsyncPocoPost>> LoadPostsAsync(CancellationToken cancellationToken = default)
{
await _loader(this, cancellationToken, nameof(LazyAsyncPocoPosts));

return LazyAsyncPocoPosts;
}

public IEnumerable<LazyAsyncPocoPost> LazyAsyncPocoPosts => _lazyAsyncPocoPosts;
}

protected class LazyAsyncPocoPost
{
private readonly Func<object, CancellationToken, string, Task> _loader;

public LazyAsyncPocoPost()
{
}

private LazyAsyncPocoPost(Func<object, CancellationToken, string, Task> lazyLoader)
{
_loader = lazyLoader;
}

public int Id { get; set; }

public async Task<LazyAsyncPocoBlog> LoadBlogAsync(CancellationToken cancellationToken = default)
{
await _loader(this, cancellationToken, nameof(LazyAsyncPocoBlog));

return LazyAsyncPocoBlog;
}

public LazyAsyncPocoBlog LazyAsyncPocoBlog { get; set; }
}

protected class LazyAsyncBlog
{
private readonly ILazyLoader _loader;
private readonly ICollection<LazyAsyncPost> _lazyAsyncPosts = new List<LazyAsyncPost>();

public LazyAsyncBlog()
{
}

private LazyAsyncBlog(ILazyLoader loader)
{
_loader = loader;
}

public int Id { get; set; }

public void AddPost(LazyAsyncPost post) => _lazyAsyncPosts.Add(post);

public async Task<IEnumerable<LazyAsyncPost>> LoadPostsAsync(CancellationToken cancellationToken = default)
{
await _loader.LoadAsync(this, cancellationToken, nameof(LazyAsyncPosts));

return LazyAsyncPosts;
}

public IEnumerable<LazyAsyncPost> LazyAsyncPosts => _lazyAsyncPosts;
}

protected class LazyAsyncPost
{
private readonly ILazyLoader _loader;

public LazyAsyncPost()
{
}

private LazyAsyncPost(ILazyLoader loader)
{
_loader = loader;
}

public int Id { get; set; }

public async Task<LazyAsyncBlog> LoadBlogAsync(CancellationToken cancellationToken = default)
{
await _loader.LoadAsync(this, cancellationToken, nameof(LazyAsyncBlog));

return LazyAsyncBlog;
}

public LazyAsyncBlog LazyAsyncBlog { get; set; }
}

public class OtherContext : DbContext
{
}
Expand Down Expand Up @@ -1326,9 +1562,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Entity<LazyBlog>();
modelBuilder.Entity<LazyPocoBlog>();

modelBuilder.Entity<LazyAsyncBlog>();
modelBuilder.Entity<LazyAsyncPocoBlog>();

modelBuilder.Entity<LazyPropertyBlog>();
modelBuilder.Entity<LazyPcBlog>();
modelBuilder.Entity<LazyPsBlog>();
modelBuilder.Entity<LazyAsyncPsBlog>();
modelBuilder.Entity<LazyPcsBlog>();

// Manually configure service fields since there is no public API yet
Expand Down Expand Up @@ -1397,12 +1637,24 @@ protected override void Seed(WithConstructorsContext context)

context.Add(lazyBlog);

var lazyAsyncBlog = new LazyAsyncBlog();
lazyAsyncBlog.AddPost(new LazyAsyncPost());
lazyAsyncBlog.AddPost(new LazyAsyncPost());

context.Add(lazyAsyncBlog);

var lazyPocoBlog = new LazyPocoBlog();
lazyPocoBlog.AddPost(new LazyPocoPost());
lazyPocoBlog.AddPost(new LazyPocoPost());

context.Add(lazyPocoBlog);

var lazyAsyncPocoBlog = new LazyAsyncPocoBlog();
lazyAsyncPocoBlog.AddPost(new LazyAsyncPocoPost());
lazyAsyncPocoBlog.AddPost(new LazyAsyncPocoPost());

context.Add(lazyAsyncPocoBlog);

var lazyPropertyBlog = new LazyPropertyBlog();
lazyPropertyBlog.AddPost(new LazyPropertyPost());
lazyPropertyBlog.AddPost(new LazyPropertyPost());
Expand All @@ -1421,6 +1673,12 @@ protected override void Seed(WithConstructorsContext context)

context.Add(lazyPsBlog);

var lazyAsyncPsBlog = new LazyAsyncPsBlog();
lazyAsyncPsBlog.AddPost(new LazyAsyncPsPost());
lazyAsyncPsBlog.AddPost(new LazyAsyncPsPost());

context.Add(lazyAsyncPsBlog);

var lazyPcBlog = new LazyPcBlog();
lazyPcBlog.AddPost(new LazyPcPost());
lazyPcBlog.AddPost(new LazyPcPost());
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/ChangeTracking/NavigationEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public virtual void Load()
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
/// </param>
/// <returns>
/// A task that represents the asynchronous save operation.
/// A task that represents the asynchronous operation.
/// </returns>
public virtual Task LoadAsync(CancellationToken cancellationToken = default)
=> IsLoaded
Expand Down
15 changes: 15 additions & 0 deletions src/EFCore/ILazyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace Microsoft.EntityFrameworkCore
Expand All @@ -19,5 +21,18 @@ public interface ILazyLoader
/// <param name="navigationName"> The navigation property name. </param>
// ReSharper disable once AssignNullToNotNullAttribute
void Load([NotNull] object entity, [NotNull] [CallerMemberName] string navigationName = null);

/// <summary>
/// Loads a navigation property if it has not already been loaded.
/// </summary>
/// <param name="entity"> The entity on which the navigation property is located. </param>
/// <param name="cancellationToken"> A <see cref="CancellationToken" /> to observe while waiting for the task to complete. </param>
/// <param name="navigationName"> The navigation property name. </param>
/// <returns> A task that represents the asynchronous operation. </returns>
Task LoadAsync(
[NotNull] object entity,
CancellationToken cancellationToken = default,
// ReSharper disable once AssignNullToNotNullAttribute
[NotNull] [CallerMemberName] string navigationName = null);
}
}
Loading

0 comments on commit 7a0ad41

Please sign in to comment.