Skip to content

Commit 64ea799

Browse files
LuukN2commonsensesoftware
authored andcommitted
Added support for resolving dependencies on properties that reference types that are still being built.
1 parent f97406c commit 64ea799

File tree

9 files changed

+315
-68
lines changed

9 files changed

+315
-68
lines changed

src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs

Lines changed: 149 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ public sealed class DefaultModelTypeBuilder : IModelTypeBuilder
2929
static readonly Type IEnumerableOfT = typeof( IEnumerable<> );
3030
readonly ICollection<Assembly> assemblies;
3131
readonly ConcurrentDictionary<ApiVersion, ModuleBuilder> modules = new ConcurrentDictionary<ApiVersion, ModuleBuilder>();
32-
readonly ConcurrentDictionary<ClassSignature, Type> generatedTypes = new ConcurrentDictionary<ClassSignature, Type>();
33-
readonly Dictionary<EdmTypeKey, Type> visitedEdmTypes = new Dictionary<EdmTypeKey, Type>();
32+
readonly ConcurrentDictionary<EdmTypeKey, Type> generatedEdmTypes = new ConcurrentDictionary<EdmTypeKey, Type>();
33+
readonly ConcurrentDictionary<EdmTypeKey, TypeBuilder> unfinishedTypes = new ConcurrentDictionary<EdmTypeKey, TypeBuilder>();
34+
readonly HashSet<EdmTypeKey> visitedEdmTypes = new HashSet<EdmTypeKey>();
35+
readonly Dictionary<EdmTypeKey, List<PropertyDependency>> dependencies = new Dictionary<EdmTypeKey, List<PropertyDependency>>();
3436

