diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index b17e7ce..a3e7d36 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -3,7 +3,10 @@
"image": "mcr.microsoft.com/devcontainers/dotnet:2-10.0-noble",
"features": {
"ghcr.io/devcontainers/features/node:1": {},
- "ghcr.io/devcontainers/features/docker-in-docker:2": {}
+ "ghcr.io/devcontainers/features/docker-in-docker:2": {
+ "dockerDashComposeVersion": "none",
+ "installDockerBuildx": false
+ }
},
"postCreateCommand": "dotnet restore"
}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ae75840..7febb0c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -169,3 +169,67 @@ jobs:
name: Container Test Results (${{ matrix.database }})
path: ./test-results/**/*.trx
reporter: dotnet-trx
+
+ mongodb-tests:
+ name: Container Tests (MongoDB)
+ runs-on: ubuntu-latest
+ needs: build-and-test
+ timeout-minutes: 10
+
+ env:
+ CI: true
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET SDKs
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 8.0.x
+ 9.0.x
+ 10.0.x
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: nuget-${{ runner.os }}-MongoDB-${{ hashFiles('**/*.csproj', 'Directory.Packages.props') }}
+ restore-keys: |
+ nuget-${{ runner.os }}-MongoDB-
+ nuget-${{ runner.os }}-
+
+ - name: Restore
+ run: >-
+ dotnet restore
+ tests/ExpressiveSharp.MongoDB.IntegrationTests/ExpressiveSharp.MongoDB.IntegrationTests.csproj
+
+ - name: Build
+ run: >-
+ dotnet build --no-restore -c Release
+ tests/ExpressiveSharp.MongoDB.IntegrationTests/ExpressiveSharp.MongoDB.IntegrationTests.csproj
+
+ - name: Test
+ run: >-
+ dotnet test --no-build -c Release
+ --project tests/ExpressiveSharp.MongoDB.IntegrationTests
+ --
+ --report-trx --report-trx-filename results.trx
+ --results-directory ./test-results
+
+ - name: Upload test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: container-test-results-MongoDB
+ path: ./test-results/**/*.trx
+ retention-days: 14
+
+ - name: Test report
+ if: always()
+ uses: dorny/test-reporter@v1
+ with:
+ name: Container Test Results (MongoDB)
+ path: ./test-results/**/*.trx
+ reporter: dotnet-trx
diff --git a/Directory.Packages.props b/Directory.Packages.props
index dc0adfb..29af0cf 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -24,5 +24,7 @@
+
+
diff --git a/ExpressiveSharp.slnx b/ExpressiveSharp.slnx
index 2cc5546..ce84c9b 100644
--- a/ExpressiveSharp.slnx
+++ b/ExpressiveSharp.slnx
@@ -15,11 +15,13 @@
+
+
diff --git a/src/ExpressiveSharp.MongoDB/ExpressiveMongoCollection.cs b/src/ExpressiveSharp.MongoDB/ExpressiveMongoCollection.cs
new file mode 100644
index 0000000..0a7b8c0
--- /dev/null
+++ b/src/ExpressiveSharp.MongoDB/ExpressiveMongoCollection.cs
@@ -0,0 +1,56 @@
+using ExpressiveSharp.MongoDB.Extensions;
+using ExpressiveSharp.Services;
+using MongoDB.Driver;
+
+namespace ExpressiveSharp.MongoDB;
+
+///
+/// A wrapper around that provides an
+/// for delegate-based LINQ queries
+/// with automatic [Expressive] member expansion.
+///
+///
+/// Analogous to ExpressiveDbSet<TEntity> in the EF Core integration.
+/// CRUD operations delegate directly to the inner collection.
+///
+///
+///
+/// var orders = new ExpressiveMongoCollection<Order>(collection);
+/// var results = await orders.AsQueryable()
+/// .Where(o => o.Customer?.Name == "Alice")
+/// .ToListAsync();
+///
+///
+public class ExpressiveMongoCollection
+{
+ private readonly IMongoCollection _inner;
+ private readonly ExpressiveOptions _options;
+
+ ///
+ /// Creates a new wrapping the specified collection.
+ ///
+ /// The underlying MongoDB collection.
+ ///
+ /// Optional controlling the transformer pipeline.
+ /// When null, is used.
+ ///
+ public ExpressiveMongoCollection(IMongoCollection inner, ExpressiveOptions? options = null)
+ {
+ _inner = inner ?? throw new ArgumentNullException(nameof(inner));
+ _options = options ?? MongoExpressiveOptions.CreateDefault();
+ }
+
+ ///
+ /// Gets the underlying for direct access
+ /// to non-LINQ operations (inserts, updates, deletes, aggregation pipeline, etc.).
+ ///
+ public IMongoCollection Inner => _inner;
+
+ ///
+ /// Returns an that supports delegate-based
+ /// LINQ with modern C# syntax and automatic [Expressive] expansion.
+ ///
+ /// Optional MongoDB aggregation options.
+ public IExpressiveMongoQueryable AsQueryable(AggregateOptions? aggregateOptions = null)
+ => _inner.AsExpressive(_options, aggregateOptions);
+}
diff --git a/src/ExpressiveSharp.MongoDB/ExpressiveSharp.MongoDB.csproj b/src/ExpressiveSharp.MongoDB/ExpressiveSharp.MongoDB.csproj
new file mode 100644
index 0000000..51d5e70
--- /dev/null
+++ b/src/ExpressiveSharp.MongoDB/ExpressiveSharp.MongoDB.csproj
@@ -0,0 +1,19 @@
+
+
+
+ MongoDB Driver integration for ExpressiveSharp — automatically expands [Expressive] members in MongoDB LINQ queries
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ExpressiveSharp.MongoDB/Extensions/ExpressiveQueryableMongoExtensions.cs b/src/ExpressiveSharp.MongoDB/Extensions/ExpressiveQueryableMongoExtensions.cs
new file mode 100644
index 0000000..84a8b7e
--- /dev/null
+++ b/src/ExpressiveSharp.MongoDB/Extensions/ExpressiveQueryableMongoExtensions.cs
@@ -0,0 +1,438 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq.Expressions;
+using ExpressiveSharp;
+using MongoDB.Driver.Linq;
+
+// ReSharper disable once CheckNamespace — intentionally in MongoDB.Driver namespace for discoverability
+namespace MongoDB.Driver;
+
+///
+/// Delegate-based async method stubs on for MongoDB
+/// async operations. These stubs are intercepted at compile time by the ExpressiveSharp
+/// source generator via and forwarded to
+/// extension methods with expression tree arguments.
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public static class ExpressiveQueryableMongoExtensions
+{
+ private const string InterceptedMessage =
+ "This method must be intercepted by the ExpressiveSharp source generator. " +
+ "Ensure the generator package is installed and the InterceptorsNamespaces MSBuild property is configured.";
+
+ // ── AnyAsync ────────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AnyAsync(
+ this IRewritableQueryable source,
+ Func predicate,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── CountAsync ──────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task CountAsync(
+ this IRewritableQueryable source,
+ Func predicate,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── LongCountAsync ─────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task LongCountAsync(
+ this IRewritableQueryable source,
+ Func predicate,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── FirstAsync / FirstOrDefaultAsync ────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task FirstAsync(
+ this IRewritableQueryable source,
+ Func predicate,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task FirstOrDefaultAsync(
+ this IRewritableQueryable source,
+ Func predicate,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── SingleAsync / SingleOrDefaultAsync ──────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SingleAsync(
+ this IRewritableQueryable source,
+ Func predicate,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SingleOrDefaultAsync(
+ this IRewritableQueryable source,
+ Func predicate,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── MinAsync / MaxAsync ─────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task MinAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task MaxAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── SumAsync (int) ──────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── SumAsync (nullable int) ─────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task SumAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── AverageAsync (double) ───────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── AverageAsync (nullable) ─────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task AverageAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── StandardDeviationPopulationAsync ─────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationPopulationAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ // ── StandardDeviationSampleAsync ─────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [PolyfillTarget(typeof(MongoQueryable))]
+ public static Task StandardDeviationSampleAsync(
+ this IRewritableQueryable source,
+ Func selector,
+ CancellationToken cancellationToken = default)
+ => throw new UnreachableException(InterceptedMessage);
+}
diff --git a/src/ExpressiveSharp.MongoDB/Extensions/MongoExpressiveExtensions.cs b/src/ExpressiveSharp.MongoDB/Extensions/MongoExpressiveExtensions.cs
new file mode 100644
index 0000000..72155fd
--- /dev/null
+++ b/src/ExpressiveSharp.MongoDB/Extensions/MongoExpressiveExtensions.cs
@@ -0,0 +1,61 @@
+using ExpressiveSharp.MongoDB.Infrastructure;
+using ExpressiveSharp.Services;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+
+namespace ExpressiveSharp.MongoDB.Extensions;
+
+///
+/// Entry-point extension methods for wrapping MongoDB queryables and collections
+/// with ExpressiveSharp expression expansion.
+///
+public static class MongoExpressiveExtensions
+{
+ ///
+ /// Wraps an backed by MongoDB's LINQ provider in an
+ /// that automatically expands
+ /// [Expressive] members before query execution.
+ ///
+ /// The MongoDB queryable source (typically from collection.AsQueryable()).
+ ///
+ /// Optional controlling the transformer pipeline.
+ /// When null, is used.
+ ///
+ public static IExpressiveMongoQueryable AsExpressive(
+ this IQueryable source,
+ ExpressiveOptions? options = null)
+ {
+ var mongoProvider = source.Provider as IMongoQueryProvider
+ ?? throw new ArgumentException(
+ "The source queryable's Provider must implement IMongoQueryProvider. " +
+ "Use collection.AsQueryable() to obtain a MongoDB-backed queryable.",
+ nameof(source));
+
+ var effectiveOptions = options ?? MongoExpressiveOptions.CreateDefault();
+ var provider = new ExpressiveMongoQueryProvider(mongoProvider, effectiveOptions);
+ return new ExpressiveMongoQueryable(source, provider);
+ }
+
+ ///
+ /// Creates an directly from an
+ /// , combining AsQueryable()
+ /// with ExpressiveSharp expression expansion.
+ ///
+ /// The MongoDB collection.
+ ///
+ /// Optional controlling the transformer pipeline.
+ /// When null, is used.
+ ///
+ /// Optional MongoDB aggregation options.
+ public static IExpressiveMongoQueryable AsExpressive(
+ this IMongoCollection collection,
+ ExpressiveOptions? options = null,
+ AggregateOptions? aggregateOptions = null)
+ {
+ var queryable = aggregateOptions is not null
+ ? collection.AsQueryable(aggregateOptions)
+ : collection.AsQueryable();
+
+ return queryable.AsExpressive(options);
+ }
+}
diff --git a/src/ExpressiveSharp.MongoDB/IExpressiveMongoQueryable.cs b/src/ExpressiveSharp.MongoDB/IExpressiveMongoQueryable.cs
new file mode 100644
index 0000000..1b45172
--- /dev/null
+++ b/src/ExpressiveSharp.MongoDB/IExpressiveMongoQueryable.cs
@@ -0,0 +1,17 @@
+namespace ExpressiveSharp.MongoDB;
+
+///
+/// Represents a MongoDB queryable with expression-rewrite support. Extends
+/// to enable delegate-based LINQ methods
+/// with modern C# syntax (e.g., ?., switch expressions, pattern matching)
+/// when querying MongoDB collections.
+///
+///
+/// In MongoDB.Driver v3, the driver's LINQ provider works through standard
+/// with an .
+/// This interface marks a queryable whose provider is an
+/// that automatically expands [Expressive] members before MongoDB translates the query.
+///
+public interface IExpressiveMongoQueryable : IRewritableQueryable
+{
+}
diff --git a/src/ExpressiveSharp.MongoDB/Infrastructure/ExpressiveMongoQueryProvider.cs b/src/ExpressiveSharp.MongoDB/Infrastructure/ExpressiveMongoQueryProvider.cs
new file mode 100644
index 0000000..5f031c7
--- /dev/null
+++ b/src/ExpressiveSharp.MongoDB/Infrastructure/ExpressiveMongoQueryProvider.cs
@@ -0,0 +1,59 @@
+using System.Linq.Expressions;
+using ExpressiveSharp.Extensions;
+using ExpressiveSharp.Services;
+using MongoDB.Bson;
+using MongoDB.Driver.Linq;
+
+namespace ExpressiveSharp.MongoDB.Infrastructure;
+
+///
+/// Decorates MongoDB's to automatically expand
+/// member references before query execution.
+///
+///
+/// returns an
+/// wrapper so that chained operations continue to use this provider.
+/// and call
+///
+/// on the expression before delegating to the inner provider.
+///
+internal sealed class ExpressiveMongoQueryProvider : IMongoQueryProvider
+{
+ private readonly IMongoQueryProvider _inner;
+ private readonly ExpressiveOptions _options;
+
+ public ExpressiveMongoQueryProvider(IMongoQueryProvider inner, ExpressiveOptions options)
+ {
+ _inner = inner;
+ _options = options;
+ }
+
+ public BsonDocument[] LoggedStages => _inner.LoggedStages;
+
+ public IQueryable CreateQuery(Expression expression)
+ {
+ var inner = _inner.CreateQuery(expression);
+ // Wrap in our queryable to maintain provider chain
+ var elementType = inner.ElementType;
+ var wrapperType = typeof(ExpressiveMongoQueryable<>).MakeGenericType(elementType);
+ return (IQueryable)Activator.CreateInstance(wrapperType, inner, this)!;
+ }
+
+ public IQueryable CreateQuery(Expression expression)
+ {
+ var inner = _inner.CreateQuery(expression);
+ return new ExpressiveMongoQueryable(inner, this);
+ }
+
+ public object? Execute(Expression expression)
+ => _inner.Execute(Expand(expression));
+
+ public TResult Execute(Expression expression)
+ => _inner.Execute(Expand(expression));
+
+ public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken = default)
+ => _inner.ExecuteAsync(Expand(expression), cancellationToken);
+
+ private Expression Expand(Expression expression)
+ => expression.ExpandExpressives(_options);
+}
diff --git a/src/ExpressiveSharp.MongoDB/Infrastructure/ExpressiveMongoQueryable.cs b/src/ExpressiveSharp.MongoDB/Infrastructure/ExpressiveMongoQueryable.cs
new file mode 100644
index 0000000..e1ec2b5
--- /dev/null
+++ b/src/ExpressiveSharp.MongoDB/Infrastructure/ExpressiveMongoQueryable.cs
@@ -0,0 +1,34 @@
+using System.Collections;
+using System.Linq.Expressions;
+
+namespace ExpressiveSharp.MongoDB.Infrastructure;
+
+///
+/// Internal wrapper that adapts an backed by MongoDB's LINQ provider
+/// to , enabling delegate-based LINQ overloads
+/// with modern C# syntax via source generator interception.
+///
+///
+/// Also implements so that ThenBy/ThenByDescending
+/// interceptors can cast the wrapper without a runtime exception.
+///
+internal sealed class ExpressiveMongoQueryable : IExpressiveMongoQueryable, IOrderedQueryable
+{
+ private readonly IQueryable _source;
+ private readonly ExpressiveMongoQueryProvider _provider;
+
+ public ExpressiveMongoQueryable(IQueryable source, ExpressiveMongoQueryProvider provider)
+ {
+ _source = source ?? throw new ArgumentNullException(nameof(source));
+ _provider = provider ?? throw new ArgumentNullException(nameof(provider));
+ }
+
+ public Type ElementType => _source.ElementType;
+ public Expression Expression => _source.Expression;
+ public IQueryProvider Provider => _provider;
+
+ public IEnumerator GetEnumerator()
+ => _provider.Execute>(Expression).GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+}
diff --git a/src/ExpressiveSharp.MongoDB/MongoExpressiveOptions.cs b/src/ExpressiveSharp.MongoDB/MongoExpressiveOptions.cs
new file mode 100644
index 0000000..825d677
--- /dev/null
+++ b/src/ExpressiveSharp.MongoDB/MongoExpressiveOptions.cs
@@ -0,0 +1,28 @@
+using ExpressiveSharp.Services;
+using ExpressiveSharp.Transformers;
+
+namespace ExpressiveSharp.MongoDB;
+
+///
+/// Factory for creating an pre-configured with
+/// transformers suitable for MongoDB's LINQ provider.
+///
+public static class MongoExpressiveOptions
+{
+ ///
+ /// Creates an with the default transformer pipeline
+ /// for MongoDB queries. Matches the transformers used by the EF Core integration.
+ ///
+ public static ExpressiveOptions CreateDefault()
+ {
+ var options = new ExpressiveOptions();
+ options.AddTransformers(
+ new ReplaceThrowWithDefault(),
+ new ConvertLoopsToLinq(),
+ new RemoveNullConditionalPatterns(),
+ new FlattenTupleComparisons(),
+ new FlattenConcatArrayCalls(),
+ new FlattenBlockExpressions());
+ return options;
+ }
+}
diff --git a/tests/ExpressiveSharp.MongoDB.IntegrationTests/ExpressiveSharp.MongoDB.IntegrationTests.csproj b/tests/ExpressiveSharp.MongoDB.IntegrationTests/ExpressiveSharp.MongoDB.IntegrationTests.csproj
new file mode 100644
index 0000000..b8266d9
--- /dev/null
+++ b/tests/ExpressiveSharp.MongoDB.IntegrationTests/ExpressiveSharp.MongoDB.IntegrationTests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ false
+ true
+
+
+ $(NoWarn);NU1903;NU1902;NU1901;NU1904
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/ExpressiveSharp.MongoDB.IntegrationTests/Infrastructure/MongoContainerFixture.cs b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Infrastructure/MongoContainerFixture.cs
new file mode 100644
index 0000000..41fe8ea
--- /dev/null
+++ b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Infrastructure/MongoContainerFixture.cs
@@ -0,0 +1,50 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Testcontainers.MongoDb;
+
+namespace ExpressiveSharp.MongoDB.IntegrationTests.Infrastructure;
+
+[TestClass]
+public static class MongoContainerFixture
+{
+ public static bool IsDockerAvailable { get; private set; }
+ public static string? ConnectionString { get; private set; }
+
+ private static MongoDbContainer? _container;
+
+ [AssemblyInitialize]
+ public static async Task InitializeAsync(TestContext _)
+ {
+ if (!DetectDocker())
+ {
+ IsDockerAvailable = false;
+ return;
+ }
+
+ IsDockerAvailable = true;
+ _container = new MongoDbBuilder().Build();
+ await _container.StartAsync();
+ ConnectionString = _container.GetConnectionString();
+ }
+
+ [AssemblyCleanup]
+ public static async Task CleanupAsync()
+ {
+ if (_container is not null)
+ await _container.DisposeAsync();
+ }
+
+ private static bool DetectDocker()
+ {
+ try
+ {
+ using var config = new Docker.DotNet.DockerClientConfiguration();
+ using var client = config.CreateClient();
+ client.System.PingAsync().GetAwaiter().GetResult();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+}
diff --git a/tests/ExpressiveSharp.MongoDB.IntegrationTests/Infrastructure/MongoTestBase.cs b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Infrastructure/MongoTestBase.cs
new file mode 100644
index 0000000..fcaeccc
--- /dev/null
+++ b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Infrastructure/MongoTestBase.cs
@@ -0,0 +1,108 @@
+using ExpressiveSharp.IntegrationTests.Scenarios.Store;
+using ExpressiveSharp.IntegrationTests.Scenarios.Store.Models;
+using ExpressiveSharp.MongoDB.Extensions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MongoDB.Driver;
+
+namespace ExpressiveSharp.MongoDB.IntegrationTests.Infrastructure;
+
+///
+/// Base class for all MongoDB integration tests. Creates a unique database per test,
+/// seeds embedded Order documents, and drops the database on cleanup.
+///
+public abstract class MongoTestBase
+{
+ protected IMongoDatabase Database { get; private set; } = null!;
+ protected IMongoCollection Orders { get; private set; } = null!;
+ protected IExpressiveMongoQueryable Query { get; private set; } = null!;
+
+ private MongoClient? _client;
+ private string? _dbName;
+
+ [TestInitialize]
+ public async Task InitMongo()
+ {
+ if (!MongoContainerFixture.IsDockerAvailable)
+ Assert.Inconclusive("Docker not available");
+
+ _client = new MongoClient(MongoContainerFixture.ConnectionString);
+ _dbName = $"test_{Guid.NewGuid():N}";
+ Database = _client.GetDatabase(_dbName);
+ Orders = Database.GetCollection("orders");
+ Query = Orders.AsExpressive();
+
+ await SeedDataAsync();
+ }
+
+ [TestCleanup]
+ public async Task CleanupMongo()
+ {
+ if (_client is not null && _dbName is not null)
+ await _client.DropDatabaseAsync(_dbName);
+ }
+
+ ///
+ /// Seeds the database with embedded Order documents.
+ /// Customer and Address are embedded within each Order (document model).
+ ///
+ protected virtual async Task SeedDataAsync()
+ {
+ var addressLookup = SeedData.Addresses.ToDictionary(a => a.Id);
+ var customerLookup = SeedData.Customers.ToDictionary(c => c.Id);
+ var lineItemsByOrder = SeedData.LineItems
+ .GroupBy(li => li.OrderId)
+ .ToDictionary(g => g.Key, g => g.ToList());
+
+ var orders = new List();
+
+ foreach (var order in SeedData.Orders)
+ {
+ var mongoOrder = new Order
+ {
+ Id = order.Id,
+ Tag = order.Tag,
+ Price = order.Price,
+ Quantity = order.Quantity,
+ Status = order.Status,
+ CustomerId = order.CustomerId,
+ };
+
+ if (order.CustomerId is { } customerId && customerLookup.TryGetValue(customerId, out var customer))
+ {
+ mongoOrder.Customer = new Customer
+ {
+ Id = customer.Id,
+ Name = customer.Name,
+ Email = customer.Email,
+ AddressId = customer.AddressId,
+ };
+
+ if (customer.AddressId is { } addressId && addressLookup.TryGetValue(addressId, out var address))
+ {
+ mongoOrder.Customer.Address = new Address
+ {
+ Id = address.Id,
+ City = address.City,
+ Country = address.Country,
+ };
+ }
+ }
+
+ if (lineItemsByOrder.TryGetValue(order.Id, out var items))
+ {
+ mongoOrder.Items = items.Select(li => new LineItem
+ {
+ Id = li.Id,
+ OrderId = li.OrderId,
+ ProductName = li.ProductName,
+ UnitPrice = li.UnitPrice,
+ Quantity = li.Quantity,
+ }).ToList();
+ }
+
+ orders.Add(mongoOrder);
+ }
+
+ await Orders.InsertManyAsync(orders);
+ }
+}
diff --git a/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/AsyncMethodTests.cs b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/AsyncMethodTests.cs
new file mode 100644
index 0000000..e46cdfe
--- /dev/null
+++ b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/AsyncMethodTests.cs
@@ -0,0 +1,105 @@
+using ExpressiveSharp.IntegrationTests.Scenarios.Store.Models;
+using ExpressiveSharp.MongoDB.IntegrationTests.Infrastructure;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+
+namespace ExpressiveSharp.MongoDB.IntegrationTests.Tests;
+
+///
+/// Verifies MongoDB-specific async methods work through delegate-based stubs
+/// with [PolyfillTarget(typeof(MongoQueryable))].
+///
+[TestClass]
+public class AsyncMethodTests : MongoTestBase
+{
+ [TestMethod]
+ public async Task AnyAsync_WithDelegatePredicate_Executes()
+ {
+ var result = await MongoQueryable.AnyAsync(
+ Query.Where(o => o.Price > 100));
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public async Task CountAsync_WithDelegatePredicate_Executes()
+ {
+ var result = await MongoQueryable.CountAsync(
+ Query.Where(o => o.Status == OrderStatus.Pending));
+ Assert.AreEqual(2, result);
+ }
+
+ [TestMethod]
+ public async Task FirstAsync_WithDelegatePredicate_Executes()
+ {
+ var result = await MongoQueryable.FirstAsync(
+ Query.Where(o => o.Id == 1));
+ Assert.AreEqual(1, result.Id);
+ Assert.AreEqual(120.0, result.Price);
+ }
+
+ [TestMethod]
+ public async Task FirstOrDefaultAsync_WithDelegatePredicate_ReturnsNull()
+ {
+ var result = await MongoQueryable.FirstOrDefaultAsync(
+ Query.Where(o => o.Id == 999));
+ Assert.IsNull(result);
+ }
+
+ [TestMethod]
+ public async Task SingleAsync_WithDelegatePredicate_Executes()
+ {
+ var result = await MongoQueryable.SingleAsync(
+ Query.Where(o => o.Id == 2));
+ Assert.AreEqual(2, result.Id);
+ }
+
+ [TestMethod]
+ public async Task SingleOrDefaultAsync_WithDelegatePredicate_ReturnsNull()
+ {
+ var result = await MongoQueryable.SingleOrDefaultAsync(
+ Query.Where(o => o.Id == 999));
+ Assert.IsNull(result);
+ }
+
+ [TestMethod]
+ public async Task SumAsync_WithSelector_Executes()
+ {
+ var result = await MongoQueryable.SumAsync(
+ Query.Select(o => (int)o.Price));
+ // 120 + 75 + 10 + 50 = 255
+ Assert.AreEqual(255, result);
+ }
+
+ [TestMethod]
+ public async Task MinAsync_WithSelector_Executes()
+ {
+ var result = await MongoQueryable.MinAsync(
+ Query.Select(o => o.Price));
+ Assert.AreEqual(10.0, result);
+ }
+
+ [TestMethod]
+ public async Task MaxAsync_WithSelector_Executes()
+ {
+ var result = await MongoQueryable.MaxAsync(
+ Query.Select(o => o.Price));
+ Assert.AreEqual(120.0, result);
+ }
+
+ [TestMethod]
+ public async Task AverageAsync_WithSelector_Executes()
+ {
+ var result = await MongoQueryable.AverageAsync(
+ Query.Select(o => o.Price));
+ // (120 + 75 + 10 + 50) / 4 = 63.75
+ Assert.AreEqual(63.75, result);
+ }
+
+ [TestMethod]
+ public async Task ToListAsync_Works()
+ {
+ var results = await MongoQueryable.ToListAsync(Query);
+ Assert.AreEqual(4, results.Count);
+ }
+}
diff --git a/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/EmbeddedDocumentTests.cs b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/EmbeddedDocumentTests.cs
new file mode 100644
index 0000000..102464e
--- /dev/null
+++ b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/EmbeddedDocumentTests.cs
@@ -0,0 +1,78 @@
+using System.Linq.Expressions;
+using ExpressiveSharp.Extensions;
+using ExpressiveSharp.IntegrationTests.Scenarios.Store.Models;
+using ExpressiveSharp.MongoDB.IntegrationTests.Infrastructure;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MongoDB.Driver.Linq;
+
+namespace ExpressiveSharp.MongoDB.IntegrationTests.Tests;
+
+///
+/// Verifies expressive features work with MongoDB's embedded document model.
+///
+[TestClass]
+public class EmbeddedDocumentTests : MongoTestBase
+{
+ [TestMethod]
+ public async Task ExpressiveMember_OnEmbeddedDocument_Expands()
+ {
+ // CustomerName is [Expressive] => Customer?.Name
+ // Customer is an embedded document in MongoDB
+ Expression> expr = o => o.CustomerName;
+ var expanded = (Expression>)expr.ExpandExpressives();
+
+ var results = await Query
+ .Where(o => o.Customer != null)
+ .OrderBy(o => o.Id)
+ .Select(expanded)
+ .ToListAsync();
+
+ Assert.AreEqual(3, results.Count);
+ Assert.AreEqual("Alice", results[0]);
+ Assert.AreEqual("Bob", results[1]);
+ Assert.IsNull(results[2]); // Customer exists but Name is null
+ }
+
+ [TestMethod]
+ public async Task NullConditional_ThroughEmbeddedDocument_Works()
+ {
+ // CustomerCountry is [Expressive] => Customer?.Address?.Country
+ // Two levels of embedded document navigation
+ Expression> expr = o => o.CustomerCountry;
+ var expanded = (Expression>)expr.ExpandExpressives();
+
+ var results = await Query
+ .OrderBy(o => o.Id)
+ .Select(expanded)
+ .ToListAsync();
+
+ Assert.AreEqual("US", results[0]); // Order 1: Alice → New York, US
+ Assert.AreEqual("UK", results[1]); // Order 2: Bob → London, UK
+ Assert.IsNull(results[2]); // Order 3: no customer
+ Assert.IsNull(results[3]); // Order 4: customer has no address
+ }
+
+ [TestMethod]
+ public async Task Query_EmbeddedCustomer_ByName()
+ {
+ var results = await Query
+ .Where(o => o.Customer != null && o.Customer.Name == "Alice")
+ .Select(o => o.Id)
+ .ToListAsync();
+
+ Assert.AreEqual(1, results.Count);
+ Assert.AreEqual(1, results[0]);
+ }
+
+ [TestMethod]
+ public async Task Query_EmbeddedAddress_ByCity()
+ {
+ var results = await Query
+ .Where(o => o.Customer != null && o.Customer.Address != null && o.Customer.Address.City == "London")
+ .Select(o => o.Id)
+ .ToListAsync();
+
+ Assert.AreEqual(1, results.Count);
+ Assert.AreEqual(2, results[0]);
+ }
+}
diff --git a/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ExpressiveMongoCollectionTests.cs b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ExpressiveMongoCollectionTests.cs
new file mode 100644
index 0000000..06fe10a
--- /dev/null
+++ b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ExpressiveMongoCollectionTests.cs
@@ -0,0 +1,61 @@
+using ExpressiveSharp.IntegrationTests.Scenarios.Store.Models;
+using ExpressiveSharp.MongoDB.IntegrationTests.Infrastructure;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+
+namespace ExpressiveSharp.MongoDB.IntegrationTests.Tests;
+
+///
+/// Verifies the high-level wrapper
+/// correctly provides queryable access and delegates CRUD operations.
+///
+[TestClass]
+public class ExpressiveMongoCollectionTests : MongoTestBase
+{
+ [TestMethod]
+ public void AsQueryable_ReturnsExpressiveMongoQueryable()
+ {
+ var wrapper = new ExpressiveMongoCollection(Orders);
+ var queryable = wrapper.AsQueryable();
+ Assert.IsInstanceOfType>(queryable);
+ }
+
+ [TestMethod]
+ public async Task CrudOperations_DelegateToInner()
+ {
+ var wrapper = new ExpressiveMongoCollection(Orders);
+
+ // Insert via inner
+ var newOrder = new Order { Id = 100, Tag = "TEST", Price = 99.0, Quantity = 1, Status = OrderStatus.Approved };
+ await wrapper.Inner.InsertOneAsync(newOrder);
+
+ // Query via wrapper
+ var results = await MongoQueryable.ToListAsync(
+ wrapper.AsQueryable().Where(o => o.Id == 100));
+ Assert.AreEqual(1, results.Count);
+ Assert.AreEqual("TEST", results[0].Tag);
+
+ // Delete via inner
+ await wrapper.Inner.DeleteOneAsync(Builders.Filter.Eq(o => o.Id, 100));
+ var count = await MongoQueryable.CountAsync(
+ wrapper.AsQueryable().Where(o => o.Id == 100));
+ Assert.AreEqual(0, count);
+ }
+
+ [TestMethod]
+ public async Task AsQueryable_WithExpressiveExpansion_Works()
+ {
+ var wrapper = new ExpressiveMongoCollection(Orders);
+
+ // End-to-end: collection → queryable → Where(computed property) → ToListAsync
+ var results = await MongoQueryable.ToListAsync(
+ wrapper.AsQueryable()
+ .Where(o => o.Price > 50)
+ .OrderBy(o => o.Id));
+
+ Assert.AreEqual(2, results.Count);
+ Assert.AreEqual(1, results[0].Id);
+ Assert.AreEqual(2, results[1].Id);
+ }
+}
diff --git a/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ExpressiveMongoQueryProviderTests.cs b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ExpressiveMongoQueryProviderTests.cs
new file mode 100644
index 0000000..fed137d
--- /dev/null
+++ b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ExpressiveMongoQueryProviderTests.cs
@@ -0,0 +1,115 @@
+using System.Linq.Expressions;
+using ExpressiveSharp.IntegrationTests.Scenarios.Store.Models;
+using ExpressiveSharp.MongoDB.Extensions;
+using ExpressiveSharp.MongoDB.IntegrationTests.Infrastructure;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+using static ExpressiveSharp.Extensions.ExpressionExtensions;
+
+namespace ExpressiveSharp.MongoDB.IntegrationTests.Tests;
+
+///
+/// Verifies that correctly
+/// expands [Expressive] members before MongoDB's LINQ provider processes the query.
+///
+[TestClass]
+public class ExpressiveMongoQueryProviderTests : MongoTestBase
+{
+ [TestMethod]
+ public async Task Select_ExpressiveMember_ExpandsBeforeMongoTranslation()
+ {
+ // Total is [Expressive] => Price * Quantity
+ // Provider should expand before MongoDB sees it
+ var results = await Orders.AsQueryable()
+ .AsExpressive()
+ .Select(o => o.Total)
+ .ToListAsync();
+
+ // Order 1: 120*2=240, Order 2: 75*20=1500, Order 3: 10*3=30, Order 4: 50*5=250
+ CollectionAssert.AreEquivalent(new[] { 240.0, 1500.0, 30.0, 250.0 }, results);
+ }
+
+ [TestMethod]
+ public async Task Where_ExpressiveMember_ExpandsInPredicate()
+ {
+ Expression> predicate = o => o.Total > 200;
+ var expanded = (Expression>)predicate.ExpandExpressives();
+
+ var results = await Orders.AsQueryable()
+ .AsExpressive()
+ .Where(expanded)
+ .OrderBy(o => o.Id)
+ .Select(o => o.Id)
+ .ToListAsync();
+
+ // Totals: 240, 1500, 30, 250 → >200 keeps orders 1, 2, 4
+ CollectionAssert.AreEqual(new[] { 1, 2, 4 }, results);
+ }
+
+ [TestMethod]
+ public async Task OrderBy_ExpressiveMember_ExpandsInSort()
+ {
+ Expression> totalExpr = o => o.Total;
+ var expanded = (Expression>)totalExpr.ExpandExpressives();
+
+ var results = await Orders.AsQueryable()
+ .AsExpressive()
+ .OrderBy(expanded)
+ .Select(o => o.Id)
+ .ToListAsync();
+
+ // Totals: 30(3), 240(1), 250(4), 1500(2)
+ CollectionAssert.AreEqual(new[] { 3, 1, 4, 2 }, results);
+ }
+
+ [TestMethod]
+ public async Task ExpressiveFor_StaticMethod_ExpandsCorrectly()
+ {
+ Expression> expr = o => PricingUtils.Clamp(o.Price, 20.0, 100.0);
+ var expanded = (Expression>)expr.ExpandExpressives();
+
+ var results = await Orders.AsQueryable()
+ .AsExpressive()
+ .OrderBy(o => o.Id)
+ .Select(expanded)
+ .ToListAsync();
+
+ // Prices: 120→100, 75→75, 10→20, 50→50
+ CollectionAssert.AreEqual(new[] { 100.0, 75.0, 20.0, 50.0 }, results);
+ }
+
+ [TestMethod]
+ public async Task NestedExpressive_ExpandsRecursively()
+ {
+ // PricingUtils.Clamp is [ExpressiveFor], Total is [Expressive]
+ Expression> expr = o => PricingUtils.Clamp(o.Total, 0.0, 200.0);
+ var expanded = (Expression>)expr.ExpandExpressives();
+
+ var results = await Orders.AsQueryable()
+ .AsExpressive()
+ .OrderBy(o => o.Id)
+ .Select(expanded)
+ .ToListAsync();
+
+ // Totals: 240→200, 1500→200, 30→30, 250→200
+ CollectionAssert.AreEqual(new[] { 200.0, 200.0, 30.0, 200.0 }, results);
+ }
+
+ [TestMethod]
+ public async Task CapturedVariable_WithExpressive_BothResolve()
+ {
+ var minTotal = 200.0;
+ Expression> expr = o => o.Total > minTotal;
+ var expanded = (Expression>)expr.ExpandExpressives();
+
+ var results = await Orders.AsQueryable()
+ .AsExpressive()
+ .Where(expanded)
+ .OrderBy(o => o.Id)
+ .Select(o => o.Id)
+ .ToListAsync();
+
+ CollectionAssert.AreEqual(new[] { 1, 2, 4 }, results);
+ }
+}
diff --git a/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ExpressiveMongoQueryableTests.cs b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ExpressiveMongoQueryableTests.cs
new file mode 100644
index 0000000..486b1a5
--- /dev/null
+++ b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ExpressiveMongoQueryableTests.cs
@@ -0,0 +1,73 @@
+using ExpressiveSharp.IntegrationTests.Scenarios.Store.Models;
+using ExpressiveSharp.MongoDB.Extensions;
+using ExpressiveSharp.MongoDB.Infrastructure;
+using ExpressiveSharp.MongoDB.IntegrationTests.Infrastructure;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+
+namespace ExpressiveSharp.MongoDB.IntegrationTests.Tests;
+
+///
+/// Verifies that the wrapper correctly
+/// implements the expected interfaces and preserves MongoDB capabilities.
+///
+[TestClass]
+public class ExpressiveMongoQueryableTests : MongoTestBase
+{
+ [TestMethod]
+ public void AsExpressive_ReturnsIExpressiveMongoQueryable()
+ {
+ var result = Orders.AsExpressive();
+ Assert.IsInstanceOfType>(result);
+ }
+
+ [TestMethod]
+ public void AsExpressive_FromCollection_CreatesQueryable()
+ {
+ var result = Orders.AsExpressive();
+ Assert.IsNotNull(result);
+ Assert.IsInstanceOfType>(result);
+ }
+
+ [TestMethod]
+ public void Provider_IsExpressiveMongoQueryProvider()
+ {
+ var result = Orders.AsExpressive();
+ Assert.IsInstanceOfType(result.Provider);
+ }
+
+ [TestMethod]
+ public void Expression_MatchesUnderlyingQueryable()
+ {
+ var baseline = Orders.AsQueryable();
+ var expressive = Orders.AsExpressive();
+
+ // Both should have the same root expression (ConstantExpression pointing to the collection)
+ Assert.AreEqual(baseline.Expression.NodeType, expressive.Expression.NodeType);
+ }
+
+ [TestMethod]
+ public async Task ChainedOperations_MaintainWrapperType()
+ {
+ // After Where/Select, the result should still go through our provider
+ var filtered = Query.Where(o => o.Price > 50);
+ Assert.IsInstanceOfType(filtered.Provider);
+
+ var results = await MongoQueryable.ToListAsync(filtered);
+ Assert.IsTrue(results.Count > 0);
+ }
+
+ [TestMethod]
+ public async Task ToCursorAsync_WorksThroughWrapper()
+ {
+ // MongoDB-specific: ToCursorAsync should work through the wrapper
+ using var cursor = await MongoQueryable.ToCursorAsync(Query);
+ var results = new List();
+ while (await cursor.MoveNextAsync())
+ {
+ results.AddRange(cursor.Current);
+ }
+ Assert.AreEqual(4, results.Count);
+ }
+}
diff --git a/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/PolyfillInterceptorTests.cs b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/PolyfillInterceptorTests.cs
new file mode 100644
index 0000000..d9653fa
--- /dev/null
+++ b/tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/PolyfillInterceptorTests.cs
@@ -0,0 +1,205 @@
+using ExpressiveSharp.Extensions;
+using ExpressiveSharp.IntegrationTests.Scenarios.Store.Models;
+using ExpressiveSharp.MongoDB.IntegrationTests.Infrastructure;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MongoDB.Driver.Linq;
+
+namespace ExpressiveSharp.MongoDB.IntegrationTests.Tests;
+
+///