Fix paths when operation is bound to a type derived from the type of a

navigation property. (#88)
This commit is contained in:
Gareth Jones 2021-02-10 16:05:26 -08:00 committed by Sam Xu
parent 1643ba5ea1
commit e1763727f5
10 changed files with 405 additions and 28 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*.{cs,vb}]
# IDE0009: Member access should be qualified.
dotnet_diagnostic.IDE0009.severity = none

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.3
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.OData.Reader", "src\Microsoft.OpenApi.OData.Reader\Microsoft.OpenApi.OData.Reader.csproj", "{FF3ACD93-19E0-486C-9C0F-FA1C2E7FC8C2}"
EndProject
@ -11,6 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OoasUtil", "src\OoasUtil\Oo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OoasGui", "src\OoasGui\OoasGui.csproj", "{79B190E8-EDB0-4C03-8FD8-EB48E4807CFB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99C5C9A7-63FD-4E78-96E8-69C402868C3E}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

View File

@ -24,7 +24,8 @@ namespace Microsoft.OpenApi.OData.Edm
/// Generate the list of <see cref="ODataPath"/> based on the given <see cref="IEdmModel"/>.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="settings">The conversion settings.</param>
/// <returns>The collection of built <see cref="ODataPath"/>.</returns>
IEnumerable<ODataPath> GetPaths(IEdmModel model);
IEnumerable<ODataPath> GetPaths(IEdmModel model, OpenApiConvertSettings settings);
}
}

View File

