diff --git a/src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs b/src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs
index bc86a95..5f974c2 100644
--- a/src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs
+++ b/src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs
@@ -1,12 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Reflection.Metadata;
-using System.Runtime.CompilerServices;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
-using System.Threading.Tasks;
+using System.Reflection;
namespace EntityFrameworkCore.Projectables.Extensions
{
@@ -137,13 +129,32 @@ public static PropertyInfo GetImplementingProperty(this Type derivedType, Proper
return propertyInfo;
}
- var derivedProperties = derivedType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ var implementingType = implementingAccessor.DeclaringType
+ // This should only be null if it is a property accessor on the global module,
+ // which should never happen since we found it from derivedType
+ ?? throw new ApplicationException("The property accessor has no declaring type!");
+
+ var derivedProperties = implementingType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
return derivedProperties.First(propertyInfo.GetMethod == accessor
- ? p => p.GetMethod == implementingAccessor
- : p => p.SetMethod == implementingAccessor);
+ ? p => MethodInfosEqual(p.GetMethod, implementingAccessor)
+ : p => MethodInfosEqual(p.SetMethod, implementingAccessor));
}
+ ///
+ /// The built-in
+ /// does not work if the s don't agree.
+ ///
+ private static bool MethodInfosEqual(MethodInfo? first, MethodInfo second)
+ => first?.ReflectedType == second.ReflectedType
+ ? first == second
+ : first is not null
+ && first.DeclaringType == second.DeclaringType
+ && first.Name == second.Name
+ && first.GetParameters().Select(p => p.ParameterType)
+ .SequenceEqual(second.GetParameters().Select(p => p.ParameterType))
+ && first.GetGenericArguments().SequenceEqual(second.GetGenericArguments());
+
public static MethodInfo GetConcreteMethod(this Type derivedType, MethodInfo methodInfo)
=> methodInfo.DeclaringType?.IsInterface == true
? derivedType.GetImplementingMethod(methodInfo)
diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs
index 1c63e77..fd253df 100644
--- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs
+++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs
@@ -55,7 +55,7 @@ static string GenerateNameImpl(StringBuilder stringBuilder, string? namespaceNam
}
}
- stringBuilder.Append(memberName);
+ stringBuilder.Append(memberName.Replace(".", "__")); // Support explicit interface implementations
if (arity > 0)
{
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.verified.txt
new file mode 100644
index 0000000..a473c5f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.verified.txt
@@ -0,0 +1,2 @@
+SELECT [c].[Id] * 2
+FROM [Concrete] AS [c]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.verified.txt
new file mode 100644
index 0000000..03753d3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.verified.txt
@@ -0,0 +1,2 @@
+SELECT 49
+FROM [Concrete] AS [c]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.verified.txt
new file mode 100644
index 0000000..5c98c81
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.verified.txt
@@ -0,0 +1,2 @@
+SELECT [o].[Id] / 2
+FROM [OtherConcrete] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.cs
index d2a7f34..524c17d 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.cs
@@ -53,7 +53,21 @@ public abstract class Base : IBase
public virtual int SampleMethod() => 0;
}
- public class Concrete : Base
+ public interface IDefaultBase
+ {
+ int Explicit { get; }
+
+ [Projectable]
+ int Default => 49;
+ }
+
+ public interface IDefaultBaseImplementation : IDefaultBase, IBase
+ {
+ [Projectable]
+ int IDefaultBase.Explicit => Id * 2;
+ }
+
+ public class Concrete : Base, IDefaultBaseImplementation
{
[Projectable]
public override int SampleProperty => 1;
@@ -62,6 +76,12 @@ public class Concrete : Base
public override int SampleMethod() => 1;
}
+ public class OtherConcrete : Base, IDefaultBase
+ {
+ [Projectable]
+ int IDefaultBase.Explicit => Id / 2;
+ }
+
public class MoreConcrete : Concrete
{
}
@@ -130,6 +150,36 @@ public Task ProjectOverImplementedMethod()
return Verifier.Verify(query.ToQueryString());
}
+ [Fact]
+ public Task ProjectOverDefaultImplementedProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set().SelectDefaultProperty();
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ProjectOverExplicitlyImplementedProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set().SelectExplicitProperty();
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ProjectOverDefaultExplicitlyImplementedProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set().SelectExplicitProperty();
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
[Fact]
public Task ProjectOverProvider()
{
@@ -156,6 +206,14 @@ public static class ModelExtensions
public static IQueryable SelectComputedProperty(this IQueryable concretes)
where TConcrete : InheritedModelTests.IBase
=> concretes.Select(x => x.ComputedProperty);
+
+ public static IQueryable SelectDefaultProperty(this IQueryable concretes)
+ where TConcrete : InheritedModelTests.IDefaultBase
+ => concretes.Select(x => x.Default);
+
+ public static IQueryable SelectExplicitProperty(this IQueryable concretes)
+ where TConcrete : InheritedModelTests.IDefaultBase
+ => concretes.Select(x => x.Explicit);
public static IQueryable SelectComputedMethod(this IQueryable concretes)
where TConcrete : InheritedModelTests.IBase
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultExplicitInterfaceMember.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultExplicitInterfaceMember.verified.txt
new file mode 100644
index 0000000..f4fc80e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultExplicitInterfaceMember.verified.txt
@@ -0,0 +1,16 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class _IDefaultBaseImplementation_IDefaultBase__Default
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::IDefaultBaseImplementation @this) => @this.ComputedProperty * 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultInterfaceMember.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultInterfaceMember.verified.txt
new file mode 100644
index 0000000..8bf6f22
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultInterfaceMember.verified.txt
@@ -0,0 +1,16 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class _IDefaultBase_Default
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::IDefaultBase @this) => @this.ComputedProperty * 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExplicitInterfaceMember.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExplicitInterfaceMember.verified.txt
new file mode 100644
index 0000000..d405def
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExplicitInterfaceMember.verified.txt
@@ -0,0 +1,16 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class _Concrete_IBase__ComputedProperty
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Concrete @this) => @this.Id + 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
index a982c79..1f574f6 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
@@ -1856,6 +1856,100 @@ public Task GenericTypesWithConstraints()
return Verifier.Verify(result.GeneratedTrees[0].ToString());
}
+ [Fact]
+ public Task ExplicitInterfaceMember()
+ {
+ var compilation = CreateCompilation(
+ """
+ using System;
+ using EntityFrameworkCore.Projectables;
+
+ public interface IBase
+ {
+ int ComputedProperty { get; }
+ }
+
+ public class Concrete : IBase
+ {
+ int Id { get; }
+ [Projectable]
+ int IBase.ComputedProperty => Id + 1;
+ }
+ """);
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task DefaultInterfaceMember()
+ {
+ var compilation = CreateCompilation(
+ """
+ using System;
+ using EntityFrameworkCore.Projectables;
+
+ public interface IBase
+ {
+ int Id { get; }
+ int ComputedProperty { get; }
+ int ComputedMethod();
+ }
+
+ public interface IDefaultBase : IBase
+ {
+ [Projectable]
+ int Default => ComputedProperty * 2;
+ }
+ """);
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task DefaultExplicitInterfaceMember()
+ {
+ var compilation = CreateCompilation(
+ """
+ using System;
+ using EntityFrameworkCore.Projectables;
+
+ public interface IBase
+ {
+ int Id { get; }
+ int ComputedProperty { get; }
+ int ComputedMethod();
+ }
+
+ public interface IDefaultBase
+ {
+ int Default { get; }
+ }
+
+ public interface IDefaultBaseImplementation : IDefaultBase, IBase
+ {
+ [Projectable]
+ int IDefaultBase.Default => ComputedProperty * 2;
+ }
+ """);
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
#region Helpers
Compilation CreateCompilation(string source, bool expectedToCompile = true)