Skip to content

Commit d339116

Browse files
committed
also fixed dbSet.ToListAsync and variants
1 parent 5b10439 commit d339116

4 files changed

Lines changed: 48 additions & 2 deletions

File tree

src/ExpressiveSharp.EntityFrameworkCore/ExpressiveDbSet.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ public class ExpressiveDbSet<TEntity> : DbSet<TEntity>, IExpressiveQueryable<TEn
1717
private readonly DbSet<TEntity> _inner;
1818
private readonly IQueryable<TEntity> _queryable;
1919

20-
public ExpressiveDbSet(DbSet<TEntity> inner)
20+
// Constructed only via AsExpressiveDbSet() (through InternalExpressiveDbSet) so every
21+
// instance is the async-capable runtime subclass. See InternalExpressiveDbSet.
22+
private protected ExpressiveDbSet(DbSet<TEntity> inner)
2123
{
2224
_inner = inner;
2325
_queryable = inner;
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using ExpressiveSharp.EntityFrameworkCore;
2+
using ExpressiveSharp.EntityFrameworkCore.Infrastructure.Internal;
23

34
// ReSharper disable once CheckNamespace — intentionally in Microsoft.EntityFrameworkCore for discoverability
45
namespace Microsoft.EntityFrameworkCore;
@@ -7,5 +8,5 @@ public static class DbSetExtensions
78
{
89
public static ExpressiveDbSet<TEntity> AsExpressiveDbSet<TEntity>(this DbSet<TEntity> dbSet)
910
where TEntity : class
10-
=> new(dbSet);
11+
=> new InternalExpressiveDbSet<TEntity>(dbSet);
1112
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace ExpressiveSharp.EntityFrameworkCore.Infrastructure.Internal;
4+
5+
/// <summary>
6+
/// The concrete runtime instance produced by <c>AsExpressiveDbSet()</c>. It implements
7+
/// <see cref="IAsyncEnumerable{T}"/> — satisfied by the inherited
8+
/// <see cref="ExpressiveDbSet{TEntity}.GetAsyncEnumerator"/> — so EF Core's streaming async
9+
/// terminals (<c>ToListAsync</c>/<c>ToArrayAsync</c>/...), which runtime-cast the source to
10+
/// <see cref="IAsyncEnumerable{T}"/>, work directly on the set.
11+
/// <para>
12+
/// The interface lives here rather than on the public <see cref="ExpressiveDbSet{TEntity}"/> so
13+
/// callers never hold a static type that is both <see cref="IQueryable{T}"/> and
14+
/// <see cref="IAsyncEnumerable{T}"/> — which on .NET 10 makes those terminals ambiguous between
15+
/// <c>System.Linq.AsyncEnumerable</c> and EF Core's extensions. This mirrors EF Core's own
16+
/// public <c>DbSet&lt;T&gt;</c> / internal <c>InternalDbSet&lt;T&gt;</c> split.
17+
/// </para>
18+
/// </summary>
19+
internal sealed class InternalExpressiveDbSet<TEntity> : ExpressiveDbSet<TEntity>, IAsyncEnumerable<TEntity>
20+
where TEntity : class
21+
{
22+
public InternalExpressiveDbSet(DbSet<TEntity> inner) : base(inner)
23+
{
24+
}
25+
}

tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/AsyncQueryableTestBase.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ public async Task Include_Where_ToListAsync_LoadsNavigation()
121121
Assert.IsTrue(results.All(o => o.Customer != null));
122122
}
123123

124+
[TestMethod]
125+
public async Task ExpressiveDbSet_ToListAsync_DirectTerminal_Executes()
126+
{
127+
// Streaming terminal directly on the ExpressiveDbSet (no intervening operator to
128+
// re-wrap it). ToListAsync casts the source to IAsyncEnumerable<T>.
129+
var results = await Context.ExpressiveOrders.ToListAsync();
130+
131+
Assert.AreEqual(4, results.Count);
132+
}
133+
134+
[TestMethod]
135+
public async Task ExpressiveDbSet_ToArrayAsync_DirectTerminal_Executes()
136+
{
137+
var results = await Context.ExpressiveOrders.ToArrayAsync();
138+
139+
Assert.AreEqual(4, results.Length);
140+
}
141+
124142
[TestMethod]
125143
public async Task Include_ToListAsync_DirectTerminal_LoadsNavigation()
126144
{

0 commit comments

Comments
 (0)