-
Notifications
You must be signed in to change notification settings - Fork 709
Support self referencing type substitution #382
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
Support self referencing type substitution #382
Conversation
This is great! I suspected there were still some edge cases I was missing. This is a really convenient feature to use, but it's not so easy to implement. Let me go through this and we'll get it merged. |
continue; | ||
} | ||
|
||
passedTypes.Add(structuredTypeRef.ToString(), propertyType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use FullName()
here instead of ToString()
(unless you know better). It looks like the implementation of ToString
is ToTraceString
, which may not be the same. IMO FullName()
also gives better semantic meaning to what the code is doing here.
@@ -66,6 +67,14 @@ public Type NewStructuredType( IEdmStructuredType structuredType, Type clrType, | |||
var propertyType = property.PropertyType; | |||
var structuredTypeRef = structuralProperty.Type; | |||
|
|||
if ( passedTypes.TryGetValue( structuredTypeRef.ToString(), out var passedType ) ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's capture the type name so we don't have to build it two times. Something like:
var typeName = structuredTypeRef.FullName();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This obviously supplanted by the struct key or hash code if you refactor
@@ -30,6 +30,7 @@ public sealed class DefaultModelTypeBuilder : IModelTypeBuilder | |||
readonly ICollection<Assembly> assemblies; | |||
readonly ConcurrentDictionary<ApiVersion, ModuleBuilder> modules = new ConcurrentDictionary<ApiVersion, ModuleBuilder>(); | |||
readonly ConcurrentDictionary<ClassSignature, Type> generatedTypes = new ConcurrentDictionary<ClassSignature, Type>(); | |||
private readonly Dictionary<string, Type> passedTypes = new Dictionary<string, Type>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's use visitedTypes
as it's more consistent with the pattern being used
@@ -66,6 +67,14 @@ public Type NewStructuredType( IEdmStructuredType structuredType, Type clrType, | |||
var propertyType = property.PropertyType; | |||
var structuredTypeRef = structuralProperty.Type; | |||
|
|||
if ( passedTypes.TryGetValue( structuredTypeRef.ToString(), out var passedType ) ) | |||
{ | |||
properties.Add(new ClassProperty(property, passedType)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the spacing is off by my rules, but this should be flagged by code analysis and auto-formatted by the .editorconfig settings. correcting this should be as easy as formatting the document.
Definitely let me know you don't see CA warnings and/or formatting the document doesn't fix all of these. You're not supposed to have to guess or do a lot of work to format things the way I want them to be. ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It formats for methods, but I had to manually add the formatting rules for nameof, typeof etc.
I'm using Jetbrains Rider if that means anything.
|
||
// assert | ||
subsitutedType.GetRuntimeProperties().Should().HaveCount( 3 ); | ||
subsitutedType.Should().HaveProperty<int>( nameof(Company.CompanyId) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: spacing; format of the document should fix them all
var person = modelBuilder.EntitySet<Company>( "Companies" ).EntityType; | ||
|
||
var context = NewContext( modelBuilder.GetEdmModel() ); | ||
var originalType = typeof(Company); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: spacing; format of the document should fix them all
@@ -1,23 +1,18 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file doesn't seem to have actually been changed. It appears that only the extra lines have been removed. It's not a huge deal, but there's not much sense in changing a file that doesn't need changing.
@@ -30,6 +30,7 @@ public sealed class DefaultModelTypeBuilder : IModelTypeBuilder | |||
readonly ICollection<Assembly> assemblies; | |||
readonly ConcurrentDictionary<ApiVersion, ModuleBuilder> modules = new ConcurrentDictionary<ApiVersion, ModuleBuilder>(); | |||
readonly ConcurrentDictionary<ClassSignature, Type> generatedTypes = new ConcurrentDictionary<ClassSignature, Type>(); | |||
private readonly Dictionary<string, Type> passedTypes = new Dictionary<string, Type>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In thinking about this, I'm pretty sure that using string
alone for the key is not going to work because there can be a distinctly generated type by API version. This should mean that the key for the corresponding EDM type should be a combination of EDM type and API version; otherwise, it's possible that the wrong generated type will be picked.
For example, imagine that Default.Company
is generated for API version 2.0 and then it's requested again for API version 1.0. If the models aren't exactly the same (which they probably aren't), the wrong selection is made.
There's two ways this can be handled:
- Add a hashable struct, something like VisitedTypeKey
- Use
int
as the key and add a hashing function
Option 2 might look like:
static int GetVisitedTypeKey( IEdmTypeReference type, ApiVersion apiVersion ) =>
( type.FullName().GetHashCode() * 397 ) ^ apiVersion.GetHashCode();
continue; | ||
} | ||
|
||
passedTypes.Add(structuredTypeRef.ToString(), propertyType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the visited type needs to be added toward the end; probably after line 102. The propertyType
variable can be mutated after this line, which means the wrong type will be stored in the dictionary.
Thanks @LuukN2 for your contribution; really - it was useful. I'm going to close this one out because it turns out there's quite a bit more going on than just these rather basic fixes. Your change does address the basic recursion problem, but it completely misses recursively emitting a self-referencing type when it's actually substituted. I realized that this was missing when incorporating your unit tests. I even had to do some research because I had no idea how to actually build a type like that. I also expanded your scenario to also include the property I've incorporated your bits and all the additional required changes into #384 that I already had going. The last commit has all of the changes relevant to this PR. I'll leave #384 open for a bit so you have some time to review and comment if you want. Thanks again for this submission. I don't think I would have remembered about this use case otherwise. |
Thanks for the substitution fixes.
However, there was still a small recursion issue when a model references itself on a property. (Or when two models reference each other, etc.)
The program would get stuck in a loop getting the structured type of the properties causing a stackoverflow exception.
Now it keeps track of what types it has already substituted in a dictionary and passes these values instead of recreating the structured type again.
Thanks again for your hard work, your project is awesome!