@ -158,7 +158,7 @@ namespace Microsoft.OpenApi.OData.Edm
/// <returns>All acceptable OData path.</returns>
private IEnumerable<ODataPath> LoadAllODataPaths()
{
IEnumerable<ODataPath> allPaths = _pathProvider.GetPaths(Model);
IEnumerable<ODataPath> allPaths = _pathProvider.GetPaths(Model, Settings);
foreach (var path in allPaths)
{
if ((path.Kind == ODataPathKind.Operation && !Settings.EnableOperationPath) ||

View File

@ -3,10 +3,12 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Vocabularies;
namespace Microsoft.OpenApi.OData.Edm
{
@ -38,8 +40,9 @@ namespace Microsoft.OpenApi.OData.Edm
/// Generate the list of <see cref="ODataPath"/> based on the given <see cref="IEdmModel"/>.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="settings">The conversion settings.</param>
/// <returns>The collection of built <see cref="ODataPath"/>.</returns>
public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model)
public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model, OpenApiConvertSettings settings)
{
if (model == null || model.EntityContainer == null)
{
@ -67,7 +70,7 @@ namespace Microsoft.OpenApi.OData.Edm
}
// bound operations
RetrieveBoundOperationPaths();
RetrieveBoundOperationPaths(settings);
// unbound operations
foreach (IEdmOperationImport import in _model.EntityContainer.OperationImports())
@ -338,7 +341,7 @@ namespace Microsoft.OpenApi.OData.Edm
/// <summary>
/// Retrieve all bounding <see cref="IEdmOperation"/>.
/// </summary>
private void RetrieveBoundOperationPaths()
private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings)
{
foreach (var edmOperation in _model.GetAllElements().OfType<IEdmOperation>().Where(e => e.IsBound))
{
@ -396,10 +399,17 @@ namespace Microsoft.OpenApi.OData.Edm
}
// 3. Search for derived
if (AppendBoundOperationOnDerived(edmOperation, isCollection, bindingEntityType))
if (AppendBoundOperationOnDerived(edmOperation, isCollection, bindingEntityType, convertSettings))
{
continue;
}
// 4. Search for derived generated navigation property
if (AppendBoundOperationOnDerivedNavigationPropertyPath(edmOperation, isCollection, bindingEntityType, convertSettings))
{
continue;
}
}
}
}
@ -477,7 +487,11 @@ namespace Microsoft.OpenApi.OData.Edm
return found;
}
private bool AppendBoundOperationOnDerived(IEdmOperation edmOperation, bool isCollection, IEdmEntityType bindingEntityType)
private bool AppendBoundOperationOnDerived(
IEdmOperation edmOperation,
bool isCollection,
IEdmEntityType bindingEntityType,
OpenApiConvertSettings convertSettings)
{
bool found = false;
@ -488,6 +502,14 @@ namespace Microsoft.OpenApi.OData.Edm
{
foreach (var ns in baseNavigationSource)
{
if (HasUnsatisfiedDerivedTypeConstraint(
ns as IEdmVocabularyAnnotatable,
baseType,
convertSettings))
{
continue;
}
if (isCollection)
{
if (ns is IEdmEntitySet)
@ -523,5 +545,84 @@ namespace Microsoft.OpenApi.OData.Edm
return found;
}
private bool HasUnsatisfiedDerivedTypeConstraint(
IEdmVocabularyAnnotatable annotatable,
IEdmEntityType baseType,
OpenApiConvertSettings convertSettings)
{
return convertSettings.RequireDerivedTypesConstraintForBoundOperations &&
!(_model.GetCollection(annotatable, "Org.OData.Validation.V1.DerivedTypeConstraint") ?? Enumerable.Empty<string>())
.Any(c => c.Equals(baseType.FullName(), StringComparison.OrdinalIgnoreCase));
}
private bool AppendBoundOperationOnDerivedNavigationPropertyPath(
IEdmOperation edmOperation,
bool isCollection,
IEdmEntityType bindingEntityType,
OpenApiConvertSettings convertSettings)
{
bool found = false;
bool isEscapedFunction = _model.IsUrlEscapeFunction(edmOperation);
foreach (var baseType in bindingEntityType.FindAllBaseTypes())
{
if (_allNavigationPropertyPaths.TryGetValue(baseType, out IList<ODataPath> paths))
{
foreach (var path in paths)
{
if (path.Kind == ODataPathKind.Ref)
{
continue;
}
var npSegment = path.Segments.Last(s => s is ODataNavigationPropertySegment)
as ODataNavigationPropertySegment;
if (npSegment == null)
{
continue;
}
bool isLastKeySegment = path.LastSegment is ODataKeySegment;
if (isCollection)
{
if (isLastKeySegment)
{
continue;
}
if (npSegment.NavigationProperty.TargetMultiplicity() != EdmMultiplicity.Many)
{
continue;
}
}
else
{
if (!isLastKeySegment && npSegment.NavigationProperty.TargetMultiplicity() ==
EdmMultiplicity.Many)
{
continue;
}
}
if (HasUnsatisfiedDerivedTypeConstraint(
npSegment.NavigationProperty as IEdmVocabularyAnnotatable,
baseType,
convertSettings))
{
continue;
}
ODataPath newPath = path.Clone();
newPath.Push(new ODataTypeCastSegment(bindingEntityType));
newPath.Push(new ODataOperationSegment(edmOperation, isEscapedFunction));
AppendPath(newPath);
found = true;
}
}
}
return found;
}
}
}

View File

@ -161,6 +161,13 @@ namespace Microsoft.OpenApi.OData
/// </summary>
public bool ShowSchemaExamples { get; set; } = false;
/// <summary>
/// Gets/Sets a value indicating whether or not to require the
/// Validation.DerivedTypeConstraint to be applied to NavigationSources
/// to bind operations of derived types to them.
/// </summary>
public bool RequireDerivedTypesConstraintForBoundOperations { get; set; } = false;
/// <summary>
/// Gets/sets a value indicating whether or not to show the root path of the described API.
/// </summary>
@ -197,6 +204,7 @@ namespace Microsoft.OpenApi.OData
EnableDerivedTypesReferencesForRequestBody = this.EnableDerivedTypesReferencesForRequestBody,
RoutePathPrefixProvider = this.RoutePathPrefixProvider,
ShowLinks = this.ShowLinks,
RequireDerivedTypesConstraintForBoundOperations = this.RequireDerivedTypesConstraintForBoundOperations,
ShowSchemaExamples = this.ShowSchemaExamples,
ShowRootPath = this.ShowRootPath,
PathProvider = this.PathProvider

View File

@ -71,6 +71,9 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="..\..\.editorconfig">
<Link>.editorconfig</Link>
</None>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>

View File

@ -74,22 +74,29 @@ namespace OoasUtil
/// Set the output to expect all derived types in request bodies.
/// </summary>
public bool? DerivedTypesReferencesForRequestBody { get; private set; }
/// <summary>
/// Set the output to expose pagination for collections.
/// </summary>
public bool? EnablePagination { get; private set; }
/// <summary>
/// tSet the output to use unqualified calls for bound operations.
/// Set the output to use unqualified calls for bound operations.
/// </summary>
public bool? EnableUnqualifiedCall { get; private set; }
/// <summary>
/// tDisable examples in the schema.
/// Disable examples in the schema.
/// </summary>
public bool? DisableSchemaExamples { get; private set; }
/// <summary>
/// Gets/Sets a value indicating whether or not to require the
/// Validation.DerivedTypeConstraint to be applied to NavigationSources
/// to bind operations of derived types to them.
/// </summary>
public bool? RequireDerivedTypesConstraint { get; private set; }
/// <summary>
/// Process the arguments.
/// </summary>
@ -186,6 +193,14 @@ namespace OoasUtil
}
break;
case "--requireDerivedTypesConstraint":
case "-rdt":
if (!ProcessRequireDerivedTypesConstraint(true))
{
return false;
}
break;
case "--enablepagination":
case "-p":
if (!ProcessEnablePagination(true))
@ -248,6 +263,11 @@ namespace OoasUtil
DerivedTypesReferencesForRequestBody = false;
}
if (RequireDerivedTypesConstraint == null)
{
RequireDerivedTypesConstraint = false;
}
if (EnablePagination == null)
{
EnablePagination = false;
@ -345,6 +365,19 @@ namespace OoasUtil
return true;
}
private bool ProcessRequireDerivedTypesConstraint(bool requireDerivedTypesConstraint)
{
if (RequireDerivedTypesConstraint != null)
{
Console.WriteLine("[Error:] Multiple [--requireDerivedTypesConstraint|-rdt] are not allowed.\n");
PrintUsage();
return false;
}
RequireDerivedTypesConstraint = requireDerivedTypesConstraint;
return true;
}
private bool ProcessEnablePagination(bool enablePagination)
{
if (EnablePagination != null)
@ -445,6 +478,7 @@ namespace OoasUtil
sb.Append(" --keyassegment|-k\t\t\tSet the output to use key-as-segment style URLs.\n");
sb.Append(" --derivedtypesreferencesforresponses|-drs\t\t\tSet the output to produce all derived types in responses.\n");
sb.Append(" --derivedtypesreferencesforrequestbody|-drq\t\t\tSet the output to expect all derived types in request bodies.\n");
sb.Append(" --requireDerivedTypesConstraint|-rdt\t\t\tSet the output to require derived type constraint to bind Operations.\n");
sb.Append(" --enablepagination|-p\t\t\tSet the output to expose pagination for collections.\n");
sb.Append(" --enableunqualifiedcall|-u\t\t\tSet the output to use unqualified calls for bound operations.\n");
sb.Append(" --disableschemaexamples|-x\t\t\tDisable examples in the schema.\n");

View File

@ -37,6 +37,7 @@ namespace OoasUtil
EnableKeyAsSegment = processer.KeyAsSegment,
EnableDerivedTypesReferencesForResponses = processer.DerivedTypesReferencesForResponses.Value,
EnableDerivedTypesReferencesForRequestBody = processer.DerivedTypesReferencesForRequestBody.Value,
RequireDerivedTypesConstraintForBoundOperations = processer.RequireDerivedTypesConstraint.Value,
EnablePagination = processer.EnablePagination.Value,
EnableUnqualifiedCall = processer.EnableUnqualifiedCall.Value,
ShowSchemaExamples = !processer.DisableSchemaExamples.Value,

View File

@ -4,12 +4,14 @@
// ------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Csdl;
using Microsoft.OData.Edm.Validation;
using Microsoft.OpenApi.OData.Tests;
using Xunit;
@ -23,9 +25,10 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
// Arrange
IEdmModel model = new EdmModel();
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
@ -37,25 +40,174 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
{
// Arrange
IEdmModel model = EdmModelHelper.GraphBetaModel;
var settings = new OpenApiConvertSettings();
ODataPathProvider provider = new ODataPathProvider();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
Assert.Equal(4887, paths.Count());
}
[Fact]
public void GetPathsForGraphBetaModelWithDerivedTypesConstraintReturnsAllPaths()
{
// Arrange
IEdmModel model = EdmModelHelper.GraphBetaModel;
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings
{
RequireDerivedTypesConstraintForBoundOperations = true
};
// Act
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
Assert.Equal(4544, paths.Count());
}
[Fact]
public void GetPathsForInheritanceModelWithoutDerivedTypesConstraintReturnsMore()
{
// Arrange
IEdmModel model = GetInheritanceModel(string.Empty);
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
Assert.Equal(3, paths.Count());
}
[Fact]
public void GetPathsForInheritanceModelWithDerivedTypesConstraintNoAnnotationReturnsFewer()
{
// Arrange
IEdmModel model = GetInheritanceModel(string.Empty);
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings
{
RequireDerivedTypesConstraintForBoundOperations = true
};
// Act
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
Assert.Equal(2, paths.Count());
}
[Fact]
public void GetPathsForInheritanceModelWithDerivedTypesConstraintWithAnnotationReturnsMore()
{
// Arrange
IEdmModel model = GetInheritanceModel(@"
<Annotation Term=""Org.OData.Validation.V1.DerivedTypeConstraint"">
<Collection>
<String>NS.Customer</String>
<String>NS.NiceCustomer</String>
</Collection>
</Annotation>");
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings
{
RequireDerivedTypesConstraintForBoundOperations = true
};
// Act
var paths = provider.GetPaths(model, settings);
// Assert
Assert.Equal(3, paths.Count());
}
#if DEBUG
// Super useful for debugging tests.
private string ListToString(IEnumerable<ODataPath> paths)
{
return string.Join(Environment.NewLine,
paths.Select(p => string.Join("/", p.Segments.Select(s => s.Identifier))));
}
#endif
[Fact]
public void GetPathsForNavPropModelWithoutDerivedTypesConstraintReturnsMore()
{
// Arrange
IEdmModel model = GetNavPropModel(string.Empty);
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
Assert.Equal(4, paths.Count());
}
[Fact]
public void GetPathsForNavPropModelWithDerivedTypesConstraintNoAnnotationReturnsFewer()
{
// Arrange
IEdmModel model = GetNavPropModel(string.Empty);
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings
{
RequireDerivedTypesConstraintForBoundOperations = true
};
// Act
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
Assert.Equal(3, paths.Count());
}
[Fact]
public void GetPathsForNavPropModelWithDerivedTypesConstraintWithAnnotationReturnsMore()
{
// Arrange
IEdmModel model = GetNavPropModel(@"
<Annotation Term=""Org.OData.Validation.V1.DerivedTypeConstraint"">
<Collection>
<String>NS.Customer</String>
<String>NS.NiceCustomer</String>
</Collection>
</Annotation>");
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings
{
RequireDerivedTypesConstraintForBoundOperations = true
};
// Act
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
Assert.Equal(4, paths.Count());
}
[Fact]
public void GetPathsForSingleEntitySetWorks()
{
// Arrange
IEdmModel model = GetEdmModel("", "");
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
@ -69,9 +221,10 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
// Arrange
IEdmModel model = GetEdmModel("", @"<Singleton Name=""Me"" Type=""NS.Customer"" />");
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
@ -90,9 +243,10 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
</Function>";
IEdmModel model = GetEdmModel(boundFunction, "");
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
@ -111,9 +265,10 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
</Action>";
IEdmModel model = GetEdmModel(boundAction, "");
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
@ -137,9 +292,10 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
IEdmModel model = GetEdmModel(boundAction, unbounds);
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
@ -165,9 +321,10 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
IEdmModel model = GetEdmModel(entityType, entitySet);
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
@ -196,9 +353,10 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
string entitySet = @"<EntitySet Name=""Orders"" EntityType=""NS.Order"" />";
IEdmModel model = GetEdmModel(entityType, entitySet);
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model, settings);
// Assert
Assert.NotNull(paths);
@ -220,9 +378,10 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
// Arrange
IEdmModel model = GetEdmModel(hasStream, streamPropName);
ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings();
// Act
var paths = provider.GetPaths(model);
var paths = provider.GetPaths(model,settings);
// Assert
Assert.NotNull(paths);
@ -250,7 +409,7 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
private static IEdmModel GetEdmModel(string schemaElement, string containerElement)
{
string template = @"<?xml version=""1.0"" encoding=""utf-16""?>
string template = $@"<?xml version=""1.0"" encoding=""utf-16""?>
<Schema Namespace=""NS"" xmlns=""http://docs.oasis-open.org/odata/ns/edm"">
<EntityType Name=""Customer"">
<Key>
@ -258,15 +417,76 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
</Key>
<Property Name=""ID"" Type=""Edm.Int32"" Nullable=""false"" />
</EntityType>
{0}
{schemaElement}
<EntityContainer Name =""Default"">
<EntitySet Name=""Customers"" EntityType=""NS.Customer"" />
{1}
{containerElement}
</EntityContainer>
</Schema>";
string schema = string.Format(template, schemaElement, containerElement);
bool parsed = SchemaReader.TryParse(new XmlReader[] { XmlReader.Create(new StringReader(schema)) }, out IEdmModel parsedModel, out _);
Assert.True(parsed);
return GetEdmModel(template);
}
private static IEdmModel GetInheritanceModel(string annotation)
{
string template = $@"<?xml version=""1.0"" encoding=""utf-16""?>
<Schema Namespace=""NS"" xmlns=""http://docs.oasis-open.org/odata/ns/edm"">
<EntityType Name=""Customer"">
<Key>
<PropertyRef Name=""ID"" />
</Key>
<Property Name=""ID"" Type=""Edm.Int32"" Nullable=""false"" />
</EntityType>
<EntityType Name=""NiceCustomer"" BaseType=""NS.Customer"">
<Property Name=""Other"" Type=""Edm.Int32"" Nullable=""true"" />
</EntityType>
<Action Name=""Ack"" IsBound=""true"" >
<Parameter Name = ""bindingParameter"" Type=""NS.NiceCustomer"" />
</Action>
<EntityContainer Name =""Default"">
<EntitySet Name=""Customers"" EntityType=""NS.Customer"">
{annotation}
</EntitySet>
</EntityContainer>
</Schema>";
return GetEdmModel(template);
}
private static IEdmModel GetNavPropModel(string annotation)
{
string template = $@"<?xml version=""1.0"" encoding=""utf-16""?>
<Schema Namespace=""NS"" xmlns=""http://docs.oasis-open.org/odata/ns/edm"">
<EntityType Name=""Root"">
<Key>
<PropertyRef Name=""ID"" />
</Key>
<Property Name=""ID"" Type=""Edm.Int32"" Nullable=""false"" />
<NavigationProperty Name=""Customers"" Type=""Collection(NS.Customer)"" ContainsTarget=""true"">
{annotation}
</NavigationProperty>
</EntityType>
<EntityType Name=""Customer"">
<Key>
<PropertyRef Name=""ID"" />
</Key>
<Property Name=""ID"" Type=""Edm.Int32"" Nullable=""false"" />
</EntityType>
<EntityType Name=""NiceCustomer"" BaseType=""NS.Customer"">
<Property Name=""Other"" Type=""Edm.Int32"" Nullable=""true"" />
</EntityType>
<Action Name=""Ack"" IsBound=""true"" >
<Parameter Name = ""bindingParameter"" Type=""NS.NiceCustomer"" />
</Action>
<EntityContainer Name =""Default"">
<Singleton Name=""Root"" Type=""NS.Root"" />
</EntityContainer>
</Schema>";
return GetEdmModel(template);
}
private static IEdmModel GetEdmModel(string schema)
{
bool parsed = SchemaReader.TryParse(new XmlReader[] { XmlReader.Create(new StringReader(schema)) }, out IEdmModel parsedModel, out IEnumerable<EdmError> errors);
Assert.True(parsed, $"Parse failure. {string.Join(Environment.NewLine, errors.Select(e => e.ToString()))}");
return parsedModel;
}