-
Notifications
You must be signed in to change notification settings - Fork 351
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
Import vocabulary annotations declared in external referenced models #1987
Import vocabulary annotations declared in external referenced models #1987
Conversation
f998660
to
26a681e
Compare
@@ -114,13 +114,14 @@ private CsdlSemanticsModel(CsdlModel referencedCsdlModel, IEdmDirectValueAnnotat | |||
foreach (var schema in referencedCsdlModel.Schemata) | |||
{ | |||
string schemaNamespace = schema.Namespace; | |||
IEdmInclude edmInclude = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.Includes).FirstOrDefault(s => s.Namespace == schemaNamespace); | |||
if (edmInclude != null) | |||
bool shouldAddTypes = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.Includes).Any(s => s.Namespace == schemaNamespace); |
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.
bool shouldAddTypes = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.Includes).Any(s => s.Namespace == schemaNamespace); | |
bool addSchemaElements = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.Includes).Any(s => s.Namespace == schemaNamespace); |
test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderTests.cs
Outdated
Show resolved
Hide resolved
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.AddSchema(schema, true); | ||
this.AddSchema(schema, addSchemaElements, true, null); |
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.AddSchema(schema, addSchemaElements, true, null); | |
this.AddSchema(schema, addSchemaElements, true /*addAnnotations*/, null /*includeAnnotations*/); |
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.
Maybe I could use named parameters instead, like:
this.AddSchema(schema, addSchemaElements, addAnnotations: true, includeAnnotations: null);
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.
@habbes Use John's suggestion. Named parameters aren't supported in some c# versions.
IEdmInclude edmInclude = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.Includes).FirstOrDefault(s => s.Namespace == schemaNamespace); | ||
if (edmInclude != null) | ||
bool shouldAddTypes = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.Includes).Any(s => s.Namespace == schemaNamespace); | ||
IEnumerable<IEdmIncludeAnnotations> includeAnnotations = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.IncludeAnnotations); |
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.
includeAnnotations [](start = 52, length = 18)
I'm concerned about the performance of executing this (somewhat expensive) SelectMany LINQ expression multiple times -- we execute it once on line 119, again on line 625, and then for each term on line 641. Probably be better, especially since we are iterating multiple times, to make this a list. removing the linq expression all together and simply iterating through building the collection would be even better, but probably not a significant savings here.
// we check if the namespace matches, and (if applicable) also the target namespace and the qualifier | ||
// see: http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_IncludedAnnotations | ||
|
||
return includeAnnotations.Any(include => |
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.
Any [](start = 38, length = 3)
LINQ is expensive -- AGS has gone through a LINQ hunt and removed all use of LINQ from their code. We should avoid it where we can.
Instead, it would be more efficient to iterate through the collection and evaluate the options.
<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx""> | ||
<edmx:Reference Uri=""SimpleModel.xml""> | ||
<edmx:Include Namespace=""Default""/> | ||
<edmx:Include Namespace=""Example.Types"" /> |
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.
Maybe add a test where the annotation schema defines an alias for the referenced namespace that is used locally in specifying the target, and make sure that annotations restricted to that target are still (exclusively) included.
i.e., define an alias for the "Example.Types" namespace in permissions.Csdl (say "examples") and use it in the Annotations Target, and then in SimpleModel.xml specify a TargetNamespace of "Example.Types" in your include annotations and make sure it still returns the right set of annotations (I think your code handles that, just want to make sure it's in the tests...)
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.
I've added the test
|
||
var filteredAnnotations = csdlAnnotations.Annotations.Where(annotation => | ||
ShouldIncludeAnnotation(annotation, target, includeAnnotations)); | ||
return new CsdlAnnotations(filteredAnnotations, csdlAnnotations.Target, csdlAnnotations.Qualifier); |
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.
csdlAnnotations.Target [](start = 60, length = 22)
should this use csdlAnnotations.Target, or "target" (which has the alias already resolved)?
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.
The aim here is to return a copy of the CsdlAnnotations
instance but with the actual annotations filtered. So every other property should be identical to the original CsdlAnnotations
instance, hence csdlAnnotations.Target
. The target
with the alias already resolved is used when comparing against the include.TargetNamespace
.
test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderTests.cs
Outdated
Show resolved
Hide resolved
/// figure out whether the schema's types (entiy types, enums, containers, etc.) and annotations should | ||
/// be imported | ||
/// </summary> | ||
/// <param name="schema"></param> |
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.
Document this param and the following 2
|
||
/// <summary> | ||
/// Process the <see cref="IEdmReference"/>s of the parent model to | ||
/// figure out whether the schema's types (entiy types, enums, containers, etc.) and annotations should |
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.
/// figure out whether the schema's types (entiy types, enums, containers, etc.) and annotations should | |
/// figure out whether the schema's types (entity types, enums, containers, etc.) and annotations should |
shouldAddSchemaElements = false; | ||
includeAnnotationsIndex = new Dictionary<string, List<IEdmIncludeAnnotations>>(); | ||
|
||
|
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.
string schemaNamespace = schema.Namespace; | ||
|
||
shouldAddSchemaElements = false; | ||
includeAnnotationsIndex = new Dictionary<string, List<IEdmIncludeAnnotations>>(); |
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.
Maybe apply the constructor that takes an IEqualityComparer<T>
argument if TermNamespace="Org.OData.Capabilities.V1"
is considered to be equivalent to TermNamespace="org.OData.Capabilities.V1"
(i.e. case-insensitive)
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.
I believe namespaces are case-sensitive. At least in many cases, the spec mentions that identifiers are case-sensitive. I assume it applies to namespaces as well.
string target, | ||
Dictionary<string, List<IEdmIncludeAnnotations>> includeAnnotationsIndex) | ||
{ | ||
if (includeAnnotationsIndex == null) |
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.
Is this ever likely to evaluate to true
given the initialization of includeAnnotationsIndex
here? Maybe check for null
and empty (or just empty if null
is unlikely)?
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's actually set to null when called from the original AddSchema(schema, addAnnotations)
method here.
In that method, when the addAnnotations
was set true, all the annotations in the schema were added without any filtering. In that case I call the AddSchemaOutOfLineAnnotations
with includeAnnotationsIndex
set to null
to indicate that all annotations should be added without any filtering. If, on the other hand, the includeAnnotationsIndex
is empty, then all annotations will be filtered out and not get added, since the ShouldIncludeAnnotation
method will return false.
I think I should mention in the doc comments what the intended behaviour is when includeAnnotationsIndex
is null
.
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.
I think I should make an optimization to avoid filtering the annotations if the includeAnnotationsIndex
is empty, since in that case none of the csdl annotations will be imported.
|
||
string qualifiedTerm = this.ReplaceAlias(annotation.Term); | ||
string targetNamespace; | ||
EdmUtil.TryGetNamespaceNameFromQualifiedName(target, out targetNamespace, out string baseTarget); |
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.
EdmUtil.TryGetNamespaceNameFromQualifiedName(target, out targetNamespace, out string baseTarget); | |
EdmUtil.TryGetNamespaceNameFromQualifiedName(target, out targetNamespace, out _); |
string qualifiedTerm = this.ReplaceAlias(annotation.Term); | ||
string targetNamespace; | ||
EdmUtil.TryGetNamespaceNameFromQualifiedName(target, out targetNamespace, out string baseTarget); | ||
if (EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedTerm, out string termNamespace, out string baseTerm)) |
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.
if (EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedTerm, out string termNamespace, out string baseTerm)) | |
if (EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedTerm, out string termNamespace, out _)) |
|
||
annotations.Add(new CsdlSemanticsAnnotations(schemaWrapper, schemaOutOfLineAnnotations)); | ||
return includeAnnotations.Any(include => |
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.
Just to confirm annotation.Qualifier == include.Qualifier
and targetNamespace == include.TargetNamespace
will be a case-sensitive comparison...
Maybe
(string.IsNullOrEmpty(include.Qualifier) || include.Qualifier.Equals(annotation.Qualifier, StringComparison.OrdinalIgnoreCase))
&& (string.IsNullOrEmpty(include.TargetNamespace) || include.TargetNamespace.Equals(targetNamespace, StringComparison.OrdinalIgnoreCase)));
?
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.
I believe they are case sensitive
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.
Indeed: Names are case-sensitive, but service authors SHOULD NOT choose names that differ only in case.
/// If this dictionary is non-empty, then only annotations that match | ||
/// the references will be added. If it's empty, no annotations will be added. If it's null, all annotations | ||
/// will be added unconditionally.</param> | ||
private void AddOutOfLineAnnotationsFromSchema( |
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.
@habbes RE: If it's null, all annotations will be added unconditionally
Does this happen from the calling method or this (AddOutOfLineAnnotationsFromSchema
) method?
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.
That actually takes place in the FilterIncludedAnnotations
method, which is called by this method (AddOutOfLineAnnotationsFromSchema
). The FilterIncludedAnnotations
method has a similar note in its doc comments.
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.
LGTM
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.
LGTM
I see the build is failing. |
😀 In reply to: 772912424 [](ancestors = 772912424) |
CsdlSemanticsSchema schemaWrapper, | ||
Dictionary<string, List<IEdmIncludeAnnotations>> includeAnnotationsIndex) | ||
{ | ||
if (includeAnnotationsIndex?.Count == 0) |
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.
if (includeAnnotationsIndex?.Count == 0) | |
if (!includeAnnotationsIndex?.Count > 0) | |
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.
or handle the null in a better way as Any can cause some issues with performance here due. I believe null == 0
wil give you false but I could be wrong.
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.
I didn't understand this comment very well. But I made the null check more explicit includeAnnotationsIndex != null && includeAnnotationsIndex.Count == 0
@@ -1230,5 +1231,323 @@ public void ValidateEdmxVersions(string odataVersion) | |||
Assert.Empty(edmErrors); | |||
Assert.Equal(edmModel.GetEdmVersion(), odataVersion == "4.0" ? EdmConstants.EdmVersion4 : EdmConstants.EdmVersion401); | |||
} | |||
|
|||
[Fact] |
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.
Fact [](start = 9, length = 4)
Do you have any tests that validate referenced types (not annotations) from referenced namespace.
For example, in your tests, you could test loading permissionsCsdl and validating that it imported types from "Default" and "Exmaple.Types" from main.csdl, but didn't import types from main.csdl defined in 3rd namespace.
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.
I've added the test.
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.
nice. |
Issues
Fix #1936
Description
Includes annotations referenced in the
edmx:IncludeAnnotations
tag of theedmx:Reference
to be included when loading external referenced models.This makes the vocabulary annotations imported from referenced models available through:
or
Note, these external vocabulary annotations will not appear in
model.FindDeclaredVocabularyAnnotations(element)
ormodel.VocabularyAnnotations
since they only return the annotations declared directly in the current model.This PR attempts to restrict the imported annotations based on the
TermNamespace
,TargetNamespace
andQualifier
as specified in the specification: http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_IncludedAnnotations
Assuming the main model has the following reference:
then this feature will import vocabulary annotations from the
SimplePermissions.xml
csdl which meet either of the following criteria:Org.OData.Capabilities.V1
namespace and it has the qualifierInsert
OtherIncludedNS
and it's target element is in the namespaceExample.Types
Other vocabulary annotations in the referenced csdl will be skipped.
Note: This restriction only applies to "out of line" annotations, i.e. annotations with are declared inside of a top-level
<Annotations>
element rather than being declared directly inside ("in-line") the target element.Note: If the reference only has
edmx:IncludeAnnotations
declarations, but notedmx:Include
declaration (http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_IncludedSchema), then this feature will only vocabulary annotations from the referenced models and exclude other schema elements like entity types, operations, etc.Checklist (Uncheck if it is not completed)
Additional work necessary
If documentation update is needed, please add "Docs Needed" label to the issue and provide details about the required document change in the issue.