Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -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));
}

/// <summary>
/// The built-in <see cref="MethodInfo.op_Equality(System.Reflection.MethodInfo?,System.Reflection.MethodInfo?)"/>
/// does not work if the <see cref="MemberInfo.ReflectedType"/>s don't agree.
/// </summary>
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT [c].[Id] * 2
FROM [Concrete] AS [c]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT 49
FROM [Concrete] AS [c]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT [o].[Id] / 2
FROM [OtherConcrete] AS [o]
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
{
}
Expand Down Expand Up @@ -130,6 +150,36 @@ public Task ProjectOverImplementedMethod()
return Verifier.Verify(query.ToQueryString());
}

[Fact]
public Task ProjectOverDefaultImplementedProperty()
{
using var dbContext = new SampleDbContext<Concrete>();

var query = dbContext.Set<Concrete>().SelectDefaultProperty();

return Verifier.Verify(query.ToQueryString());
}

[Fact]
public Task ProjectOverExplicitlyImplementedProperty()
{
using var dbContext = new SampleDbContext<OtherConcrete>();

var query = dbContext.Set<OtherConcrete>().SelectExplicitProperty();

return Verifier.Verify(query.ToQueryString());
}

[Fact]
public Task ProjectOverDefaultExplicitlyImplementedProperty()
{
using var dbContext = new SampleDbContext<Concrete>();

var query = dbContext.Set<Concrete>().SelectExplicitProperty();

return Verifier.Verify(query.ToQueryString());
}

[Fact]
public Task ProjectOverProvider()
{
Expand All @@ -156,6 +206,14 @@ public static class ModelExtensions
public static IQueryable<int> SelectComputedProperty<TConcrete>(this IQueryable<TConcrete> concretes)
where TConcrete : InheritedModelTests.IBase
=> concretes.Select(x => x.ComputedProperty);

public static IQueryable<int> SelectDefaultProperty<TConcrete>(this IQueryable<TConcrete> concretes)
where TConcrete : InheritedModelTests.IDefaultBase
=> concretes.Select(x => x.Default);

public static IQueryable<int> SelectExplicitProperty<TConcrete>(this IQueryable<TConcrete> concretes)
where TConcrete : InheritedModelTests.IDefaultBase
=> concretes.Select(x => x.Explicit);

public static IQueryable<int> SelectComputedMethod<TConcrete>(this IQueryable<TConcrete> concretes)
where TConcrete : InheritedModelTests.IBase
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// <auto-generated/>
#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<global::System.Func<global::IDefaultBaseImplementation, int>> Expression()
{
return (global::IDefaultBaseImplementation @this) => @this.ComputedProperty * 2;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// <auto-generated/>
#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<global::System.Func<global::IDefaultBase, int>> Expression()
{
return (global::IDefaultBase @this) => @this.ComputedProperty * 2;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// <auto-generated/>
#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<global::System.Func<global::Concrete, int>> Expression()
{
return (global::Concrete @this) => @this.Id + 1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down