-
Type:
Improvement
-
Resolution: Done
-
Priority:
Unknown
-
None
-
Affects Version/s: None
-
Component/s: None
-
None
-
None
-
Dotnet Drivers
-
None
-
None
-
None
-
None
-
None
-
None
Description
There is currently no supported way to get from a MongoDB IQueryable (or the public IMongoQueryProvider interface) back to the IMongoClient that owns it. The internal MongoQueryProvider<TDocument> holds the source collection/database (and therefore the client), but none of it is reachable through public API — the public IMongoQueryProvider only exposes LoggedStages/ExecuteAsync, and the source metadata (CollectionNamespace, etc.) lives on the internal IMongoQueryProviderInternal.
Frameworks and middleware that operate on a Mongo IQueryable they did not construct (query-layer integrations such as OData, diagnostic/telemetry code, session/transaction enlistment) need to identify the queryable's client. Today the only option is reflecting into driver internals.
Motivation / concrete consumer
mongo-aspnetcore-odata (ODATA-38) needs the owning IMongoClient to call IMongoClient.AppendMetadata(LibraryInfo) and tag the driver handshake with the OData library name/version. Its current workaround reflects MongoQueryProvider<T>.Collection -> IMongoCollection<T>.Database -> Client. This is fragile in two ways:
- It couples to internal member names.
- It builds IMongoCollection<ElementType> from queryable.ElementType, which is wrong whenever the queryable has been projected/joined so that ElementType != TDocument (e.g. a GroupJoin). Because IMongoCollection<T> is invariant, invoking the getter throws TargetException, and the telemetry is silently lost for those queries.
Proposed API
An additive extension method (non-breaking), in MongoDB.Driver.Linq, alongside the existing IQueryableExtensions.GetMongoQueryProvider and IQueryableExtensions.GetLoggedStages (which it directly mirrors):
namespace MongoDB.Driver.Linq; public static partial class IQueryableExtensions { // Returns the IMongoClient that owns the source queryable's collection/database. public static IMongoClient GetClient(this IQueryable source); }
Usage from a consumer:
var client = queryable.GetClient();
Requirements
- Implemented via direct access to the internal provider state (no reflection), maintained alongside MongoQueryProvider<T>. Unlike GetLoggedStages, it cannot read from the public IMongoQueryProvider interface (we are deliberately not adding an interface member), so it casts to the internal provider and reads the internal field.
- Returns the owning client for both provider construction paths — collection-based queryables and database-level aggregation queryables.
- Correct when the queryable has been transformed (Select, GroupJoin, GroupBy, ...) such that the element type differs from the collection's document type.
- Returns the same IMongoClient instance that created the collection/database.
- Non-null: an IMongoQueryProvider always has a resolvable client, so no Try/null semantics are needed. Consistent with GetMongoQueryProvider/GetLoggedStages, it throws ArgumentException for a non-Mongo IQueryable.
Versioning / backport
Purely additive (extension method, no interface change), so it is not a breaking change. Implement on main (4.x) and backport to the next 3.x minor. A public Client member on the IMongoQueryProvider interface was considered but rejected: it would be a breaking change (DIMs are not viable across the driver's net472/netstandard2.1 targets), and it benefits only external implementers of the interface, of which there are effectively none. It was also considered whether to place the method on a new IMongoQueryProviderExtensions; the existing IQueryableExtensions was chosen for consistency with GetMongoQueryProvider/GetLoggedStages and because callers hold an IQueryable, not a provider.
Acceptance criteria
- queryable.GetClient() returns the owning client for a plain collection.AsQueryable().
- Same for a projected/joined queryable where ElementType != TDocument (regression case from ODATA-38 CountriesController, which uses GroupJoin).
- Same for a database-level aggregation queryable.
- Returned instance is reference-equal to the client used to create the collection/database.
- Throws ArgumentException for a non-Mongo IQueryable (consistent with existing IQueryableExtensions).
References
ODATA-38; mongo-aspnetcore-odata PR #31 (interim reflection-based ClientMetadata.TryGetClient, to be replaced by this API once the 3.x backport ships).
- blocks
-
ODATA-38 Add metadata to instantiated MongoClient
-
- Blocked
-