3537
/// <summary>
3638
/// Initializes a new instance of the <see cref="DefaultModelTypeBuilder"/> class.
@@ -52,16 +54,20 @@ public Type NewStructuredType( IEdmStructuredType structuredType, Type clrType,
5254

5355
var typeKey = new EdmTypeKey( structuredType, apiVersion );
5456

55-
if ( visitedEdmTypes.TryGetValue( typeKey, out var generatedType ) )
57+
if ( generatedEdmTypes.TryGetValue( typeKey, out var generatedType ) )
5658
{
5759
return generatedType;
5860
}
5961

62+
visitedEdmTypes.Add( typeKey );
63+
6064
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
6165

6266
var properties = new List<ClassProperty>();
6367
var structuralProperties = structuredType.Properties().ToDictionary( p => p.Name, StringComparer.OrdinalIgnoreCase );
6468
var clrTypeMatchesEdmType = true;
69+
var hasUnfinishedTypes = false;
70+
var dependentProperties = new Dictionary<string, Tuple<EdmTypeKey, bool>>();
6571

6672
foreach ( var property in clrType.GetProperties( bindingFlags ) )
6773
{
@@ -73,14 +79,61 @@ public Type NewStructuredType( IEdmStructuredType structuredType, Type clrType,
7379

7480
var structuredTypeRef = structuralProperty.Type;
7581
var propertyType = property.PropertyType;
82+
var propertyTypeKey = new EdmTypeKey( structuredTypeRef, apiVersion );
7683

7784
if ( structuredTypeRef.IsCollection() )
7885
{
79-
propertyType = NewStructuredTypeOrSelf( typeKey, structuredTypeRef.AsCollection(), propertyType, apiVersion );
86+
var collectionType = structuredTypeRef.AsCollection();
87+
var elementType = collectionType.ElementType();
88+
89+
if ( elementType.IsStructured() )
90+
{
91+
assemblies.Add( clrType.Assembly );
92+
visitedEdmTypes.Add( propertyTypeKey );
93+
94+
var itemType = elementType.Definition.GetClrType( assemblies );
95+
var elementKey = new EdmTypeKey(elementType, apiVersion);
96+
97+
if ( visitedEdmTypes.Contains( elementKey ) )
98+
{
99+
clrTypeMatchesEdmType = false;
100+
hasUnfinishedTypes = true;
101+
var keyTuple = new Tuple<EdmTypeKey, bool>( elementKey, true );
102+
dependentProperties.Add(property.Name, keyTuple );
103+
continue;
104+
}
105+
106+
var newItemType = NewStructuredType( elementType.ToStructuredType(), itemType, apiVersion );
107+
108+
if ( newItemType is TypeBuilder )
109+
{
110+
hasUnfinishedTypes = true;
111+
}
112+
113+
if ( !itemType.Equals( newItemType ) )
114+
{
115+
propertyType = IEnumerableOfT.MakeGenericType( newItemType );
116+
}
117+
}
80118
}
81119
else if ( structuredTypeRef.IsStructured() )
82120
{
83-
propertyType = NewStructuredTypeOrSelf( typeKey, structuredTypeRef.ToStructuredType(), propertyType, apiVersion );
121+
if ( !visitedEdmTypes.Contains( propertyTypeKey ) )
122+
{
123+
propertyType = NewStructuredType( structuredTypeRef.ToStructuredType(), propertyType, apiVersion );
124+
if ( propertyType is TypeBuilder )
125+
{
126+
hasUnfinishedTypes = true;
127+
}
128+
}
129+
else
130+
{
131+
clrTypeMatchesEdmType = false;
132+
hasUnfinishedTypes = true;
133+
var keyTuple = new Tuple<EdmTypeKey, bool>( propertyTypeKey, false );
134+
dependentProperties.Add(property.Name, keyTuple );
135+
continue;
136+
}
84137
}
85138

86139
clrTypeMatchesEdmType &= propertyType.IsDeclaringType() || property.PropertyType.Equals( propertyType );
@@ -94,10 +147,28 @@ public Type NewStructuredType( IEdmStructuredType structuredType, Type clrType,
94147

95148
var signature = new ClassSignature( clrType.FullName, properties, apiVersion );
96149

97-
generatedType = generatedTypes.GetOrAdd( signature, CreateFromSignature );
98-
visitedEdmTypes.Add( typeKey, generatedType );
150+
if ( hasUnfinishedTypes )
151+
{
152+
if ( !unfinishedTypes.TryGetValue( typeKey, out var typeBuilder ) )
153+
{
154+
typeBuilder = CreateTypeBuilderFromSignature( signature );
155+
var newPropertyDependencies = new List<PropertyDependency>();
156+
foreach ( var name in dependentProperties.Keys )
157+
{
158+
var keyTuple = dependentProperties[name];
159+
newPropertyDependencies.Add( new PropertyDependency( typeBuilder, keyTuple.Item1, name, keyTuple.Item2 ) );
160+
}
161+
162+
dependencies.Add( typeKey, newPropertyDependencies );
163+
unfinishedTypes.GetOrAdd( typeKey, typeBuilder );
164+
165+
return ResolveDependencies( typeBuilder, typeKey );
166+
}
99167

100-
return generatedType;
168+
return typeBuilder;
169+
}
170+
171+
return generatedEdmTypes.GetOrAdd( typeKey, CreateTypeInfoFromSignature( signature ) );
101172
}
102173

103174
/// <inheritdoc />
@@ -111,97 +182,107 @@ public Type NewActionParameters( IEdmAction action, ApiVersion apiVersion )
111182
var properties = action.Parameters.Where( p => p.Name != "bindingParameter" ).Select( p => new ClassProperty( assemblies, p ) );
112183
var signature = new ClassSignature( name, properties, apiVersion );
113184

114-
return generatedTypes.GetOrAdd( signature, CreateFromSignature );
185+
return CreateTypeInfoFromSignature( signature );
115186
}
116187

117-
TypeInfo CreateFromSignature( ClassSignature @class )
188+
TypeInfo CreateTypeInfoFromSignature( ClassSignature @class )
118189
{
119190
Contract.Requires( @class != null );
120191
Contract.Ensures( Contract.Result<TypeInfo>() != null );
121192

122-
const MethodAttributes PropertyMethodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
193+
return CreateTypeBuilderFromSignature( @class ).CreateTypeInfo();
194+
}
195+
196+
TypeBuilder CreateTypeBuilderFromSignature( ClassSignature @class )
197+
{
198+
Contract.Requires( @class != null );
199+
Contract.Ensures( Contract.Result<TypeBuilder>() != null );
200+
123201
var moduleBuilder = modules.GetOrAdd( @class.ApiVersion, CreateModuleForApiVersion );
124202
var typeBuilder = moduleBuilder.DefineType( @class.Name, TypeAttributes.Class );
125203

126204
foreach ( var property in @class.Properties )
127205
{
128206
var type = property.GetType( typeBuilder );
129207
var name = property.Name;
130-
var field = typeBuilder.DefineField( "_" + name, type, FieldAttributes.Private );
131-
var propertyBuilder = typeBuilder.DefineProperty( name, PropertyAttributes.HasDefault, type, null );
132-
var getter = typeBuilder.DefineMethod( "get_" + name, PropertyMethodAttributes, type, Type.EmptyTypes );
133-
var setter = typeBuilder.DefineMethod( "set_" + name, PropertyMethodAttributes, null, new[] { type } );
134-
var il = getter.GetILGenerator();
135-
136-
il.Emit( OpCodes.Ldarg_0 );
137-
il.Emit( OpCodes.Ldfld, field );
138-
il.Emit( OpCodes.Ret );
139-
140-
il = setter.GetILGenerator();
141-
il.Emit( OpCodes.Ldarg_0 );
142-
il.Emit( OpCodes.Ldarg_1 );
143-
il.Emit( OpCodes.Stfld, field );
144-
il.Emit( OpCodes.Ret );
145-
146-
propertyBuilder.SetGetMethod( getter );
147-
propertyBuilder.SetSetMethod( setter );
208+
var propertyBuilder = AddProperty( typeBuilder, type, name );
148209

149210
foreach ( var attribute in property.Attributes )
150211
{
151212
propertyBuilder.SetCustomAttribute( attribute );
152213
}
153214
}
154215

155-
return typeBuilder.CreateTypeInfo();
216+
return typeBuilder;
156217
}
157218

158-
Type NewStructuredTypeOrSelf( EdmTypeKey declaringTypeKey, IEdmCollectionTypeReference collectionType, Type clrType, ApiVersion apiVersion )
219+
private Type ResolveDependencies( TypeBuilder typeBuilder, EdmTypeKey typeKey )
159220
{
160-
Contract.Requires( collectionType != null );
161-
Contract.Requires( clrType != null );
162-
Contract.Requires( apiVersion != null );
163-
Contract.Ensures( Contract.Result<Type>() != null );
164-
165-
var elementType = collectionType.ElementType();
166-
167-
if ( !elementType.IsStructured() )
168-
{
169-
return clrType;
170-
}
221+
var keys = dependencies.Keys.ToList();
171222

172-
var structuredType = elementType.ToStructuredType();
173-
174-
if ( declaringTypeKey == new EdmTypeKey( structuredType, apiVersion ) )
223+
foreach ( var key in keys )
175224
{
176-
return IEnumerableOfT.MakeGenericType( DeclaringType.Value );
177-
}
178-
179-
assemblies.Add( clrType.Assembly );
180-
181-
var itemType = elementType.Definition.GetClrType( assemblies );
182-
var newItemType = NewStructuredType( structuredType, itemType, apiVersion );
225+
var propertyDependencies = dependencies[key];
226+
for ( var x = propertyDependencies.Count - 1; x >= 0; x-- )
227+
{
228+
var propertyDependency = propertyDependencies[x];
229+
if ( propertyDependency.DependentOnTypeKey == typeKey )
230+
{
231+
if ( propertyDependency.IsCollection )
232+
{
233+
var collectionType = IEnumerableOfT.MakeGenericType( typeBuilder );
234+
AddProperty( propertyDependency.DependentType, collectionType, propertyDependency.PropertyName );
235+
}
236+
else
237+
{
238+
AddProperty( propertyDependency.DependentType, typeBuilder, propertyDependency.PropertyName );
239+
}
240+
241+
propertyDependencies.Remove( propertyDependency );
242+
}
243+
}
183244

184-
if ( !itemType.Equals( newItemType ) )
185-
{
186-
clrType = IEnumerableOfT.MakeGenericType( newItemType );
245+
if ( propertyDependencies.Count == 0 )
246+
{
247+
dependencies.Remove( key );
248+
if ( unfinishedTypes.TryRemove( key, out var type ) )
249+
{
250+
var typeInfo = type.CreateTypeInfo();
251+
generatedEdmTypes.GetOrAdd( key, typeInfo );
252+
253+
if ( key == typeKey )
254+
{
255+
return typeInfo;
256+
}
257+
}
258+
}
187259
}
188260

189-
return clrType;
261+
return typeBuilder;
190262
}
191263

192-
Type NewStructuredTypeOrSelf( EdmTypeKey declaringTypeKey, IEdmStructuredType structuredType, Type clrType, ApiVersion apiVersion )
264+
static PropertyBuilder AddProperty( TypeBuilder addTo, Type shouldBeAdded, string name )
193265
{
194-
Contract.Requires( structuredType != null );
195-
Contract.Requires( clrType != null );
196-
Contract.Requires( apiVersion != null );
197-
Contract.Ensures( Contract.Result<Type>() != null );
198-
199-
if ( declaringTypeKey == new EdmTypeKey( structuredType, apiVersion ) )
200-
{
201-
return DeclaringType.Value;
202-
}
203-
204-
return NewStructuredType( structuredType, clrType, apiVersion );
266+
const MethodAttributes propertyMethodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
267+
var field = addTo.DefineField( name, shouldBeAdded, FieldAttributes.Private );
268+
var propertyBuilder = addTo.DefineProperty( name, PropertyAttributes.HasDefault, shouldBeAdded, null );
269+
var getter = addTo.DefineMethod( "get_" + name, propertyMethodAttributes, shouldBeAdded, Type.EmptyTypes );
270+
var setter = addTo.DefineMethod( "set_" + name, propertyMethodAttributes, shouldBeAdded, Type.EmptyTypes );
271+
var il = getter.GetILGenerator();
272+
273+
il.Emit( OpCodes.Ldarg_0 );
274+
il.Emit( OpCodes.Ldfld, field );
275+
il.Emit( OpCodes.Ret );
276+
277+
il = setter.GetILGenerator();
278+
il.Emit( OpCodes.Ldarg_0 );
279+
il.Emit( OpCodes.Ldarg_1 );
280+
il.Emit( OpCodes.Stfld, field );
281+
il.Emit( OpCodes.Ret );
282+
propertyBuilder.SetGetMethod( getter );
283+
propertyBuilder.SetSetMethod( setter );
284+
285+
return propertyBuilder;
205286
}
206287

207288
static ModuleBuilder CreateModuleForApiVersion( ApiVersion apiVersion )
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
namespace Microsoft.AspNet.OData
2+
{
3+
using System.Reflection.Emit;
4+
5+
/// <summary>
6+
/// Represents a dependency on a type of a property that has not been built yet.
7+
/// </summary>
8+
internal class PropertyDependency
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="PropertyDependency"/> class.
12+
/// </summary>
13+
/// <param name="dependentType">The type that contains the dependent property.</param>
14+
/// <param name="dependentOnTypeKey">The key of the type the property has a dependency on.</param>
15+
/// <param name="propertyName">The name of the property.</param>
16+
/// <param name="isCollection">Whether the property is a collection or not.</param>
17+
internal PropertyDependency( TypeBuilder dependentType, EdmTypeKey dependentOnTypeKey, string propertyName, bool isCollection )
18+
{
19+
Arg.NotNull<TypeBuilder>( dependentType, nameof(dependentType) );
20+
Arg.NotNull<EdmTypeKey>( dependentOnTypeKey, nameof( dependentOnTypeKey ) );
21+
Arg.NotNull<string>( propertyName, nameof( propertyName ) );
22+
23+
DependentType = dependentType;
24+
DependentOnTypeKey = dependentOnTypeKey;
25+
PropertyName = propertyName;
26+
IsCollection = isCollection;
27+
}
28+
29+
/// <summary>
30+
/// Gets the type that has a dependent property.
31+
/// </summary>
32+
internal TypeBuilder DependentType { get; }
33+
34+
/// <summary>
35+
/// Gets the key of the type the property has a dependency on.
36+
/// </summary>
37+
internal EdmTypeKey DependentOnTypeKey { get; }
38+
39+
/// <summary>
40+
/// Gets the name of the property.
41+
/// </summary>
42+
internal string PropertyName { get; }
43+
44+
/// <summary>
45+
/// Gets a value indicating whether the property is a collection.
46+
/// </summary>
47+
internal bool IsCollection { get; }
48+
}
49+
}

src/Common.OData.ApiExplorer/Common.OData.ApiExplorer.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<Compile Include="$(MSBuildThisFileDirectory)AspNet.OData\IModelTypeBuilder.cs" />
1717
<Compile Include="$(MSBuildThisFileDirectory)AspNet.OData\DefaultModelTypeBuilder.cs" />
1818
<Compile Include="$(MSBuildThisFileDirectory)AspNet.OData\ODataValue{T}.cs" />
19+
<Compile Include="$(MSBuildThisFileDirectory)AspNet.OData\PropertyDependency.cs" />
1920
<Compile Include="$(MSBuildThisFileDirectory)AspNet.OData\Routing\ODataRouteActionType.cs" />
2021
<Compile Include="$(MSBuildThisFileDirectory)AspNet.OData\Routing\ODataRouteBuilder.cs" />
2122
<Compile Include="$(MSBuildThisFileDirectory)AspNet.OData\Routing\ODataRouteBuilderContext.cs" />

test/Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,31 @@ public void type_should_use_self_referencing_property_substitution()
173173
subsitutedType.Should().Be( subsitutedType.GetRuntimeProperty( nameof( Company.Subsidiaries ) ).PropertyType.GetGenericArguments()[0] );
174174
}
175175

