Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog/7.2.2-dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[main] Query.CreateDelayedQuery(key, Func<IOrderedQueryable<TElement>>) applies external key instead of default computed, as it suppose to
[main] QueryEndpoint.SingleAsync()/SingleOrDefaultAsync() get overloads that can recieve one key value as parameter without need to create array explicitly
[main] PrefetchQuery<T> implements IAsyncEnumerable<T>, extra call for .AsAsyncEmumerable() is not needed for async enumeration
[main] PrefetchQuery<T>.AsAsyncEmumerable() is marked as Obsolete
[main] Support for C#14+ optimization that applies ReadOnlySpan<T>.Contains() extension instead of IEnumerable<T>.Contains() one to arrays
8 changes: 4 additions & 4 deletions Orm/Xtensive.Orm.Manual/Prefetch/PrefetchTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public async Task MainAsyncTest()
.Prefetch(p => p.Photo) // Lazy load field
.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
.Prefetch(p => p.Manager.Photo); // Referenced entity and lazy load field for each of them
await foreach (var person in people.AsAsyncEnumerable()) {
await foreach (var person in people) {
var accessor = DirectStateAccessor.Get(person);
Assert.That(accessor.GetFieldState("Photo"), Is.EqualTo(PersistentFieldState.Loaded));
Assert.That(accessor.GetFieldState("Manager"), Is.EqualTo(PersistentFieldState.Loaded));
Expand Down Expand Up @@ -275,7 +275,7 @@ orderby person.Name
.Prefetch(p => p.Employees // EntitySet Employees
.Prefetch(e => e.Photo)) // and lazy load field of each of its items
.Prefetch(p => p.Manager); // Referenced entity
await foreach (var person in prefetchedPeople.AsAsyncEnumerable()) {
await foreach (var person in prefetchedPeople) {
var accessor = DirectStateAccessor.Get(person);
Assert.That(accessor.GetFieldState("Photo"), Is.EqualTo(PersistentFieldState.Loaded));
Assert.That(accessor.GetFieldState("Manager"), Is.EqualTo(PersistentFieldState.Loaded));
Expand Down Expand Up @@ -362,7 +362,7 @@ public async Task DelayedQueryAsyncTest()
.Prefetch(p => p.Photo) // Lazy load field
.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
.Prefetch(p => p.Manager.Photo); // Referenced entity and lazy load field for each of them
await foreach (var person in people.AsAsyncEnumerable()) {
await foreach (var person in people) {
var accessor = DirectStateAccessor.Get(person);
Assert.That(accessor.GetFieldState("Photo"), Is.EqualTo(PersistentFieldState.Loaded));
Assert.That(accessor.GetFieldState("Manager"), Is.EqualTo(PersistentFieldState.Loaded));
Expand Down Expand Up @@ -450,7 +450,7 @@ public async Task CachedQueryAsyncTest()
.Prefetch(p => p.Photo) // Lazy load field
.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
.Prefetch(p => p.Manager.Photo); // Referenced entity and lazy load field for each of them
await foreach (var person in people.AsAsyncEnumerable()) {
await foreach (var person in people) {
var accessor = DirectStateAccessor.Get(person);
Assert.That(accessor.GetFieldState("Photo"), Is.EqualTo(PersistentFieldState.Loaded));
Assert.That(accessor.GetFieldState("Manager"), Is.EqualTo(PersistentFieldState.Loaded));
Expand Down
23 changes: 11 additions & 12 deletions Orm/Xtensive.Orm.Tests/Storage/Prefetch/PrefetchTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,7 @@ public async Task PrefetchGraphPerformanceAsyncTest()
.Prefetch(i => i.InvoiceLines
.Prefetch(id => id.Track)
.Prefetch(id => id.Track.Album)
.Prefetch(id => id.Track.Bytes))
.AsAsyncEnumerable();
.Prefetch(id => id.Track.Bytes));
await foreach (var invoice in invoices) {
var id = invoice.InvoiceId;
var name = invoice.Customer.CompanyName;
Expand Down Expand Up @@ -247,7 +246,7 @@ public async Task EnumerableOfNonEntityAsyncTest()
await using (var session = await Domain.OpenSessionAsync())
await using (var tx = session.OpenTransaction()) {
var invoices = session.Query.Many<Invoice>(keys)
.Prefetch(o => o.DesignatedEmployee).AsAsyncEnumerable();
.Prefetch(o => o.DesignatedEmployee);
var invoiceType = Domain.Model.Types[typeof(Invoice)];
var employeeField = invoiceType.Fields[nameof(Invoice.DesignatedEmployee)];
var employeeType = Domain.Model.Types[typeof(Employee)];
Expand Down Expand Up @@ -361,7 +360,7 @@ public async Task PrefetchManyNotFullBatchAsyncTest()
var invoicesField = Domain.Model.Types[typeof (Track)].Fields["Playlists"];
var invoiceLinesField = Domain.Model.Types[typeof (Playlist)].Fields["Tracks"];
var tracks = session.Query.All<Track>().Take(50)
.Prefetch(t => t.Playlists.Prefetch(il => il.Tracks)).AsAsyncEnumerable();
.Prefetch(t => t.Playlists.Prefetch(il => il.Tracks));
int count1 = 0, count2 = 0;
await foreach (var track in tracks) {
count1++;
Expand Down Expand Up @@ -413,7 +412,7 @@ public async Task PrefetchManySeveralBatchesAsyncTest()
var trackField = Domain.Model.Types[typeof(InvoiceLine)].Fields[nameof(InvoiceLine.Track)];
var invoices = session.Query.All<Invoice>()
.Take(90)
.Prefetch(o => o.InvoiceLines.Prefetch(od => od.Track)).AsAsyncEnumerable();
.Prefetch(o => o.InvoiceLines.Prefetch(od => od.Track));
int count1 = 0, count2 = 0;
await foreach (var invoice in invoices) {
count1++;
Expand Down Expand Up @@ -483,7 +482,7 @@ public async Task PrefetchSingleAsyncTest()
var employeeType = Domain.Model.Types[typeof(Employee)];
var invoiceType = Domain.Model.Types[typeof(Invoice)];
var invoices = session.Query.Many<Invoice>(keys)
.Prefetch(o => o.DesignatedEmployee.Invoices).AsAsyncEnumerable();
.Prefetch(o => o.DesignatedEmployee.Invoices);
var count = 0;
await foreach (var invoice in invoices) {
Assert.AreEqual(keys[count], invoice.Key);
Expand Down Expand Up @@ -674,7 +673,7 @@ public async Task SimultaneouslyUsageOfMultipleEnumeratorsAsyncTest()
await using (var session = await Domain.OpenSessionAsync())
await using (var tx = session.OpenTransaction()) {
var source = session.Query.All<Invoice>().ToList();
var prefetchQuery = source.Prefetch(i => i.InvoiceLines).AsAsyncEnumerable();
var prefetchQuery = source.Prefetch(i => i.InvoiceLines);
await using (var enumerator0 = prefetchQuery.GetAsyncEnumerator()) {
_ = await enumerator0.MoveNextAsync();
_ = await enumerator0.MoveNextAsync();
Expand Down Expand Up @@ -729,7 +728,7 @@ public async Task RootElementIsNullPrefetchAsyncTest()

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

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

await using (var tx = session.OpenTransaction()) {
var books = session.Query.All<PrefetchModel.Book>().AsEnumerable().Concat(Enumerable.Repeat<PrefetchModel.Book>(null, 1))
.Prefetch(b => b.Title.Book).AsAsyncEnumerable();
.Prefetch(b => b.Title.Book);
var titleField = Domain.Model.Types[typeof(PrefetchModel.Book)].Fields[nameof(PrefetchModel.Book.Title)];
var titleType = Domain.Model.Types[typeof(PrefetchModel.Title)];
var count = 0;
Expand Down Expand Up @@ -883,7 +882,7 @@ public async Task StructureFieldsPrefetchAsyncTest()
await using (var tx = session.OpenTransaction()) {
var containers = session.Query.Many<PrefetchModel.OfferContainer>(Enumerable.Repeat(containerKey, 1))
.Prefetch(oc => oc.RealOffer.Book)
.Prefetch(oc => oc.IntermediateOffer.RealOffer.BookShop).AsAsyncEnumerable();
.Prefetch(oc => oc.IntermediateOffer.RealOffer.BookShop);
await foreach (var key in containers) {
PrefetchTestHelper.AssertOnlyDefaultColumnsAreLoaded(book0Key, book0Key.TypeInfo, session);
PrefetchTestHelper.AssertOnlyDefaultColumnsAreLoaded(bookShop1Key, bookShop1Key.TypeInfo, session);
Expand Down Expand Up @@ -917,7 +916,7 @@ public async Task StructurePrefetchAsyncTest()
await using (var session = await Domain.OpenSessionAsync())
await using (var tx = session.OpenTransaction()) {
var containers = session.Query.Many<PrefetchModel.OfferContainer>(Enumerable.Repeat(containerKey, 1))
.Prefetch(oc => oc.IntermediateOffer).AsAsyncEnumerable();
.Prefetch(oc => oc.IntermediateOffer);
await foreach (var key in containers) {
PrefetchTestHelper.AssertOnlySpecifiedColumnsAreLoaded(containerKey, containerKey.TypeInfo, session,
field => PrefetchTestHelper.IsFieldToBeLoadedByDefault(field) || field.Name.StartsWith("IntermediateOffer"));
Expand Down
46 changes: 26 additions & 20 deletions Orm/Xtensive.Orm/Orm/PrefetchQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,27 @@ namespace Xtensive.Orm
/// initial query result.
/// </summary>
/// <typeparam name="TElement">The type of the queried elements.</typeparam>
public readonly struct PrefetchQuery<TElement> : IEnumerable<TElement>
public readonly struct PrefetchQuery<TElement> : IEnumerable<TElement>, IAsyncEnumerable<TElement>
{
#region Nested types
private class ExecuteAsyncResult : IEnumerable<TElement>
{
private readonly List<TElement> items;
// We need to hold StrongReferenceContainer to prevent loaded entities from being collected
private readonly StrongReferenceContainer referenceContainer;

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public IEnumerator<TElement> GetEnumerator() => items.GetEnumerator();

public ExecuteAsyncResult(List<TElement> items, StrongReferenceContainer referenceContainer)
{
this.items = items;
this.referenceContainer = referenceContainer;
}
}
#endregion

private readonly Session session;
private readonly IEnumerable<TElement> source;
private readonly SinglyLinkedList<KeyExtractorNode<TElement>> nodes;
Expand All @@ -38,38 +57,25 @@ internal PrefetchQuery<TElement> RegisterPath<TValue>(Expression<Func<TElement,
public IEnumerator<TElement> GetEnumerator() =>
new PrefetchQueryEnumerable<TElement>(session, source, nodes).GetEnumerator();

/// <inheritdoc />
public IAsyncEnumerator<TElement> GetAsyncEnumerator(CancellationToken token = default) =>
new PrefetchQueryAsyncEnumerable<TElement>(session, source, nodes).GetAsyncEnumerator(token);

/// <summary>
/// Transforms <see cref="PrefetchQuery{TElement}"/> to an <see cref="IAsyncEnumerable{T}"/> sequence.
/// </summary>
[Obsolete("PrefetchQuery itself is an IAsyncEnumerable implementation")]
public IAsyncEnumerable<TElement> AsAsyncEnumerable() =>
new PrefetchQueryAsyncEnumerable<TElement>(session, source, nodes);

private class ExecuteAsyncResult : IEnumerable<TElement>
{
private readonly List<TElement> items;
// We need to hold StrongReferenceContainer to prevent loaded entities from being collected
private readonly StrongReferenceContainer referenceContainer;

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public IEnumerator<TElement> GetEnumerator() => items.GetEnumerator();

public ExecuteAsyncResult(List<TElement> items, StrongReferenceContainer referenceContainer)
{
this.items = items;
this.referenceContainer = referenceContainer;
}
}

/// <summary>
/// Asynchronously executes given <see cref="PrefetchQuery{TElement}"/> instance.
/// </summary>
/// <remarks>
/// <para>
/// This method internally puts all elements of the resulting sequence to a list
/// and then it wraps mentioned list as a <see cref="QueryResult{TItem}"/>.
/// As a consequence it is more efficient to use asynchronous enumeration over result of
/// <see cref="AsAsyncEnumerable"/> method call because it can perform lazily
/// As a consequence it is more efficient to use asynchronous enumeration because it can perform lazily
/// not putting everything into intermediate list.
/// </para>
/// <para> Multiple active operations are not supported. Use <see langword="await"/>
Expand Down
Loading