How can I find all the entity types referenced by the Expression
passed to IQueryExpressionInterceptor.QueryCompilationStarting
? Where in the expression tree should I look for them?
I've tried a variety of queries and examined the Expression
in the debugger. It seems that the entity types involved in the query always appear in the Type
properties of the expression and its recursive Arguments
, as themselves or as the type arguments to IQueryable
or IIncludableQueryable
. But I wonder if there are ways to construct a EF query where this would not be true.
Here are some of the query shapes I've tested. In every case, I see a series of nested MethodCallExpression
(a LINQ type) enclosing an EntityQueryRootExpression
(an EF type).
var first = await db.Foos.FirstAsync();
var name = await db.Foos.Select(f => f.Name).FirstAsync();
var foosWithBars = await db.Foos.Include(f => f.Bars).ToListAsync();
var bars = await db.Foos
.Where(f => f.FooId == 1)
.SelectMany(f => f.Bars)
.ToListAsync();
var firstBar = await db.Foos
.OrderBy(f => f.FooId)
.Select(f => f.Bars.First())
.FirstAsync();
Consider focusing only on QueryRootExpression
and MemberExpression
, as they contain sufficient information about which entities are involved in a LINQ query.
Below is a straightforward ExpressionVisitor
implementation that gathers these entity types:
Usage:
var visitor = new EntitiesCollectorVisitor(db.Model);
visitor.Visit(queryExpression);
foreach (var entityType in visitor.Entities)
{
Console.WriteLine(entityType.ClrType.Name);
}
Implementation:
public class EntitiesCollectorVisitor : ExpressionVisitor
{
private readonly IModel _model;
public HashSet<IEntityType> Entities { get; } = new();
public EntitiesCollectorVisitor(IModel model)
{
_model = model;
}
void CollectTypes(Type type)
{
var entityType = _model.FindEntityType(type);
if (entityType != null)
{
Entities.Add(entityType);
}
else if (type.IsGenericType)
{
foreach (var genericArg in type.GenericTypeArguments)
CollectTypes(genericArg);
}
}
protected override Expression VisitMember(MemberExpression node)
{
CollectTypes(node.Type);
return base.VisitMember(node);
}
protected override Expression VisitExtension(Expression node)
{
if (node is QueryRootExpression rootExpression)
{
CollectTypes(rootExpression.ElementType);
}
return base.VisitExtension(node);
}
}