176+
[Fact]
177+
public void type_should_use_back_referencing_property_substitution()
178+
{
179+
// arrange
180+
var modelBuilder = new ODataConventionModelBuilder();
181+
var employer = modelBuilder.EntitySet<Employer>( "Employers" ).EntityType;
182+
183+
employer.Ignore( e => e.Birthday );
184+
185+
var context = NewContext( modelBuilder.GetEdmModel() );
186+
var originalType = typeof( Employer );
187+
188+
// act
189+
var substitutedType = originalType.SubstituteIfNecessary( context );
190+
191+
// assert
192+
substitutedType.GetRuntimeProperties().Should().HaveCount( 4 );
193+
substitutedType.Should().HaveProperty<int>( nameof( Employer.EmployerId ) );
194+
substitutedType.Should().HaveProperty<string>( nameof( Employer.FirstName ) );
195+
substitutedType.Should().HaveProperty<string>( nameof( Employer.LastName ) );
196+
197+
var employees = substitutedType.GetProperty( nameof( Employer.Employees ) ).PropertyType.GetGenericArguments()[0];
198+
substitutedType.Should().Be( employees.GetProperty( nameof( Employee.Employer ) ).PropertyType );
199+
}
200+
176201
[Theory]
177202
[MemberData( nameof( SubstitutionData ) )]
178203
public void type_should_match_edm_with_child_entity_substitution( Type originalType )

0 commit comments

Comments
 (0)