I need to wrap an IQueryable<T> so that when the result is finally iterated I will have access to each element as it is returned.
To achieve this, I tried creating a QueryableWrapper<T> class which acts as a man-in-the-middle for the real queryable instance.
Then, when my repository is asked for its IQueryable<T> I returned this wrapper instead. This works fine until the consumer applies any further LINQ statements such as Where etc.
What I need to do is have my Repository intercept every item returned from the query as the consumer iterates it. This is so I can do the following
- Is the result a domain object?
- If so, then keep track of the object in case it is modified
I managed to get a bit further by also wrapping the result of Provider - but this crashes when the consumer does an OrderBy, because my result is not an IOrderedEnumerable.
I could implement this, but then I fear I am going down a rabbit hole, and there should be an easier way to achieve this.
internal class QueryableWrapper<T> : IQueryable<T>
{
private readonly IQueryable<T> Source;
private readonly IQueryProvider QueryProvider;
public QueryableWrapper(IQueryable<T> source)
{
CollectionName = collectionName;
DbContext = dbContext;
Source = source;
QueryProvider = new QueryProviderWrapper<T>(source.Provider);
}
public Type ElementType => Source.ElementType;
public Expression Expression => Source.Expression;
public IQueryProvider Provider => QueryProvider;
public IEnumerator<T> GetEnumerator() =>
new EnumeratorWrapper<T>(Source.GetEnumerator());
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
internal class EnumeratorWrapper<T> : IEnumerator<T>
{
private readonly IEnumerator<T> Source;
public EnumeratorWrapper(IEnumerator<T> source)
{
Source = source;
}
public T Current => Source.Current;
object? IEnumerator.Current => Current;
public void Dispose()
{
Source.Dispose();
}
public bool MoveNext()
{
bool result = Source.MoveNext();
if (result)
{
Console.WriteLine("Intercepted " + Source.Current.GetType().FullName);
}
return result;
}
public void Reset()
{
Source.Reset();
}
}
internal class QueryProviderWrapper<T> : IQueryProvider
{
private readonly IQueryProvider Source;
public QueryProviderWrapper(IQueryProvider source)
{
Source = source;
}
public IQueryable CreateQuery(Expression expression) => CreateQuery<T>(expression);
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
IQueryable<TElement> result = Source.CreateQuery<TElement>(expression);
return new QueryableWrapper<TElement>(source: result);
}
public object? Execute(Expression expression) => Execute<T>(expression);
public TResult Execute<TResult>(Expression expression) => Source.Execute<TResult>(expression);
}
}
internal class QueryableWrapper<T> : IQueryable<T>
{
private readonly IQueryable<T> Source;
public QueryableWrapper(IQueryable<T> source)
{
Source = source;
}
public Type ElementType => Source.ElementType;
public Expression Expression => Source.Expression;
public IQueryProvider Provider => Source.Provider;
public IEnumerator<T> GetEnumerator() =>
new EnumeratorWrapper<T>(Source.GetEnumerator());
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
internal class EnumeratorWrapper<T> : IEnumerator<T>
{
private readonly IEnumerator<T> Source;
public EnumeratorWrapper(IEnumerator<T> source)
{
Source = source;
}
public T Current => Source.Current;
object? IEnumerator.Current => Source.Current;
public void Dispose()
{
Source.Dispose();
}
public bool MoveNext()
{
bool result = Source.MoveNext();
if (result)
Console.WriteLine("Intercepted!");
return result;
}
public void Reset()
{
Source.Reset();
}
}