diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs
index bfaab69..84ac27b 100644
--- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs
+++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs
@@ -140,6 +140,16 @@ private static void AppendSanitizedTypeName(StringBuilder sb, string typeName)
continue;
}
+ // Skip the verbatim identifier prefix '@' — it is a C# syntactic escape for
+ // reserved keywords (e.g. '@event') and has no meaning at the CLR level.
+ // The CLR type name is just 'event', so stripping '@' keeps the generated name
+ // consistent with the runtime resolver's output.
+ if (typeName[i] == '@')
+ {
+ i++;
+ continue;
+ }
+
var c = typeName[i];
sb.Append(IsInvalidIdentifierChar(c) ? '_' : c);
i++;
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ExtensionMethodTests.ProjectableExtensionMethod_WithVerbatimKeywordParameterType.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ExtensionMethodTests.ProjectableExtensionMethod_WithVerbatimKeywordParameterType.verified.txt
new file mode 100644
index 0000000..39e2716
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ExtensionMethodTests.ProjectableExtensionMethod_WithVerbatimKeywordParameterType.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EventExtensions_GetId_P0_Foo_event
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.@event e) => e.Id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ExtensionMethodTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ExtensionMethodTests.cs
index b9bcaa7..4aaa91b 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ExtensionMethodTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ExtensionMethodTests.cs
@@ -105,6 +105,32 @@ static class C {
return Verifier.Verify(result.GeneratedTrees[0].ToString());
}
+ [Fact]
+ public Task ProjectableExtensionMethod_WithVerbatimKeywordParameterType()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class @event {
+ public int Id { get; set; }
+ }
+
+ static class EventExtensions {
+ [Projectable]
+ public static int GetId(this @event e) => e.Id;
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
[Fact]
public Task ProjectableExtensionMethod_WithExpressionPropertyBody()
{
diff --git a/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectionExpressionClassNameGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectionExpressionClassNameGeneratorTests.cs
index cf0df91..248ec57 100644
--- a/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectionExpressionClassNameGeneratorTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectionExpressionClassNameGeneratorTests.cs
@@ -50,6 +50,31 @@ public void GenerateName_WithGlobalPrefixInGenericArgs_StripsAllGlobalPrefixes(
Assert.Equal(expected, result);
}
+ ///
+ /// Verifies that the verbatim identifier prefix @ is stripped from parameter type
+ /// names. Roslyn's FullyQualifiedFormat includes it for types whose names are
+ /// reserved C# keywords (e.g. @event), but the CLR runtime name never includes
+ /// @ — so both sides must agree on the sanitised name.
+ ///
+ [Theory]
+ [InlineData(
+ "global::Foo.Storage.@event",
+ "ns_a_m_P0_Foo_Storage_event")]
+ [InlineData(
+ "@event",
+ "ns_a_m_P0_event")]
+ [InlineData(
+ "global::Foo.@delegate",
+ "ns_a_m_P0_Foo_delegate")]
+ public void GenerateName_WithVerbatimAtPrefixInParamType_StripsAtSign(
+ string paramTypeName, string expected)
+ {
+ var result = ProjectionExpressionClassNameGenerator.GenerateName(
+ "ns", new[] { "a" }, "m", new[] { paramTypeName });
+
+ Assert.Equal(expected, result);
+ }
+
[Fact]
public void GeneratedFullName()
{