-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
When looking up methods via reflection, be trimming/AOT-friendly #26288
Comments
I just got this exception when executing a self-contained, trimmed executable with EF Core 6.0.0:
I quickly realized it was because of trimming and I had to disable it. I see you explicitly mention |
@0xced yeah, this is one of the high-priority items for EF7 - stay tuned, it'll hopefully make it into an early preview. |
We ran in this issue, too when switching to .NET6 / EF6 with trimmed binaries on production systems (Docker Images). |
This issue is in the 7.0.0 milestone. There's little chance we'll be fixing this in 6.0.0, but we'll discuss that after the fix is done and the general trimming situation is clearer. |
EF6 is LTS so people will use it until 8. November 2024. In my opinion the trimming fix should be made in EF6 when it will be supported for such a long time. |
@seriouz @lechgu @EjaYF if someone can share a minimal, trimmed console application which works with EF Core 5.0 (i.e. executes some trivial query), that would help push this forward. I'm seeing various linker-related failures when doing that, since EF Core 5.0 (and 6.0) weren't annotated for trimming in any way. |
Never mind, I've managed to get a trimmed version working here. I'll update soon. |
FYI I've merged #27098 for 6.0.2 - that's a temporary fix which specifically fixes the Math-related errors; in my testing it allowed at least basic queries to run properly in trimmed applications. This isn't a guarantee that everything will work - just an attempt to unblock people for basic usage. I'm tackling trimming in a more serious way for 7.0, please report any further problems you run across. |
We currently indeed use reflection to look up MethodInfos; some of these patterns are understood by the .NET linker, which then refrains from trimming those methods. In effect, this means that EF's pipeline causes a lot more methods to get preserved, even if they never actually get used by user code, only because they could in theory be used and would need to be translated. This results in binaries that are bigger than they need to be. The alternative is to use string-based matching, where we look at the name of the method in the query pipline, and also at its DeclaringType. That refrains from directly referencing the method in a way that's visible to the linker. |
We can also make the code conditional on a feature switch |
@AndriySvyryd right.. The code in question (method/member translators) is part of the query pipeline, which indeed should not be compiled in when the user is using precompiled queries only. But when we enable dynamic queries (#29760), the query pipeline would need to be compiled in, and changing our method/member pattern matching to be string-based should allow unused methods to be trimmed away... Makes sense? |
@roji Are you saying that for the dynamic query support in NativeAOT we'll require some of the query pipeline, but we'd still not use any of these reflection objects? If so, we can add a more specific feature switch for AOT dynamic queries. |
Yeah, AFAICT dynamic queries will require the full query pipeline at runtime, exactly like a regular JIT application today. However, we don't want all methods that can potentially appear in a LINQ query to be preserved by the trimmer... For example, we can translate So this is a case where I don't think we need feature switches (and I don't see how they could scale here, for all the translatable .NET methods etc.) - we just need to make sure we don't (directly) reference things which aren't needed. |
Sure, but even if |
@AndriySvyryd I'm not 100% sure about all this, but here's a quick test: // Expression<Func<int, int>> expr = i => Foo.SomeMethod(i);
Reflect("Foo");
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2075")]
void Reflect(string typeName)
{
var type = Assembly.GetExecutingAssembly().GetType(typeName);
if (type is null)
{
throw new Exception($"Type {typeName} not found");
}
var method = type.GetMethods().FirstOrDefault(m => m.Name == "SomeMethod");
if (method is null)
{
throw new Exception("Method not found");
}
method.Invoke(null, [8]);
}
class Foo
{
public static int SomeMethod(int i)
{
Console.WriteLine("SomeMethod invoked: " + i);
return i * 2;
}
}
The moment you uncomment the first line, the program starts working correctly. In other words, AFAICT merely referencing some method/type inside an expression tree makes its Type, MethodInfo and even code get preserved (otherwise that expression tree would be totally invalid). So I think all this means that we should strive to not get translatable methods preserved in the query pipeline, and simply match by examining the incoming MethodInfos from the tree, without referencing any MethodInfos ourselves (since that would cause them to get preserved). If the user includes some method in some expression tree, it will get preserved and everything works; otherwise, it gets trimmed out and everything also works. Does this make sense, do you think I'm missing something? (BTW we could even go a step further and refrain from referencing types in the query pipelines, not just MethodInfos/MemberInfos. In other words, we could pattern match by the type's fully-qualified name, plus an assembly check - this would avoid preserving the Type as well when it's not needed.) |
The text was updated successfully, but these errors were encountered: