You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Virtual and Polymorphic Members {#virtual-polymorphic-members}
115
+
116
+
Expression-tree expansion happens at **compile time** and works purely from the **static (declared) type** of each receiver. It has no runtime instance to inspect, so it cannot honor C# virtual dispatch.
117
+
118
+
If you mark a `virtual`, `abstract`, or `override` member `[Expressive]` (a default interface member counts too -- it is implicitly virtual), the generator reports [EXP0038](../reference/diagnostics#exp0038). When the member is expanded for a query provider (EF Core, MongoDB), the call is resolved against the declared type and the **base** body is always inlined -- an overridden body in a derived type is never used:
// The static type is Animal, so expansion inlines the BASE body -- even for Dog rows:
136
+
db.Animals.AsExpressive().Select(a=>a.Describe()); // => "Animal: {Name}" in SQL
137
+
```
138
+
139
+
This is by design: a query provider translates the expression to SQL/MQL and never materializes a CLR object, so there is no runtime type to dispatch on. (Contrast this with compiling the expression to a delegate and invoking it in memory, where the CLR *does* dispatch on the runtime type.)
140
+
141
+
### Recommended: test the runtime type explicitly
142
+
143
+
Branch on the concrete type so each arm has a statically-typed receiver. Every branch then expands to the correct body and the provider emits a `CASE`:
144
+
145
+
```csharp
146
+
db.Animals.AsExpressive().Select(a=>aswitch
147
+
{
148
+
Dogd=>d.Describe(), // expands Dog.Describe
149
+
_=>a.Describe(), // expands Animal.Describe
150
+
});
151
+
```
152
+
153
+
### Recommended: use a non-virtual static/extension method
154
+
155
+
Move the logic into a single non-virtual `[Expressive]` method that performs the type test itself. This keeps the polymorphic shape in one place and produces no EXP0038:
Declaring entity members `virtual` is common in EF Core because it enables lazy-loading proxies. That remains fine for plain navigation and scalar properties -- EXP0038 only concerns members you *also* mark `[Expressive]`.
173
+
:::
174
+
114
175
## Performance: First-Execution Overhead
115
176
116
177
`ExpandExpressives()` walks the expression tree and substitutes `[Expressive]` member references on every query execution. This adds a small cost to the first execution of each unique query shape. EF Core caches the compiled query afterward, so subsequent executions of the same shape skip the expansion entirely.
### EXP0038 -- Virtual member will not dispatch polymorphically {#exp0038}
704
+
705
+
**Severity:** Warning
706
+
**Category:** Design
707
+
708
+
**Message:**
709
+
```
710
+
[Expressive] member '{0}' is virtual, abstract, or an override. When it is expanded into an
711
+
expression tree (e.g. for EF Core or MongoDB), the call is resolved using the static (declared)
712
+
type, so an overridden body in a derived type is never used. Test the runtime type explicitly
713
+
(e.g. 'x switch { Derived d => d.Member, _ => x.Member }'), or move the logic into a non-virtual
714
+
[Expressive] static/extension method.
715
+
```
716
+
717
+
**Cause:** An `[Expressive]` member is declared `virtual`, `abstract`, or `override` (a default interface member counts -- it is implicitly virtual). Expression-tree expansion happens at compile time and only sees the **static (declared) type** of the receiver, so it cannot honor C# virtual dispatch. When the member is expanded for a query provider, the **base** body is always inlined; an overridden body in a derived type is never used.
718
+
719
+
This differs from compiling the expression to a delegate and invoking it in memory, where the CLR dispatches on the runtime type.
720
+
721
+
**Fix:** Branch on the runtime type so each arm has a statically-typed receiver, or move the logic into a single non-virtual `[Expressive]` static/extension method. See [Limitations: virtual and polymorphic members](../advanced/limitations#virtual-polymorphic-members) for full examples.
title:"[Expressive] member is virtual and will not dispatch polymorphically",
207
+
messageFormat:"[Expressive] member '{0}' is virtual, abstract, or an override. When it is expanded into an expression tree (e.g. for EF Core or MongoDB), the call is resolved using the static (declared) type, so an overridden body in a derived type is never used. Test the runtime type explicitly (e.g. 'x switch {{ Derived d => d.Member, _ => x.Member }}'), or move the logic into a non-virtual [Expressive] static/extension method.",
208
+
category:"Design",
209
+
DiagnosticSeverity.Warning,
210
+
isEnabledByDefault:true,
211
+
description:"Expression-tree expansion happens at compile time and only sees the static (declared) type of the receiver, so it cannot honor C# virtual dispatch. Declaring an [Expressive] member virtual/abstract/override therefore silently expands the base body for query providers. This differs from compiling the expression to a delegate and invoking it in memory, where the CLR dispatches on the runtime type.");
0 commit comments