Skip to content

Commit

Permalink
Undeclared properties should be allowed on Open Types
Browse files Browse the repository at this point in the history
Added support to ClientEdmModel to consult IEdmEntityType's IsOpen
property when instantiating EdmEntityTypeWithDelayLoadedProperties and
EdmComplexTypeWithDelayLoadedProperties.  ClientEdmModel was always
setting isOpen to false for these instances. This fixes an issue where
sending arbitrary/undeclared ODataProperty instances up would throw in
WriterValidationUtil.ValidatePropertyDefined().  This addresses
OData#143
  • Loading branch information
AdamCaviness committed Apr 22, 2015
1 parent 0223a9f commit fefc1a7
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 3 deletions.
27 changes: 25 additions & 2 deletions src/Microsoft.OData.Client/ClientEdmModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ internal sealed class ClientEdmModel : EdmElement, IEdmModel
/// <summary>Referenced core model.</summary>
private readonly IEnumerable<IEdmModel> coreModel = new IEdmModel[] { EdmCoreModel.Instance };

/// <summary>The edm entity types from the TypeResolver's service model.</summary>
private IEnumerable<IEdmEntityType> edmEntityTypes;

/// <summary>
/// Constructor.
/// </summary>
Expand Down Expand Up @@ -268,6 +271,18 @@ internal ClientTypeAnnotation GetClientTypeAnnotation(string edmTypeName)
return this.GetClientTypeAnnotation(result);
}

/// <summary>
/// Sets the entity sets which will be used to set the entity types.
/// </summary>
/// <param name="serviceModel">The service model.</param>
internal void SetEdmEntitySets(IEnumerable<IEdmEntitySet> edmEntitySets)
{
if (edmEntitySets.Any())
{
this.edmEntityTypes = edmEntitySets.Select(es => es.EntityType());
}
}

