Add IODataTypeMapper - #342
Conversation
| /// <summary> | ||
| /// Creates a static instance for the Default type mapper. | ||
| /// </summary> | ||
| internal static DefaultODataTypeMapper Default = new DefaultODataTypeMapper(); |
There was a problem hiding this comment.
It would probably be good to mark this readonly. Also, I think usually these singletons are properties, but I guess it's fine since this is internal? #WontFix
There was a problem hiding this comment.
Thanks. I keep it unchanged.
| /// The default mapping between Edm primitive type and Clr primitive type. | ||
| /// Primitive types are cross Edm models. | ||
| /// </summary> | ||
| private static ConcurrentDictionary<Type, IEdmPrimitiveTypeReference> ClrPrimitiveTypes |
There was a problem hiding this comment.
I'm coming from pretty old .NET versions as this point (4.6 and earlier) so a lot might have changed, but I recall there being lots of overhead to using ConcurrentDictionary compared to Dictionary. I see that we want to be thread-safe, particularly because these are static, but they are only ever mutated during the static constructor. According to this article, we can trust the CLR to only call the static constructor once, meaning that modifications to these dictionaries will not be happening concurrently. Further, because the thread-safety document for Dictionary indicates that reads are thread-safe on instances, I believe it would be best for performance reasons to use Dictionary here instead of ConcurrentDictionary. #Resolved
There was a problem hiding this comment.
Changed to IDictionary<T,T>. Thanks.
| edmModel | ||
| .SchemaElements | ||
| .OfType<IEdmType>() | ||
| .Select(edmType => new { EdmType = edmType, Annotation = edmModel.GetAnnotationValue<ClrTypeAnnotation>(edmType) }) |
There was a problem hiding this comment.
I'm not sure if this is "nit" or not, but I think the Where should come before the Select. Doing so, we don't lose track of edmType at all (so we maintain functionality from what I can tell), we prevent the allocation (and therefore garbage collection as well) of these anonymous types, and we prevent the extra Select later that also requires dereferencing the EdmType property. #Pending
There was a problem hiding this comment.
where clause needs the "Annotation" calculation from Select clause. So, i think it could be infeasible to switch them. And These codes are from existing codes. I don't want to add more perf in a feature request PR. If it's ok, we can improve them in the next Perf improvement PR?
| .Select(edmType => new { EdmType = edmType, Annotation = edmModel.GetAnnotationValue<ClrTypeAnnotation>(edmType) }) | ||
| .Where(tuple => tuple.Annotation != null && tuple.Annotation.ClrType == clrType) | ||
| .Select(tuple => tuple.EdmType) | ||
| .SingleOrDefault(); |
There was a problem hiding this comment.
Do we have a reasonable expectation that there can never be more than one element found? I ask because SingleOrDefault will still throw (rather than return null) if there is more than one element found. #WontFix
There was a problem hiding this comment.
Each EdmType has 0 or 1 'ClrTypeAnnotation' associated.
So, here only returns 'null' or the found returnType.
| string typeName = edmSchemaType.FullName(); | ||
| IEnumerable<Type> matchingTypes = GetMatchingTypes(typeName, assembliesResolver); | ||
|
|
||
| if (matchingTypes.Count() > 1) |
There was a problem hiding this comment.
I'm seeing this type of thing a bit. We might be able to reasonably assume that there are few elements in the sequence, but I don't know why we should take that risk. I suggest we have an internal extension (since we are already going to be enumerating here for Count) like:
public static bool AtLeast<T>(this IEnumerable<T> source, int count)
{
using (var enumerator = source.GetEnumerator())
{
for (int i = 0; i < count; ++i)
{
if (!enumerator.MoveNext())
{
return false;
}
}
return true;
}
}
Where it can act like Any for cases greater than 0 (e.g. AtLeast(1) is equivalent to Any, and here we could say AtLeast(2))
I intend to do something like this for the issue I created last week, so if you want to keep this for now, I can go ahead and incorporate that change when I have it. What do you think? #WontFix
There was a problem hiding this comment.
Thanks. I don't like the current implementation. And I'd like to change it in your PR together.
|
|
||
| if (matchingTypes.Count() > 1) | ||
| { | ||
| throw Error.Argument("edmTypeReference", SRResources.MultipleMatchingClrTypesForEdmType, |
There was a problem hiding this comment.
I think this should be an InvalidOperationException rather than ArgumentException #Resolved
|
|
||
| private TypeCacheItem GetOrCreateCacheItem(IEdmModel model) | ||
| { | ||
| if (!_cache.TryGetValue(model, out TypeCacheItem map)) |
There was a problem hiding this comment.
Since _cache is ConcurrentDictionary, can't we use the GetOrAdd method? Is there some overhead I'm not aware of that we are trying to avoid? #Resolved
|
|
||
| private static Type ExtractGenericInterface(Type queryType, Type interfaceType) | ||
| { | ||
| Func<Type, bool> matchesInterface = t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType; |
There was a problem hiding this comment.
I don't have a proposed fix, but I do know what is written here has potential memory issues because of some maths concepts I don't fully understand about closures (probably @chrisspre knows) and the fact that interfaceType is a parameter. What happens in .NET is that a new Func instance needs to be created for every interfaceType that this method is called with, and if this is a long-lived object, that expands memory a bunch because those Funcs never go out of scope.
If this is the only way we can achieve this functionality, that's fine, but if you can figure out a different way to write this, it would almost certainly be preferable. #WontFix
There was a problem hiding this comment.
Thanks for your points.
I'd like to keep them unchanged now since that's the existing codes. We'd make a better way in the next perf PR.
| /// <summary> | ||
| /// <see cref="Type"/> to <see cref="IEdmTypeReference"/>. | ||
| /// </summary> | ||
| public ConcurrentDictionary<Type, IEdmTypeReference> ClrToEdmTypeCache = new ConcurrentDictionary<Type, IEdmTypeReference>(); |
There was a problem hiding this comment.
This should almost certainly be readonly or a property right? #Resolved
| Assert.True(primitiveTypeReference.IsNullable); | ||
| } | ||
|
|
||
| //[Theory] |
There was a problem hiding this comment.
Why is this commented out? #Resolved
There was a problem hiding this comment.
IsNonstandardEdmPrimitive is an extension method on IEdmModel. So, the test case moved to "EdmClrTypeMapExtensionsTests".
Forgot to clean it up.
corranrogue9
left a comment
There was a problem hiding this comment.
I'm approving on the basis of the design that the cache itself will certainly be beneficial in its own right, but I do think that the other performance feedback I gave would be good to incorporate as well.
In the existing implementation, customers can't customize the OData CLR type and Edm type mapping.
This PR introduces
IODataTypeMapperand its default implementationDefaultODataTypeMapper.The developer can create his own type of mapper to override it.