Skip to content

Commit ad5585e

Browse files
authored
Merge pull request #465 from DataObjects-NET/7.2-prefetchquery-iasyncenumerable
PrefetchQuery<T> implements IAsyncEnumerable<T>
2 parents 470d6c7 + 67339d0 commit ad5585e

4 files changed

Lines changed: 43 additions & 36 deletions

File tree

ChangeLog/7.2.2-dev.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
[main] Query.CreateDelayedQuery(key, Func<IOrderedQueryable<TElement>>) applies external key instead of default computed, as it suppose to
22
[main] QueryEndpoint.SingleAsync()/SingleOrDefaultAsync() get overloads that can recieve one key value as parameter without need to create array explicitly
3+
[main] PrefetchQuery<T> implements IAsyncEnumerable<T>, extra call for .AsAsyncEmumerable() is not needed for async enumeration
4+
[main] PrefetchQuery<T>.AsAsyncEmumerable() is marked as Obsolete
35
[main] Support for C#14+ optimization that applies ReadOnlySpan<T>.Contains() extension instead of IEnumerable<T>.Contains() one to arrays

Orm/Xtensive.Orm.Manual/Prefetch/PrefetchTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public async Task MainAsyncTest()
180180
.Prefetch(p => p.Photo) // Lazy load field
181181
.Prefetch(p => p.Employees.Prefetch(e => e.Photo)) // EntitySet Employees and lazy load field of each of its items with the limit on number of items to be loaded
182182
.Prefetch(p => p.Manager.Photo); // Referenced entity and lazy load field for each of them
183-
await foreach (var person in people.AsAsyncEnumerable()) {
183+
await foreach (var person in people) {
184184
var accessor = DirectStateAccessor.Get(person);
185185
Assert.That(accessor.GetFieldState("Photo"), Is.EqualTo(PersistentFieldState.Loaded));
186186
Assert.That(accessor.GetFieldState("Manager"), Is.EqualTo(PersistentFieldState.Loaded));
@@ -275,7 +275,7 @@ orderby person.Name
275275
.Prefetch(p => p.Employees // EntitySet Employees
276276
.Prefetch(e => e.Photo)) // and lazy load field of each of its items
277277
.Prefetch(p => p.Manager); // Referenced entity
278-
await foreach (var person in prefetchedPeople.AsAsyncEnumerable()) {
278+
await foreach (var person in prefetchedPeople) {
279279
var accessor = DirectStateAccessor.Get(person);
280280
Assert.That(accessor.GetFieldState("Photo"), Is.EqualTo(PersistentFieldState.Loaded));
281281
Assert.That(accessor.GetFieldState("Manager"), Is.EqualTo(PersistentFieldState.Loaded));
@@ -362,7 +362,7 @@ public async Task DelayedQueryAsyncTest()
362362
.Prefetch(p => p.Photo) // Lazy load field
363363
.Prefetch(p => p.Employees.Prefetch(e => e.Photo)) // EntitySet Employees and lazy load field of each of its items with the limit on number of items to be loaded
364364
.Prefetch(p => p.Manager.Photo); // Referenced entity and lazy load field for each of them
365-
await foreach (var person in people.AsAsyncEnumerable()) {
365+
await foreach (var person in people) {
366366
var accessor = DirectStateAccessor.Get(person);
367367
Assert.That(accessor.GetFieldState("Photo"), Is.EqualTo(PersistentFieldState.Loaded));
368368
Assert.That(accessor.GetFieldState("Manager"), Is.EqualTo(PersistentFieldState.Loaded));
@@ -450,7 +450,7 @@ public async Task CachedQueryAsyncTest()
450450
.Prefetch(p => p.Photo) // Lazy load field
451451
.Prefetch(p => p.Employees.Prefetch(e => e.Photo)) // EntitySet Employees and lazy load field of each of its items with the limit on number of items to be loaded
452452
.Prefetch(p => p.Manager.Photo); // Referenced entity and lazy load field for each of them
453-
await foreach (var person in people.AsAsyncEnumerable()) {
453+
await foreach (var person in people) {
454454
var accessor = DirectStateAccessor.Get(person);
455455
Assert.That(accessor.GetFieldState("Photo"), Is.EqualTo(PersistentFieldState.Loaded));
456456
Assert.That(accessor.GetFieldState("Manager"), Is.EqualTo(PersistentFieldState.Loaded));

Orm/Xtensive.Orm.Tests/Storage/Prefetch/PrefetchTest.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,7 @@ public async Task PrefetchGraphPerformanceAsyncTest()
181181
.Prefetch(i => i.InvoiceLines
182182
.Prefetch(id => id.Track)
183183
.Prefetch(id => id.Track.Album)
184-
.Prefetch(id => id.Track.Bytes))
185-
.AsAsyncEnumerable();
184+
.Prefetch(id => id.Track.Bytes));
186185
await foreach (var invoice in invoices) {
187186
var id = invoice.InvoiceId;
188187
var name = invoice.Customer.CompanyName;
@@ -247,7 +246,7 @@ public async Task EnumerableOfNonEntityAsyncTest()
247246
await using (var session = await Domain.OpenSessionAsync())
248247
await using (var tx = session.OpenTransaction()) {
249248
var invoices = session.Query.Many<Invoice>(keys)
250-
.Prefetch(o => o.DesignatedEmployee).AsAsyncEnumerable();
249+
.Prefetch(o => o.DesignatedEmployee);
251250
var invoiceType = Domain.Model.Types[typeof(Invoice)];
252251
var employeeField = invoiceType.Fields[nameof(Invoice.DesignatedEmployee)];
253252
var employeeType = Domain.Model.Types[typeof(Employee)];
@@ -361,7 +360,7 @@ public async Task PrefetchManyNotFullBatchAsyncTest()
361360
var invoicesField = Domain.Model.Types[typeof (Track)].Fields["Playlists"];
362361
var invoiceLinesField = Domain.Model.Types[typeof (Playlist)].Fields["Tracks"];
363362
var tracks = session.Query.All<Track>().Take(50)
364-
.Prefetch(t => t.Playlists.Prefetch(il => il.Tracks)).AsAsyncEnumerable();
363+
.Prefetch(t => t.Playlists.Prefetch(il => il.Tracks));
365364
int count1 = 0, count2 = 0;
366365
await foreach (var track in tracks) {
367366
count1++;
@@ -413,7 +412,7 @@ public async Task PrefetchManySeveralBatchesAsyncTest()
413412
var trackField = Domain.Model.Types[typeof(InvoiceLine)].Fields[nameof(InvoiceLine.Track)];
414413
var invoices = session.Query.All<Invoice>()
415414
.Take(90)
416-
.Prefetch(o => o.InvoiceLines.Prefetch(od => od.Track)).AsAsyncEnumerable();
415+
.Prefetch(o => o.InvoiceLines.Prefetch(od => od.Track));
417416
int count1 = 0, count2 = 0;
418417
await foreach (var invoice in invoices) {
419418
count1++;
@@ -483,7 +482,7 @@ public async Task PrefetchSingleAsyncTest()
483482
var employeeType = Domain.Model.Types[typeof(Employee)];
484483
var invoiceType = Domain.Model.Types[typeof(Invoice)];
485484
var invoices = session.Query.Many<Invoice>(keys)
486-
.Prefetch(o => o.DesignatedEmployee.Invoices).AsAsyncEnumerable();
485+
.Prefetch(o => o.DesignatedEmployee.Invoices);
487486
var count = 0;
488487
await foreach (var invoice in invoices) {
489488
Assert.AreEqual(keys[count], invoice.Key);
@@ -674,7 +673,7 @@ public async Task SimultaneouslyUsageOfMultipleEnumeratorsAsyncTest()
674673
await using (var session = await Domain.OpenSessionAsync())
675674
await using (var tx = session.OpenTransaction()) {
676675
var source = session.Query.All<Invoice>().ToList();
677-
var prefetchQuery = source.Prefetch(i => i.InvoiceLines).AsAsyncEnumerable();
676+
var prefetchQuery = source.Prefetch(i => i.InvoiceLines);
678677
await using (var enumerator0 = prefetchQuery.GetAsyncEnumerator()) {
679678
_ = await enumerator0.MoveNextAsync();
680679
_ = await enumerator0.MoveNextAsync();
@@ -729,7 +728,7 @@ public async Task RootElementIsNullPrefetchAsyncTest()
729728

730729
await using (var tx = session.OpenTransaction()) {
731730
var books = session.Query.All<PrefetchModel.Book>().AsEnumerable()
732-
.Concat(Enumerable.Repeat<PrefetchModel.Book>(null, 1)).Prefetch(b => b.Title).AsAsyncEnumerable();
731+
.Concat(Enumerable.Repeat<PrefetchModel.Book>(null, 1)).Prefetch(b => b.Title);
733732
var titleField = Domain.Model.Types[typeof(PrefetchModel.Book)].Fields[nameof(PrefetchModel.Book.Title)];
734733
var titleType = Domain.Model.Types[typeof(PrefetchModel.Title)];
735734
var count = 0;
@@ -783,7 +782,7 @@ public async Task NestedPrefetchWhenChildElementIsNullAsyncTest()
783782

784783
await using (var tx = session.OpenTransaction()) {
785784
var prefetcher = session.Query.All<PrefetchModel.Book>()
786-
.Prefetch(b => b.Title.Book).AsAsyncEnumerable();
785+
.Prefetch(b => b.Title.Book);
787786
var titleField = Domain.Model.Types[typeof(PrefetchModel.Book)].Fields[nameof(PrefetchModel.Book.Title)];
788787
var titleType = Domain.Model.Types[typeof(PrefetchModel.Title)];
789788
await foreach (var book in prefetcher) {
@@ -837,7 +836,7 @@ public async Task NestedPrefetchWhenRootElementIsNullAsyncTest()
837836

838837
await using (var tx = session.OpenTransaction()) {
839838
var books = session.Query.All<PrefetchModel.Book>().AsEnumerable().Concat(Enumerable.Repeat<PrefetchModel.Book>(null, 1))
840-
.Prefetch(b => b.Title.Book).AsAsyncEnumerable();
839+
.Prefetch(b => b.Title.Book);
841840
var titleField = Domain.Model.Types[typeof(PrefetchModel.Book)].Fields[nameof(PrefetchModel.Book.Title)];
842841
var titleType = Domain.Model.Types[typeof(PrefetchModel.Title)];
843842
var count = 0;
@@ -883,7 +882,7 @@ public async Task StructureFieldsPrefetchAsyncTest()
883882
await using (var tx = session.OpenTransaction()) {
884883
var containers = session.Query.Many<PrefetchModel.OfferContainer>(Enumerable.Repeat(containerKey, 1))
885884
.Prefetch(oc => oc.RealOffer.Book)
886-
.Prefetch(oc => oc.IntermediateOffer.RealOffer.BookShop).AsAsyncEnumerable();
885+
.Prefetch(oc => oc.IntermediateOffer.RealOffer.BookShop);
887886
await foreach (var key in containers) {
888887
PrefetchTestHelper.AssertOnlyDefaultColumnsAreLoaded(book0Key, book0Key.TypeInfo, session);
889888
PrefetchTestHelper.AssertOnlyDefaultColumnsAreLoaded(bookShop1Key, bookShop1Key.TypeInfo, session);
@@ -917,7 +916,7 @@ public async Task StructurePrefetchAsyncTest()
917916
await using (var session = await Domain.OpenSessionAsync())
918917
await using (var tx = session.OpenTransaction()) {
919918
var containers = session.Query.Many<PrefetchModel.OfferContainer>(Enumerable.Repeat(containerKey, 1))
920-
.Prefetch(oc => oc.IntermediateOffer).AsAsyncEnumerable();
919+
.Prefetch(oc => oc.IntermediateOffer);
921920
await foreach (var key in containers) {
922921
PrefetchTestHelper.AssertOnlySpecifiedColumnsAreLoaded(containerKey, containerKey.TypeInfo, session,
923922
field => PrefetchTestHelper.IsFieldToBeLoadedByDefault(field) || field.Name.StartsWith("IntermediateOffer"));

Orm/Xtensive.Orm/Orm/PrefetchQuery.cs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,27 @@ namespace Xtensive.Orm
1919
/// initial query result.
2020
/// </summary>
2121
/// <typeparam name="TElement">The type of the queried elements.</typeparam>
22-
public readonly struct PrefetchQuery<TElement> : IEnumerable<TElement>
22+
public readonly struct PrefetchQuery<TElement> : IEnumerable<TElement>, IAsyncEnumerable<TElement>
2323
{
24+
#region Nested types
25+
private class ExecuteAsyncResult : IEnumerable<TElement>
26+
{
27+
private readonly List<TElement> items;
28+
// We need to hold StrongReferenceContainer to prevent loaded entities from being collected
29+
private readonly StrongReferenceContainer referenceContainer;
30+
31+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
32+
33+
public IEnumerator<TElement> GetEnumerator() => items.GetEnumerator();
34+
35+
public ExecuteAsyncResult(List<TElement> items, StrongReferenceContainer referenceContainer)
36+
{
37+
this.items = items;
38+
this.referenceContainer = referenceContainer;
39+
}
40+
}
41+
#endregion
42+
2443
private readonly Session session;
2544
private readonly IEnumerable<TElement> source;
2645
private readonly SinglyLinkedList<KeyExtractorNode<TElement>> nodes;
@@ -38,38 +57,25 @@ internal PrefetchQuery<TElement> RegisterPath<TValue>(Expression<Func<TElement,
3857
public IEnumerator<TElement> GetEnumerator() =>
3958
new PrefetchQueryEnumerable<TElement>(session, source, nodes).GetEnumerator();
4059

60+
/// <inheritdoc />
61+
public IAsyncEnumerator<TElement> GetAsyncEnumerator(CancellationToken token = default) =>
62+
new PrefetchQueryAsyncEnumerable<TElement>(session, source, nodes).GetAsyncEnumerator(token);
63+
4164
/// <summary>
4265
/// Transforms <see cref="PrefetchQuery{TElement}"/> to an <see cref="IAsyncEnumerable{T}"/> sequence.
4366
/// </summary>
67+
[Obsolete("PrefetchQuery itself is an IAsyncEnumerable implementation")]
4468
public IAsyncEnumerable<TElement> AsAsyncEnumerable() =>
4569
new PrefetchQueryAsyncEnumerable<TElement>(session, source, nodes);
4670

47-
private class ExecuteAsyncResult : IEnumerable<TElement>
48-
{
49-
private readonly List<TElement> items;
50-
// We need to hold StrongReferenceContainer to prevent loaded entities from being collected
51-
private readonly StrongReferenceContainer referenceContainer;
52-
53-
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
54-
55-
public IEnumerator<TElement> GetEnumerator() => items.GetEnumerator();
56-
57-
public ExecuteAsyncResult(List<TElement> items, StrongReferenceContainer referenceContainer)
58-
{
59-
this.items = items;
60-
this.referenceContainer = referenceContainer;
61-
}
62-
}
63-
6471
/// <summary>
6572
/// Asynchronously executes given <see cref="PrefetchQuery{TElement}"/> instance.
6673
/// </summary>
6774
/// <remarks>
6875
/// <para>
6976
/// This method internally puts all elements of the resulting sequence to a list
7077
/// and then it wraps mentioned list as a <see cref="QueryResult{TItem}"/>.
71-
/// As a consequence it is more efficient to use asynchronous enumeration over result of
72-
/// <see cref="AsAsyncEnumerable"/> method call because it can perform lazily
78+
/// As a consequence it is more efficient to use asynchronous enumeration because it can perform lazily
7379
/// not putting everything into intermediate list.
7480
/// </para>
7581
/// <para> Multiple active operations are not supported. Use <see langword="await"/>

0 commit comments

Comments
 (0)