/// <summary>Returns <paramref name="type"/> and its base types, in the order of most base type first and <paramref name="type"/> last.</summary>
/// <param name="type">Type instance in question.</param>
/// <param name="keyProperties">Returns the list of key properties if <paramref name="type"/> is an entity type; null otherwise.</param>
Expand Down Expand Up @@ -402,6 +417,14 @@ private EdmTypeCacheValue GetOrCreateEdmTypeInternal(IEdmStructuredType edmBaseT
if (cachedEdmType == null)
{
Type collectionType;
bool isOpen = false;
if (edmEntityTypes != null && edmEntityTypes.Any())
{
IEdmEntityType edmEntityType = edmEntityTypes.FirstOrDefault(et => et.Name == type.Name);
if (edmEntityType != null)
isOpen = edmEntityType.IsOpen;
}

if (PrimitiveType.IsKnownNullableType(type))
{
PrimitiveType primitiveType;
Expand Down Expand Up @@ -462,7 +485,7 @@ private EdmTypeCacheValue GetOrCreateEdmTypeInternal(IEdmStructuredType edmBaseT
Debug.Assert(edmBaseType == null || edmBaseType.TypeKind == EdmTypeKind.Entity, "baseType == null || baseType.TypeKind == EdmTypeKind.Entity");
bool hasStream = GetHasStreamValue((IEdmEntityType)edmBaseType, type);
cachedEdmType = new EdmTypeCacheValue(
new EdmEntityTypeWithDelayLoadedProperties(CommonUtil.GetModelTypeNamespace(type), CommonUtil.GetModelTypeName(type), (IEdmEntityType)edmBaseType, c.PlatformHelper.IsAbstract(type), /*isOpen*/ false, hasStream, delayLoadEntityProperties),
new EdmEntityTypeWithDelayLoadedProperties(CommonUtil.GetModelTypeNamespace(type), CommonUtil.GetModelTypeName(type), (IEdmEntityType)edmBaseType, c.PlatformHelper.IsAbstract(type), isOpen, hasStream, delayLoadEntityProperties),
hasProperties);
}
else if ((enumTypeTmp = Nullable.GetUnderlyingType(type) ?? type) != null
Expand Down Expand Up @@ -513,7 +536,7 @@ private EdmTypeCacheValue GetOrCreateEdmTypeInternal(IEdmStructuredType edmBaseT
// Creating a complex type
Debug.Assert(edmBaseType == null || edmBaseType.TypeKind == EdmTypeKind.Complex, "baseType == null || baseType.TypeKind == EdmTypeKind.Complex");
cachedEdmType = new EdmTypeCacheValue(
new EdmComplexTypeWithDelayLoadedProperties(CommonUtil.GetModelTypeNamespace(type), CommonUtil.GetModelTypeName(type), (IEdmComplexType)edmBaseType, c.PlatformHelper.IsAbstract(type), /*isOpen*/ false, delayLoadComplexProperties),
new EdmComplexTypeWithDelayLoadedProperties(CommonUtil.GetModelTypeNamespace(type), CommonUtil.GetModelTypeName(type), (IEdmComplexType)edmBaseType, c.PlatformHelper.IsAbstract(type), isOpen, delayLoadComplexProperties),
hasProperties);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.OData.Client/TypeResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ internal TypeResolver(ClientEdmModel model, Func<string, Type> resolveTypeFromNa
Debug.Assert(resolveNameFromType != null, "resolveNameFromType != null");
this.resolveTypeFromName = resolveTypeFromName;
this.resolveNameFromType = resolveNameFromType;
this.clientEdmModel = model;
this.serviceModel = serviceModel;
this.clientEdmModel = model;
this.clientEdmModel.SetEdmEntitySets(serviceModel.EntityContainer.EntitySets());
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//---------------------------------------------------------------------
// <copyright file="ClientUpdateTests.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.Test.OData.Tests.Client
{
using Microsoft.OData.Client;
using System.Linq;
using Microsoft.Test.OData.Framework;
using Microsoft.Test.OData.Framework.Client;
using Microsoft.Test.OData.Services.TestServices;
using Microsoft.Test.OData.Services.TestServices.OpenTypesServiceReference;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using Microsoft.OData.Core;
using System;

/// <summary>
/// Generic client update test cases.
/// </summary>
[TestClass]
public class ClientOpenTypeUpdateTests : EndToEndTestBase
{
private DataServiceContextWrapper<DefaultContainer> contextWrapper;

public ClientOpenTypeUpdateTests()
: base(ServiceDescriptors.OpenTypesService)
{
}

[TestMethod]
public void UpdateOpenTypeWithUndeclaredProperties()
{
SetContextWrapper();
contextWrapper.MergeOption = MergeOption.PreserveChanges;
contextWrapper.Configurations.RequestPipeline.OnEntryStarting(ea => EntryStarting(ea));
var row = contextWrapper.Context.Row.Where(r => r.Id == Guid.Parse("814d505b-6b6a-45a0-9de0-153b16149d56")).First();

// In practice, transient property data would be mutated here in the partial companion to the client proxy.

contextWrapper.UpdateObject(row);
contextWrapper.SaveChanges();
}

private void EntryStarting(WritingEntryArgs ea)
{
var odataProps = ea.Entry.Properties as List<ODataProperty>;
var entityState = contextWrapper.Context.Entities.First(e => e.Entity == ea.Entity).State;

// Send up an undeclared property on an Open Type.
if (entityState == EntityStates.Modified || entityState == EntityStates.Added)
{
if (ea.Entity.GetType() == typeof(Row))
{
// In practice, the data from this undeclared property would probably be stored in a transient property of the partial companion class to the client proxy.
var undeclaredOdataProperty = new ODataProperty() { Name = "dynamicPropertyKey", Value = "dynamicPropertyValue" };
odataProps.Add(undeclaredOdataProperty);
}
}

ea.Entry.Properties = odataProps;
}
private void SetContextWrapper()
{
contextWrapper = this.CreateWrappedContext<DefaultContainer>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
<Compile Include="AnnotationTests\InstanceAnnotationTests.cs" />
<Compile Include="AsyncRequestTests\AsyncRequestTests.cs" />
<Compile Include="ClientTests\ClientEntityDescripterTests.cs" />
<Compile Include="ClientTests\ClientOpenTypeUpdateTests.cs" />
<Compile Include="ClientTests\ClientQueryTests.cs" />
<Compile Include="ClientTests\ClientDeleteTests.cs" />
<Compile Include="ClientWithoutTypeResolverTests\MismatchedClientModelWithoutTypeResolverTests.cs" />
Expand Down

0 comments on commit fefc1a7

Please sign in to comment.